+1
-4
appview/pages/templates/repo/issues/issue.html
+1
-4
appview/pages/templates/repo/issues/issue.html
+15
-9
appview/pages/templates/repo/pulls/new.html
+15
-9
appview/pages/templates/repo/pulls/new.html
···
15
<p class="text-gray-500">
16
The branch you want to make your change against.
17
</p>
18
-
<select name="targetBranch" class="p-1 border border-gray-200 bg-white">
19
-
<option disabled selected>select a branch</option>
20
-
{{ range .Branches }}
21
-
<option
22
-
value="{{ .Reference.Name }}"
23
-
class="py-1">
24
-
{{ .Reference.Name }}
25
-
</option>
26
-
{{ end }}
27
</select>
28
</div>
29
<div>
···
44
rows="10"
45
class="w-full resize-y font-mono"
46
placeholder="Paste your git-format-patch output here."
47
></textarea>
48
</div>
49
</div>
50
<div>
···
15
<p class="text-gray-500">
16
The branch you want to make your change against.
17
</p>
18
+
<select
19
+
name="targetBranch"
20
+
class="p-1 border border-gray-200 bg-white"
21
+
>
22
+
<option disabled selected>select a branch</option>
23
+
{{ range .Branches }}
24
+
<option value="{{ .Reference.Name }}" class="py-1">
25
+
{{ .Reference.Name }}
26
+
</option>
27
+
{{ end }}
28
</select>
29
</div>
30
<div>
···
45
rows="10"
46
class="w-full resize-y font-mono"
47
placeholder="Paste your git-format-patch output here."
48
+
hx-post="/pulls/validate"
49
+
hx-trigger="input changed delay:500ms"
50
+
hx-target="#patch-validation"
51
+
hx-include="[name='patch']"
52
></textarea>
53
+
<div id="pull-validate"></div>
54
</div>
55
</div>
56
<div>
+29
-38
appview/pages/templates/repo/pulls/pull.html
+29
-38
appview/pages/templates/repo/pulls/pull.html
···
69
{{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }}
70
{{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }}
71
72
-
{{ if $isPullAuthor }}
73
<section id="update-card" class="mt-8 space-y-4 relative">
74
{{ block "resubmitCard" . }} {{ end }}
75
</section>
···
87
{{ end }}
88
</section>
89
90
-
{{ if and (or $isPullAuthor $isPushAllowed) (not .Pull.State.IsMerged) }}
91
-
{{ $action := "close" }}
92
-
{{ $icon := "circle-x" }}
93
-
{{ $hoverColor := "red" }}
94
-
{{ if .Pull.State.IsClosed }}
95
-
{{ $action = "reopen" }}
96
-
{{ $icon = "circle-dot" }}
97
-
{{ $hoverColor = "green" }}
98
-
{{ end }}
99
-
<button
100
-
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/{{ $action }}"
101
-
hx-swap="none"
102
-
class="btn mt-8 text-sm flex items-center gap-2">
103
-
<i data-lucide="{{ $icon }}" class="w-4 h-4 mr-2 text-{{ $hoverColor }}-400"></i>
104
-
<span class="text-black">{{ $action }}</span>
105
-
</button>
106
-
{{ end }}
107
-
108
<div id="pull-close"></div>
109
<div id="pull-reopen"></div>
110
{{ end }}
···
202
<div
203
id="merge-status-card"
204
class="rounded relative bg-purple-50 border border-purple-200 p-4">
205
-
{{ if gt (len .Comments) 0 }}
206
-
<div
207
-
class="absolute left-8 -top-4 w-px h-4 bg-gray-300"
208
-
></div>
209
-
{{ else }}
210
-
<div
211
-
class="absolute left-8 -top-8 w-px h-8 bg-gray-300"
212
-
></div>
213
-
{{ end }}
214
-
215
216
<div class="flex items-center gap-2 text-purple-500">
217
<i data-lucide="git-merge" class="w-4 h-4"></i>
218
<span class="font-medium"
219
-
>Pull request successfully merged</span
220
>
221
</div>
222
···
263
264
{{ define "noConflictsCard" }}
265
{{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }}
266
<div
267
id="merge-status-card"
268
class="rounded relative border bg-green-50 border-green-200 p-4">
···
291
</button>
292
{{ end }}
293
294
<div id="pull-merge-error" class="error"></div>
295
<div id="pull-merge-success" class="success"></div>
296
</div>
···
304
305
<div class="flex items-center gap-2 text-amber-500">
306
<i data-lucide="edit" class="w-4 h-4"></i>
307
-
<span class="font-medium">Resubmit your patch</span>
308
</div>
309
310
<div class="mt-2 text-sm text-gray-700">
311
-
You can update this patch to address reviews if any.
312
-
This begins a new round of reviews,
313
-
you can still view your previous submissions and reviews.
314
</div>
315
316
-
<div class="mt-4 flex items-center gap-2">
317
-
<form hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit" class="w-full">
318
<textarea
319
name="patch"
320
-
class="w-full p-2 rounded border border-gray-200"
321
-
placeholder="Enter new patch"
322
></textarea>
323
<button
324
type="submit"
···
69
{{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }}
70
{{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }}
71
72
+
{{ if and $isPullAuthor (not .Pull.State.IsMerged) }}
73
<section id="update-card" class="mt-8 space-y-4 relative">
74
{{ block "resubmitCard" . }} {{ end }}
75
</section>
···
87
{{ end }}
88
</section>
89
90
<div id="pull-close"></div>
91
<div id="pull-reopen"></div>
92
{{ end }}
···
184
<div
185
id="merge-status-card"
186
class="rounded relative bg-purple-50 border border-purple-200 p-4">
187
188
<div class="flex items-center gap-2 text-purple-500">
189
<i data-lucide="git-merge" class="w-4 h-4"></i>
190
<span class="font-medium"
191
+
>pull request successfully merged</span
192
>
193
</div>
194
···
235
236
{{ define "noConflictsCard" }}
237
{{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }}
238
+
{{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }}
239
<div
240
id="merge-status-card"
241
class="rounded relative border bg-green-50 border-green-200 p-4">
···
264
</button>
265
{{ end }}
266
267
+
{{ if and (or $isPullAuthor $isPushAllowed) (not .Pull.State.IsMerged) }}
268
+
{{ $action := "close" }}
269
+
{{ $icon := "circle-x" }}
270
+
{{ $hoverColor := "red" }}
271
+
{{ if .Pull.State.IsClosed }}
272
+
{{ $action = "reopen" }}
273
+
{{ $icon = "circle-dot" }}
274
+
{{ $hoverColor = "green" }}
275
+
{{ end }}
276
+
<button
277
+
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/{{ $action }}"
278
+
hx-swap="none"
279
+
class="btn mt-4 flex items-center gap-2">
280
+
<i data-lucide="{{ $icon }}" class="w-4 h-4 text-{{ $hoverColor }}-400"></i>
281
+
<span class="text-black">{{ $action }}</span>
282
+
</button>
283
+
{{ end }}
284
+
285
<div id="pull-merge-error" class="error"></div>
286
<div id="pull-merge-success" class="success"></div>
287
</div>
···
295
296
<div class="flex items-center gap-2 text-amber-500">
297
<i data-lucide="edit" class="w-4 h-4"></i>
298
+
<span class="font-medium">resubmit your patch</span>
299
</div>
300
301
<div class="mt-2 text-sm text-gray-700">
302
+
You can update this patch to address any reviews.
303
+
This will begin a new round of reviews,
304
+
but you'll still be able to view your previous submissions and feedback.
305
</div>
306
307
+
<div class="mt-4 flex flex-col">
308
+
<form hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit" class="w-full" hx-swap="none">
309
<textarea
310
name="patch"
311
+
class="w-full p-2 mb-2 rounded border border-gray-200"
312
+
placeholder="Paste your updated patch here."
313
></textarea>
314
<button
315
type="submit"
+37
-1
appview/state/pull.go
+37
-1
appview/state/pull.go
···
7
"log"
8
"net/http"
9
"strconv"
10
"time"
11
12
"github.com/sotangled/tangled/api/tangled"
···
300
return
301
}
302
303
tx, err := s.db.BeginTx(r.Context(), nil)
304
if err != nil {
305
log.Println("failed to start tx")
···
392
return
393
}
394
395
tx, err := s.db.BeginTx(r.Context(), nil)
396
if err != nil {
397
log.Println("failed to start tx")
···
478
}
479
480
// Merge the pull request
481
-
resp, err := ksClient.Merge([]byte(pull.LatestPatch()), user.Did, f.RepoName, pull.TargetBranch)
482
if err != nil {
483
log.Printf("failed to merge pull request: %s", err)
484
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
···
607
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
608
return
609
}
···
7
"log"
8
"net/http"
9
"strconv"
10
+
"strings"
11
"time"
12
13
"github.com/sotangled/tangled/api/tangled"
···
301
return
302
}
303
304
+
// Validate patch format
305
+
if !isPatchValid(patch) {
306
+
s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.")
307
+
return
308
+
}
309
+
310
tx, err := s.db.BeginTx(r.Context(), nil)
311
if err != nil {
312
log.Println("failed to start tx")
···
399
return
400
}
401
402
+
// Validate patch format
403
+
if !isPatchValid(patch) {
404
+
s.pages.Notice(w, "resubmit-error", "Invalid patch format. Please provide a valid diff.")
405
+
return
406
+
}
407
+
408
tx, err := s.db.BeginTx(r.Context(), nil)
409
if err != nil {
410
log.Println("failed to start tx")
···
491
}
492
493
// Merge the pull request
494
+
resp, err := ksClient.Merge([]byte(pull.LatestPatch()), user.Did, f.RepoName, pull.TargetBranch, pull.Title, pull.Body, "", "")
495
if err != nil {
496
log.Printf("failed to merge pull request: %s", err)
497
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
···
620
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
621
return
622
}
623
+
624
+
// Very basic validation to check if it looks like a diff/patch
625
+
// A valid patch usually starts with diff or --- lines
626
+
func isPatchValid(patch string) bool {
627
+
// Basic validation to check if it looks like a diff/patch
628
+
// A valid patch usually starts with diff or --- lines
629
+
if len(patch) == 0 {
630
+
return false
631
+
}
632
+
633
+
lines := strings.Split(patch, "\n")
634
+
if len(lines) < 2 {
635
+
return false
636
+
}
637
+
638
+
// Check for common patch format markers
639
+
firstLine := strings.TrimSpace(lines[0])
640
+
return strings.HasPrefix(firstLine, "diff ") ||
641
+
strings.HasPrefix(firstLine, "--- ") ||
642
+
strings.HasPrefix(firstLine, "Index: ") ||
643
+
strings.HasPrefix(firstLine, "+++ ") ||
644
+
strings.HasPrefix(firstLine, "@@ ")
645
+
}
+16
-5
appview/state/signer.go
+16
-5
appview/state/signer.go
···
10
"net/http"
11
"net/url"
12
"time"
13
)
14
15
type SignerTransport struct {
···
156
return s.client.Do(req)
157
}
158
159
-
func (s *SignedClient) Merge(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
160
const (
161
Method = "POST"
162
)
163
endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
164
165
-
body, _ := json.Marshal(map[string]interface{}{
166
-
"patch": string(patch),
167
-
"branch": branch,
168
-
})
169
170
req, err := s.newRequest(Method, endpoint, body)
171
if err != nil {
···
10
"net/http"
11
"net/url"
12
"time"
13
+
14
+
"github.com/sotangled/tangled/types"
15
)
16
17
type SignerTransport struct {
···
158
return s.client.Do(req)
159
}
160
161
+
func (s *SignedClient) Merge(
162
+
patch []byte,
163
+
ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
164
+
) (*http.Response, error) {
165
const (
166
Method = "POST"
167
)
168
endpoint := fmt.Sprintf("/%s/%s/merge", ownerDid, targetRepo)
169
+
170
+
mr := types.MergeRequest{
171
+
Branch: branch,
172
+
CommitMessage: commitMessage,
173
+
CommitBody: commitBody,
174
+
AuthorName: authorName,
175
+
AuthorEmail: authorEmail,
176
+
Patch: string(patch),
177
+
}
178
179
+
body, _ := json.Marshal(mr)
180
181
req, err := s.newRequest(Method, endpoint, body)
182
if err != nil {