+1
-4
appview/pages/templates/repo/issues/issue.html
+1
-4
appview/pages/templates/repo/issues/issue.html
···
1
-
{{ define "title" }}
2
-
{{ .Issue.Title }} · issue #{{ .Issue.IssueId }} ·
3
-
{{ .RepoInfo.FullName }}
4
-
{{ end }}
1
+
{{ define "title" }}{{ .Issue.Title }} · issue #{{ .Issue.IssueId }} ·{{ .RepoInfo.FullName }}{{ end }}
5
2
6
3
{{ define "repoContent" }}
7
4
<header class="pb-4">
+15
-9
appview/pages/templates/repo/pulls/new.html
+15
-9
appview/pages/templates/repo/pulls/new.html
···
15
15
<p class="text-gray-500">
16
16
The branch you want to make your change against.
17
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 }}
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 }}
27
28
</select>
28
29
</div>
29
30
<div>
···
44
45
rows="10"
45
46
class="w-full resize-y font-mono"
46
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']"
47
52
></textarea>
53
+
<div id="pull-validate"></div>
48
54
</div>
49
55
</div>
50
56
<div>
+29
-38
appview/pages/templates/repo/pulls/pull.html
+29
-38
appview/pages/templates/repo/pulls/pull.html
···
69
69
{{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }}
70
70
{{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }}
71
71
72
-
{{ if $isPullAuthor }}
72
+
{{ if and $isPullAuthor (not .Pull.State.IsMerged) }}
73
73
<section id="update-card" class="mt-8 space-y-4 relative">
74
74
{{ block "resubmitCard" . }} {{ end }}
75
75
</section>
···
87
87
{{ end }}
88
88
</section>
89
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
90
<div id="pull-close"></div>
109
91
<div id="pull-reopen"></div>
110
92
{{ end }}
···
202
184
<div
203
185
id="merge-status-card"
204
186
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
187
216
188
<div class="flex items-center gap-2 text-purple-500">
217
189
<i data-lucide="git-merge" class="w-4 h-4"></i>
218
190
<span class="font-medium"
219
-
>Pull request successfully merged</span
191
+
>pull request successfully merged</span
220
192
>
221
193
</div>
222
194
···
263
235
264
236
{{ define "noConflictsCard" }}
265
237
{{ $isPushAllowed := .RepoInfo.Roles.IsPushAllowed }}
238
+
{{ $isPullAuthor := and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }}
266
239
<div
267
240
id="merge-status-card"
268
241
class="rounded relative border bg-green-50 border-green-200 p-4">
···
291
264
</button>
292
265
{{ end }}
293
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
+
294
285
<div id="pull-merge-error" class="error"></div>
295
286
<div id="pull-merge-success" class="success"></div>
296
287
</div>
···
304
295
305
296
<div class="flex items-center gap-2 text-amber-500">
306
297
<i data-lucide="edit" class="w-4 h-4"></i>
307
-
<span class="font-medium">Resubmit your patch</span>
298
+
<span class="font-medium">resubmit your patch</span>
308
299
</div>
309
300
310
301
<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.
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.
314
305
</div>
315
306
316
-
<div class="mt-4 flex items-center gap-2">
317
-
<form hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit" class="w-full">
307
+
<div class="mt-4 flex flex-col">
308
+
<form hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/resubmit" class="w-full" hx-swap="none">
318
309
<textarea
319
310
name="patch"
320
-
class="w-full p-2 rounded border border-gray-200"
321
-
placeholder="Enter new patch"
311
+
class="w-full p-2 mb-2 rounded border border-gray-200"
312
+
placeholder="Paste your updated patch here."
322
313
></textarea>
323
314
<button
324
315
type="submit"
+37
-1
appview/state/pull.go
+37
-1
appview/state/pull.go
···
7
7
"log"
8
8
"net/http"
9
9
"strconv"
10
+
"strings"
10
11
"time"
11
12
12
13
"github.com/sotangled/tangled/api/tangled"
···
300
301
return
301
302
}
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
+
303
310
tx, err := s.db.BeginTx(r.Context(), nil)
304
311
if err != nil {
305
312
log.Println("failed to start tx")
···
392
399
return
393
400
}
394
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
+
395
408
tx, err := s.db.BeginTx(r.Context(), nil)
396
409
if err != nil {
397
410
log.Println("failed to start tx")
···
478
491
}
479
492
480
493
// Merge the pull request
481
-
resp, err := ksClient.Merge([]byte(pull.LatestPatch()), user.Did, f.RepoName, pull.TargetBranch)
494
+
resp, err := ksClient.Merge([]byte(pull.LatestPatch()), user.Did, f.RepoName, pull.TargetBranch, pull.Title, pull.Body, "", "")
482
495
if err != nil {
483
496
log.Printf("failed to merge pull request: %s", err)
484
497
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
···
607
620
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pull.PullId))
608
621
return
609
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
10
"net/http"
11
11
"net/url"
12
12
"time"
13
+
14
+
"github.com/sotangled/tangled/types"
13
15
)
14
16
15
17
type SignerTransport struct {
···
156
158
return s.client.Do(req)
157
159
}
158
160
159
-
func (s *SignedClient) Merge(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {
161
+
func (s *SignedClient) Merge(
162
+
patch []byte,
163
+
ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
164
+
) (*http.Response, error) {
160
165
const (
161
166
Method = "POST"
162
167
)
163
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
+
}
164
178
165
-
body, _ := json.Marshal(map[string]interface{}{
166
-
"patch": string(patch),
167
-
"branch": branch,
168
-
})
179
+
body, _ := json.Marshal(mr)
169
180
170
181
req, err := s.newRequest(Method, endpoint, body)
171
182
if err != nil {