+7
-6
appview/db/pulls.go
+7
-6
appview/db/pulls.go
···
10
type PullState int
11
12
const (
13
-
PullOpen PullState = iota
14
PullMerged
15
-
PullClosed
16
)
17
18
func (p PullState) String() string {
···
87
}
88
89
pull.PullId = nextId
90
91
_, err = tx.Exec(`
92
-
insert into pulls (repo_at, owner_did, pull_id, title, target_branch, body, patch, rkey)
93
-
values (?, ?, ?, ?, ?, ?, ?, ?)
94
-
`, pull.RepoAt, pull.OwnerDid, pull.PullId, pull.Title, pull.TargetBranch, pull.Body, pull.Patch, pull.Rkey)
95
if err != nil {
96
return err
97
}
···
134
pull_at,
135
body,
136
patch,
137
-
rkey
138
from
139
pulls
140
where
···
10
type PullState int
11
12
const (
13
+
PullClosed PullState = iota
14
+
PullOpen
15
PullMerged
16
)
17
18
func (p PullState) String() string {
···
87
}
88
89
pull.PullId = nextId
90
+
pull.State = PullOpen
91
92
_, err = tx.Exec(`
93
+
insert into pulls (repo_at, owner_did, pull_id, title, target_branch, body, patch, rkey, state)
94
+
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
95
+
`, pull.RepoAt, pull.OwnerDid, pull.PullId, pull.Title, pull.TargetBranch, pull.Body, pull.Patch, pull.Rkey, pull.State)
96
if err != nil {
97
return err
98
}
···
135
pull_at,
136
body,
137
patch,
138
+
rkey
139
from
140
pulls
141
where
+7
-2
appview/pages/pages.go
+7
-2
appview/pages/pages.go
···
271
func (r RepoInfo) TabMetadata() map[string]any {
272
meta := make(map[string]any)
273
274
+
if r.Stats.PullCount.Open > 0 {
275
+
meta["pulls"] = r.Stats.PullCount.Open
276
+
}
277
+
278
+
if r.Stats.IssueCount.Open > 0 {
279
+
meta["issues"] = r.Stats.IssueCount.Open
280
+
}
281
282
// more stuff?
283
+1
-1
appview/pages/templates/layouts/repobase.html
+1
-1
appview/pages/templates/layouts/repobase.html
+4
-4
appview/pages/templates/repo/pulls/new.html
+4
-4
appview/pages/templates/repo/pulls/new.html
···
8
>
9
<div class="flex flex-col gap-4">
10
<div>
11
-
<label for="title">title</label>
12
<input type="text" name="title" id="title" class="w-full" />
13
14
-
<label for="targetBranch">target branch</label>
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 }}"
···
27
</select>
28
</div>
29
<div>
30
-
<label for="body">body</label>
31
<textarea
32
name="body"
33
id="body"
···
8
>
9
<div class="flex flex-col gap-4">
10
<div>
11
+
<label for="title">write a title</label>
12
<input type="text" name="title" id="title" class="w-full" />
13
14
+
<label for="targetBranch">select a target branch</label>
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 }}"
···
27
</select>
28
</div>
29
<div>
30
+
<label for="body">add a description</label>
31
<textarea
32
name="body"
33
id="body"
+176
-53
appview/pages/templates/repo/pulls/pull.html
+176
-53
appview/pages/templates/repo/pulls/pull.html
···
4
{{ end }}
5
6
{{ define "repoContent" }}
7
-
8
<header class="pb-4">
9
<h1 class="text-2xl">
10
{{ .Pull.Title }}
···
17
18
{{ if .Pull.State.IsOpen }}
19
{{ $bgColor = "bg-green-600" }}
20
-
{{ $icon = "circle-dot" }}
21
{{ else if .Pull.State.IsMerged }}
22
{{ $bgColor = "bg-purple-600" }}
23
{{ $icon = "git-merge" }}
···
91
type="button"
92
class="btn btn-sm"
93
onclick="togglePatchEdit(true)"
94
>
95
<i data-lucide="edit" class="w-4 h-4 mr-1"></i>Edit
96
</button>
···
147
</script>
148
</details>
149
</div>
150
-
151
-
{{ if .MergeCheck }}
152
-
<div class="mt-4" id="merge-check">
153
-
<div
154
-
class="rounded-sm border p-4 {{ if .MergeCheck.IsConflicted }}
155
-
bg-red-50 border-red-200
156
-
{{ else }}
157
-
bg-green-50 border-green-200
158
-
{{ end }}"
159
-
>
160
-
<div
161
-
class="flex items-center gap-2 rounded-sm {{ if .MergeCheck.IsConflicted }}
162
-
text-red-500
163
-
{{ else }}
164
-
text-green-500
165
-
{{ end }}"
166
-
>
167
-
{{ if .MergeCheck.IsConflicted }}
168
-
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
169
-
<span class="font-medium"
170
-
>merge conflicts detected</span
171
-
>
172
-
{{ else }}
173
-
<i data-lucide="check-circle" class="w-4 h-4"></i>
174
-
<span class="font-medium">ready to merge</span>
175
-
{{ end }}
176
-
</div>
177
-
178
-
{{ if .MergeCheck.IsConflicted }}
179
-
<div class="mt-2">
180
-
<ul class="text-sm space-y-1">
181
-
{{ range .MergeCheck.Conflicts }}
182
-
<li class="flex items-center">
183
-
<i
184
-
data-lucide="file-warning"
185
-
class="w-3 h-3 mr-1.5 text-red-500"
186
-
></i>
187
-
<span class="font-mono"
188
-
>{{ slice .Filename 0 (sub (len .Filename) 2) }}</span
189
-
>
190
-
</li>
191
-
{{ end }}
192
-
</ul>
193
-
</div>
194
-
{{ end }}
195
-
</div>
196
-
</div>
197
-
{{ end }}
198
{{ end }}
199
200
{{ define "repoAfter" }}
···
239
</div>
240
{{ end }}
241
242
</section>
243
244
{{ if .LoggedInUser }}
245
<form
246
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/comment"
247
class="mt-8"
248
>
249
<textarea
250
name="body"
···
256
</form>
257
{{ end }}
258
259
-
{{ if eq .LoggedInUser.Did .Pull.OwnerDid }}
260
{{ $action := "close" }}
261
{{ $icon := "circle-x" }}
262
{{ $hoverColor := "red" }}
···
268
<form
269
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/{{ $action }}"
270
hx-swap="none"
271
-
class="mt-8">
272
-
<button type="submit" class="btn hover:bg-{{ $hoverColor }}-300">
273
<i
274
data-lucide="{{ $icon }}"
275
class="w-4 h-4 mr-2 text-{{ $hoverColor }}-400"
···
278
</button>
279
</form>
280
{{ end }}
281
{{ end }}
···
4
{{ end }}
5
6
{{ define "repoContent" }}
7
<header class="pb-4">
8
<h1 class="text-2xl">
9
{{ .Pull.Title }}
···
16
17
{{ if .Pull.State.IsOpen }}
18
{{ $bgColor = "bg-green-600" }}
19
+
{{ $icon = "git-pull-request" }}
20
{{ else if .Pull.State.IsMerged }}
21
{{ $bgColor = "bg-purple-600" }}
22
{{ $icon = "git-merge" }}
···
90
type="button"
91
class="btn btn-sm"
92
onclick="togglePatchEdit(true)"
93
+
{{ if or .Pull.State.IsMerged .Pull.State.IsClosed }}
94
+
disabled title="Cannot edit closed or merged
95
+
pull requests"
96
+
{{ end }}
97
>
98
<i data-lucide="edit" class="w-4 h-4 mr-1"></i>Edit
99
</button>
···
150
</script>
151
</details>
152
</div>
153
{{ end }}
154
155
{{ define "repoAfter" }}
···
194
</div>
195
{{ end }}
196
197
+
{{ if .Pull.State.IsMerged }}
198
+
<div
199
+
id="merge-status-card"
200
+
class="rounded relative bg-purple-50 border border-purple-200 p-4"
201
+
>
202
+
{{ if gt (len .Comments) 0 }}
203
+
<div
204
+
class="absolute left-8 -top-4 w-px h-4 bg-gray-300"
205
+
></div>
206
+
{{ else }}
207
+
<div
208
+
class="absolute left-8 -top-8 w-px h-8 bg-gray-300"
209
+
></div>
210
+
{{ end }}
211
+
212
+
213
+
<div class="flex items-center gap-2 text-purple-500">
214
+
<i data-lucide="git-merge" class="w-4 h-4"></i>
215
+
<span class="font-medium"
216
+
>Pull request successfully merged</span
217
+
>
218
+
</div>
219
+
220
+
<div class="mt-2 text-sm text-gray-700">
221
+
<p>
222
+
This pull request has been merged into the base branch.
223
+
</p>
224
+
</div>
225
+
</div>
226
+
{{ else if .MergeCheck }}
227
+
<div
228
+
id="merge-status-card"
229
+
class="rounded relative {{ if .MergeCheck.IsConflicted }}
230
+
bg-red-50 border border-red-200
231
+
{{ else }}
232
+
bg-green-50 border border-green-200
233
+
{{ end }} p-4"
234
+
>
235
+
{{ if gt (len .Comments) 0 }}
236
+
<div
237
+
class="absolute left-8 -top-4 w-px h-4 bg-gray-300"
238
+
></div>
239
+
{{ else }}
240
+
<div
241
+
class="absolute left-8 -top-8 w-px h-8 bg-gray-300"
242
+
></div>
243
+
{{ end }}
244
+
245
+
246
+
<div
247
+
class="flex items-center gap-2 {{ if .MergeCheck.IsConflicted }}
248
+
text-red-500
249
+
{{ else }}
250
+
text-green-500
251
+
{{ end }}"
252
+
>
253
+
{{ if .MergeCheck.IsConflicted }}
254
+
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
255
+
<span class="font-medium"
256
+
>merge conflicts detected</span
257
+
>
258
+
{{ else }}
259
+
<i data-lucide="check-circle" class="w-4 h-4"></i>
260
+
<span class="font-medium">ready to merge</span>
261
+
{{ end }}
262
+
</div>
263
+
264
+
{{ if .MergeCheck.IsConflicted }}
265
+
<div class="mt-2">
266
+
<ul class="text-sm space-y-1">
267
+
{{ range .MergeCheck.Conflicts }}
268
+
<li class="flex items-center">
269
+
<i
270
+
data-lucide="file-warning"
271
+
class="w-3 h-3 mr-1.5 text-red-500"
272
+
></i>
273
+
<span class="font-mono"
274
+
>{{ slice .Filename 0 (sub (len .Filename) 2) }}</span
275
+
>
276
+
</li>
277
+
{{ end }}
278
+
</ul>
279
+
</div>
280
+
<div class="mt-3 text-sm text-gray-700">
281
+
<p>
282
+
Please resolve these conflicts locally and update
283
+
the patch to continue with the merge.
284
+
</p>
285
+
</div>
286
+
{{ else }}
287
+
<div class="mt-2 text-sm text-gray-700">
288
+
<p>
289
+
No conflicts detected with the base branch. This
290
+
pull request can be merged safely.
291
+
</p>
292
+
</div>
293
+
{{ if and .LoggedInUser (eq .LoggedInUser.Did .RepoInfo.OwnerDid) }}
294
+
<div class="mt-4 flex items-center gap-2">
295
+
<form
296
+
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/merge"
297
+
hx-swap="none"
298
+
>
299
+
<input
300
+
type="hidden"
301
+
name="targetBranch"
302
+
value="{{ .Pull.TargetBranch }}"
303
+
/>
304
+
<input
305
+
type="hidden"
306
+
name="patch"
307
+
value="{{ .Pull.Patch }}"
308
+
/>
309
+
<button
310
+
type="submit"
311
+
class="btn flex items-center gap-2"
312
+
{{ if or .Pull.State.IsClosed .MergeCheck.IsConflicted }}
313
+
disabled
314
+
{{ end }}
315
+
>
316
+
<i
317
+
data-lucide="git-merge"
318
+
class="w-4 h-4 text-purple-500"
319
+
></i>
320
+
<span>merge</span>
321
+
</button>
322
+
</form>
323
+
324
+
{{ if or (eq .LoggedInUser.Did .Pull.OwnerDid) (eq .LoggedInUser.Did .RepoInfo.OwnerDid) }}
325
+
{{ $action := "close" }}
326
+
{{ $icon := "circle-x" }}
327
+
{{ $hoverColor := "red" }}
328
+
{{ if .Pull.State.IsClosed }}
329
+
{{ $action = "reopen" }}
330
+
{{ $icon = "circle-dot" }}
331
+
{{ $hoverColor = "green" }}
332
+
{{ end }}
333
+
<form
334
+
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/{{ $action }}"
335
+
hx-swap="none"
336
+
>
337
+
<button
338
+
type="submit"
339
+
class="btn flex items-center gap-2"
340
+
>
341
+
<i
342
+
data-lucide="{{ $icon }}"
343
+
class="w-4 h-4 text-{{ $hoverColor }}-400"
344
+
></i>
345
+
<span>{{ $action }}</span>
346
+
</button>
347
+
</form>
348
+
<div id="pull-merge-error" class="error"></div>
349
+
<div
350
+
id="pull-merge-success"
351
+
class="success"
352
+
></div>
353
+
{{ end }}
354
+
</div>
355
+
{{ end }}
356
+
{{ end }}
357
+
</div>
358
+
{{ end }}
359
</section>
360
361
{{ if .LoggedInUser }}
362
<form
363
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/comment"
364
class="mt-8"
365
+
hx-swap="none"
366
>
367
<textarea
368
name="body"
···
374
</form>
375
{{ end }}
376
377
+
{{ if and (or (eq .LoggedInUser.Did .Pull.OwnerDid) (eq .LoggedInUser.Did .RepoInfo.OwnerDid)) (not .MergeCheck) (not .Pull.State.IsMerged) }}
378
{{ $action := "close" }}
379
{{ $icon := "circle-x" }}
380
{{ $hoverColor := "red" }}
···
386
<form
387
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/{{ $action }}"
388
hx-swap="none"
389
+
class="mt-8"
390
+
>
391
+
<button type="submit" class="btn text-sm flex items-center gap-2">
392
<i
393
data-lucide="{{ $icon }}"
394
class="w-4 h-4 mr-2 text-{{ $hoverColor }}-400"
···
397
</button>
398
</form>
399
{{ end }}
400
+
401
+
402
+
<div id="pull-close"></div>
403
+
<div id="pull-reopen"></div>
404
{{ end }}
+259
-18
appview/state/repo.go
+259
-18
appview/state/repo.go
···
523
}
524
}
525
526
-
secret, err := db.GetRegistrationKey(s.db, f.Knot)
527
-
if err != nil {
528
-
log.Printf("failed to get registration key for %s", f.Knot)
529
-
s.pages.Notice(w, "pull", "Failed to load pull request. Try again later.")
530
-
return
531
-
}
532
-
533
var mergeCheckResponse types.MergeCheckResponse
534
-
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
535
-
if err == nil {
536
-
resp, err := ksClient.MergeCheck([]byte(pr.Patch), pr.OwnerDid, f.RepoName, pr.TargetBranch)
537
if err != nil {
538
-
log.Println("failed to check for mergeability:", err)
539
-
} else {
540
-
respBody, err := io.ReadAll(resp.Body)
541
if err != nil {
542
-
log.Println("failed to read merge check response body")
543
} else {
544
-
err = json.Unmarshal(respBody, &mergeCheckResponse)
545
if err != nil {
546
-
log.Println("failed to unmarshal merge check response", err)
547
}
548
}
549
}
550
-
} else {
551
-
log.Printf("failed to setup signed client for %s; ignoring...", f.Knot)
552
}
553
554
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
···
1448
DidHandleMap: didHandleMap,
1449
FilteringBy: state,
1450
})
1451
return
1452
}
1453
···
523
}
524
}
525
526
var mergeCheckResponse types.MergeCheckResponse
527
+
528
+
// Only perform merge check if the pull request is not already merged
529
+
if pr.State != db.PullMerged {
530
+
secret, err := db.GetRegistrationKey(s.db, f.Knot)
531
if err != nil {
532
+
log.Printf("failed to get registration key for %s", f.Knot)
533
+
s.pages.Notice(w, "pull", "Failed to load pull request. Try again later.")
534
+
return
535
+
}
536
+
537
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
538
+
if err == nil {
539
+
resp, err := ksClient.MergeCheck([]byte(pr.Patch), pr.OwnerDid, f.RepoName, pr.TargetBranch)
540
if err != nil {
541
+
log.Println("failed to check for mergeability:", err)
542
} else {
543
+
respBody, err := io.ReadAll(resp.Body)
544
if err != nil {
545
+
log.Println("failed to read merge check response body")
546
+
} else {
547
+
err = json.Unmarshal(respBody, &mergeCheckResponse)
548
+
if err != nil {
549
+
log.Println("failed to unmarshal merge check response", err)
550
+
}
551
}
552
}
553
+
} else {
554
+
log.Printf("failed to setup signed client for %s; ignoring...", f.Knot)
555
}
556
}
557
558
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
···
1452
DidHandleMap: didHandleMap,
1453
FilteringBy: state,
1454
})
1455
+
return
1456
+
}
1457
+
1458
+
func (s *State) MergePull(w http.ResponseWriter, r *http.Request) {
1459
+
user := s.auth.GetUser(r)
1460
+
f, err := fullyResolvedRepo(r)
1461
+
if err != nil {
1462
+
log.Println("failed to resolve repo:", err)
1463
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1464
+
return
1465
+
}
1466
+
1467
+
// Get the pull request ID from the request URL
1468
+
pullId := chi.URLParam(r, "pull")
1469
+
pullIdInt, err := strconv.Atoi(pullId)
1470
+
if err != nil {
1471
+
log.Println("failed to parse pull ID:", err)
1472
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1473
+
return
1474
+
}
1475
+
1476
+
// Get the patch data from the request body
1477
+
patch := r.FormValue("patch")
1478
+
branch := r.FormValue("targetBranch")
1479
+
1480
+
secret, err := db.GetRegistrationKey(s.db, f.Knot)
1481
+
if err != nil {
1482
+
log.Printf("no registration key found for domain %s: %s\n", f.Knot, err)
1483
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1484
+
return
1485
+
}
1486
+
1487
+
ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev)
1488
+
if err != nil {
1489
+
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
1490
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1491
+
return
1492
+
}
1493
+
1494
+
// Merge the pull request
1495
+
resp, err := ksClient.Merge([]byte(patch), user.Did, f.RepoName, branch)
1496
+
if err != nil {
1497
+
log.Printf("failed to merge pull request: %s", err)
1498
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1499
+
return
1500
+
}
1501
+
1502
+
if resp.StatusCode == http.StatusOK {
1503
+
err := db.MergePull(s.db, f.RepoAt, pullIdInt)
1504
+
if err != nil {
1505
+
log.Printf("failed to update pull request status in database: %s", err)
1506
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1507
+
return
1508
+
}
1509
+
s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s/pulls/%d", f.OwnerHandle(), f.RepoName, pullIdInt))
1510
+
} else {
1511
+
log.Printf("knotserver returned non-OK status code for merge: %d", resp.StatusCode)
1512
+
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
1513
+
}
1514
+
}
1515
+
1516
+
func (s *State) PullComment(w http.ResponseWriter, r *http.Request) {
1517
+
user := s.auth.GetUser(r)
1518
+
f, err := fullyResolvedRepo(r)
1519
+
if err != nil {
1520
+
log.Println("failed to get repo and knot", err)
1521
+
return
1522
+
}
1523
+
1524
+
pullId := chi.URLParam(r, "pull")
1525
+
pullIdInt, err := strconv.Atoi(pullId)
1526
+
if err != nil {
1527
+
http.Error(w, "bad pull id", http.StatusBadRequest)
1528
+
log.Println("failed to parse pull id", err)
1529
+
return
1530
+
}
1531
+
1532
+
switch r.Method {
1533
+
case http.MethodPost:
1534
+
body := r.FormValue("body")
1535
+
if body == "" {
1536
+
s.pages.Notice(w, "pull", "Comment body is required")
1537
+
return
1538
+
}
1539
+
1540
+
// Start a transaction
1541
+
tx, err := s.db.BeginTx(r.Context(), nil)
1542
+
if err != nil {
1543
+
log.Println("failed to start transaction", err)
1544
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
1545
+
return
1546
+
}
1547
+
defer tx.Rollback() // Will be ignored if we commit
1548
+
1549
+
commentId := rand.IntN(1000000)
1550
+
createdAt := time.Now().Format(time.RFC3339)
1551
+
commentIdInt64 := int64(commentId)
1552
+
ownerDid := user.Did
1553
+
1554
+
pullAt, err := db.GetPullAt(s.db, f.RepoAt, pullIdInt)
1555
+
if err != nil {
1556
+
log.Println("failed to get pull at", err)
1557
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
1558
+
return
1559
+
}
1560
+
1561
+
atUri := f.RepoAt.String()
1562
+
client, _ := s.auth.AuthorizedClient(r)
1563
+
atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
1564
+
Collection: tangled.RepoPullCommentNSID,
1565
+
Repo: user.Did,
1566
+
Rkey: s.TID(),
1567
+
Record: &lexutil.LexiconTypeDecoder{
1568
+
Val: &tangled.RepoPullComment{
1569
+
Repo: &atUri,
1570
+
Pull: pullAt,
1571
+
CommentId: &commentIdInt64,
1572
+
Owner: &ownerDid,
1573
+
Body: &body,
1574
+
CreatedAt: &createdAt,
1575
+
},
1576
+
},
1577
+
})
1578
+
if err != nil {
1579
+
log.Println("failed to create pull comment", err)
1580
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
1581
+
return
1582
+
}
1583
+
1584
+
// Create the pull comment in the database with the commentAt field
1585
+
err = db.NewPullComment(tx, &db.PullComment{
1586
+
OwnerDid: user.Did,
1587
+
RepoAt: f.RepoAt.String(),
1588
+
CommentId: commentId,
1589
+
PullId: pullIdInt,
1590
+
Body: body,
1591
+
CommentAt: atResp.Uri,
1592
+
})
1593
+
if err != nil {
1594
+
log.Println("failed to create pull comment", err)
1595
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
1596
+
return
1597
+
}
1598
+
1599
+
// Commit the transaction
1600
+
if err = tx.Commit(); err != nil {
1601
+
log.Println("failed to commit transaction", err)
1602
+
s.pages.Notice(w, "pull-comment", "Failed to create comment.")
1603
+
return
1604
+
}
1605
+
1606
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d#comment-%d", f.OwnerSlashRepo(), pullIdInt, commentId))
1607
+
return
1608
+
}
1609
+
}
1610
+
1611
+
func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) {
1612
+
f, err := fullyResolvedRepo(r)
1613
+
if err != nil {
1614
+
log.Println("malformed middleware")
1615
+
return
1616
+
}
1617
+
1618
+
pullId := chi.URLParam(r, "pull")
1619
+
pullIdInt, err := strconv.Atoi(pullId)
1620
+
if err != nil {
1621
+
log.Println("malformed middleware")
1622
+
return
1623
+
}
1624
+
1625
+
// Start a transaction
1626
+
tx, err := s.db.BeginTx(r.Context(), nil)
1627
+
if err != nil {
1628
+
log.Println("failed to start transaction", err)
1629
+
s.pages.Notice(w, "pull-close", "Failed to close pull.")
1630
+
return
1631
+
}
1632
+
1633
+
// Close the pull in the database
1634
+
err = db.ClosePull(tx, f.RepoAt, pullIdInt)
1635
+
if err != nil {
1636
+
log.Println("failed to close pull", err)
1637
+
s.pages.Notice(w, "pull-close", "Failed to close pull.")
1638
+
return
1639
+
}
1640
+
1641
+
// Commit the transaction
1642
+
if err = tx.Commit(); err != nil {
1643
+
log.Println("failed to commit transaction", err)
1644
+
s.pages.Notice(w, "pull-close", "Failed to close pull.")
1645
+
return
1646
+
}
1647
+
1648
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullIdInt))
1649
+
return
1650
+
}
1651
+
1652
+
func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) {
1653
+
f, err := fullyResolvedRepo(r)
1654
+
if err != nil {
1655
+
log.Println("failed to resolve repo", err)
1656
+
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
1657
+
return
1658
+
}
1659
+
1660
+
// Start a transaction
1661
+
tx, err := s.db.BeginTx(r.Context(), nil)
1662
+
if err != nil {
1663
+
log.Println("failed to start transaction", err)
1664
+
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
1665
+
return
1666
+
}
1667
+
1668
+
pullId := chi.URLParam(r, "pull")
1669
+
pullIdInt, err := strconv.Atoi(pullId)
1670
+
if err != nil {
1671
+
log.Println("failed to parse pull id", err)
1672
+
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
1673
+
return
1674
+
}
1675
+
1676
+
// Reopen the pull in the database
1677
+
err = db.ReopenPull(tx, f.RepoAt, pullIdInt)
1678
+
if err != nil {
1679
+
log.Println("failed to reopen pull", err)
1680
+
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
1681
+
return
1682
+
}
1683
+
1684
+
// Commit the transaction
1685
+
if err = tx.Commit(); err != nil {
1686
+
log.Println("failed to commit transaction", err)
1687
+
s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.")
1688
+
return
1689
+
}
1690
+
1691
+
s.pages.HxLocation(w, fmt.Sprintf("/%s/pulls/%d", f.OwnerSlashRepo(), pullIdInt))
1692
return
1693
}
1694
+4
-4
appview/state/router.go
+4
-4
appview/state/router.go
···
65
r.Get("/new", s.NewPull)
66
r.Post("/new", s.NewPull)
67
r.Patch("/{pull}/patch", s.EditPatch)
68
-
// r.Post("/{pull}/comment", s.PullComment)
69
-
// r.Post("/{pull}/close", s.ClosePull)
70
-
// r.Post("/{pull}/reopen", s.ReopenPull)
71
-
// r.Post("/{pull}/merge", s.MergePull)
72
})
73
})
74
+3
-1
input.css
+3
-1
input.css
···
139
hover:before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#f5f5f5]
140
focus:outline-none focus-visible:before:outline
141
focus-visible:before:outline-4 focus-visible:before:outline-gray-500
142
-
active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)];
143
}
144
}
145
@layer utilities {
···
139
hover:before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#f5f5f5]
140
focus:outline-none focus-visible:before:outline
141
focus-visible:before:outline-4 focus-visible:before:outline-gray-500
142
+
active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)]
143
+
disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:before:border-gray-200
144
+
disabled:hover:before:bg-white disabled:hover:before:shadow-none;
145
}
146
}
147
@layer utilities {