+1
-1
appview/db/db.go
+1
-1
appview/db/db.go
···
114
114
pull_at text,
115
115
rkey text not null,
116
116
target_branch text not null,
117
-
open integer not null default 1,
117
+
state integer not null default 0 check (state in (0, 1, 2)), -- open, merged, closed
118
118
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
119
119
unique(repo_at, pull_id),
120
120
foreign key (repo_at) references repos(at_uri) on delete cascade
+70
-15
appview/db/pulls.go
+70
-15
appview/db/pulls.go
···
7
7
"github.com/bluesky-social/indigo/atproto/syntax"
8
8
)
9
9
10
+
type PullState int
11
+
12
+
const (
13
+
PullOpen PullState = iota
14
+
PullMerged
15
+
PullClosed
16
+
)
17
+
18
+
func (p PullState) String() string {
19
+
switch p {
20
+
case PullOpen:
21
+
return "open"
22
+
case PullMerged:
23
+
return "merged"
24
+
case PullClosed:
25
+
return "closed"
26
+
default:
27
+
return "closed"
28
+
}
29
+
}
30
+
31
+
func (p PullState) IsOpen() bool {
32
+
return p == PullOpen
33
+
}
34
+
func (p PullState) IsMerged() bool {
35
+
return p == PullMerged
36
+
}
37
+
func (p PullState) IsClosed() bool {
38
+
return p == PullClosed
39
+
}
40
+
10
41
type Pull struct {
11
42
ID int
12
43
OwnerDid string
···
17
48
PullId int
18
49
Title string
19
50
Body string
20
-
Open int
51
+
State PullState
21
52
Created time.Time
22
53
Rkey string
23
54
}
···
89
120
return pullId - 1, err
90
121
}
91
122
92
-
func GetPulls(e Execer, repoAt syntax.ATURI) ([]Pull, error) {
123
+
func GetPulls(e Execer, repoAt syntax.ATURI, state PullState) ([]Pull, error) {
93
124
var pulls []Pull
94
125
95
-
rows, err := e.Query(`select owner_did, pull_id, created, title, open, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? order by created desc`, repoAt)
126
+
rows, err := e.Query(`
127
+
select
128
+
owner_did,
129
+
pull_id,
130
+
created,
131
+
title,
132
+
state,
133
+
target_branch,
134
+
pull_at,
135
+
body,
136
+
patch,
137
+
rkey
138
+
from
139
+
pulls
140
+
where
141
+
repo_at = ? and state = ?
142
+
order by
143
+
created desc`, repoAt, state)
96
144
if err != nil {
97
145
return nil, err
98
146
}
···
101
149
for rows.Next() {
102
150
var pull Pull
103
151
var createdAt string
104
-
err := rows.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.Open, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
152
+
err := rows.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.State, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
105
153
if err != nil {
106
154
return nil, err
107
155
}
···
123
171
}
124
172
125
173
func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) {
126
-
query := `select owner_did, created, title, open, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?`
174
+
query := `select owner_did, created, title, state, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?`
127
175
row := e.QueryRow(query, repoAt, pullId)
128
176
129
177
var pull Pull
130
178
var createdAt string
131
-
err := row.Scan(&pull.OwnerDid, &createdAt, &pull.Title, &pull.Open, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
179
+
err := row.Scan(&pull.OwnerDid, &createdAt, &pull.Title, &pull.State, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
132
180
if err != nil {
133
181
return nil, err
134
182
}
···
143
191
}
144
192
145
193
func GetPullWithComments(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, []PullComment, error) {
146
-
query := `select owner_did, pull_id, created, title, open, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?`
194
+
query := `select owner_did, pull_id, created, title, state, target_branch, pull_at, body, patch, rkey from pulls where repo_at = ? and pull_id = ?`
147
195
row := e.QueryRow(query, repoAt, pullId)
148
196
149
197
var pull Pull
150
198
var createdAt string
151
-
err := row.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.Open, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
199
+
err := row.Scan(&pull.OwnerDid, &pull.PullId, &createdAt, &pull.Title, &pull.State, &pull.TargetBranch, &pull.PullAt, &pull.Body, &pull.Patch, &pull.Rkey)
152
200
if err != nil {
153
201
return nil, nil, err
154
202
}
···
217
265
return comments, nil
218
266
}
219
267
268
+
func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState PullState) error {
269
+
_, err := e.Exec(`update pulls set state = ? where repo_at = ? and pull_id = ?`, pullState, repoAt, pullId)
270
+
return err
271
+
}
272
+
220
273
func ClosePull(e Execer, repoAt syntax.ATURI, pullId int) error {
221
-
_, err := e.Exec(`update pulls set open = 0 where repo_at = ? and pull_id = ?`, repoAt, pullId)
274
+
err := SetPullState(e, repoAt, pullId, PullClosed)
222
275
return err
223
276
}
224
277
225
278
func ReopenPull(e Execer, repoAt syntax.ATURI, pullId int) error {
226
-
_, err := e.Exec(`update pulls set open = 1 where repo_at = ? and pull_id = ?`, repoAt, pullId)
279
+
err := SetPullState(e, repoAt, pullId, PullOpen)
227
280
return err
228
281
}
229
282
230
283
func MergePull(e Execer, repoAt syntax.ATURI, pullId int) error {
231
-
_, err := e.Exec(`update pulls set open = 2 where repo_at = ? and pull_id = ?`, repoAt, pullId)
284
+
err := SetPullState(e, repoAt, pullId, PullMerged)
232
285
return err
233
286
}
234
287
235
288
type PullCount struct {
236
289
Open int
290
+
Merged int
237
291
Closed int
238
292
}
239
293
240
294
func GetPullCount(e Execer, repoAt syntax.ATURI) (PullCount, error) {
241
295
row := e.QueryRow(`
242
296
select
243
-
count(case when open = 1 then 1 end) as open_count,
244
-
count(case when open = 0 then 1 end) as closed_count
297
+
count(case when state = 0 then 1 end) as open_count,
298
+
count(case when state = 1 then 1 end) as merged_count,
299
+
count(case when state = 2 then 1 end) as closed_count
245
300
from pulls
246
301
where repo_at = ?`,
247
302
repoAt,
248
303
)
249
304
250
305
var count PullCount
251
-
if err := row.Scan(&count.Open, &count.Closed); err != nil {
252
-
return PullCount{0, 0}, err
306
+
if err := row.Scan(&count.Open, &count.Merged, &count.Closed); err != nil {
307
+
return PullCount{0, 0, 0}, err
253
308
}
254
309
255
310
return count, nil
+1
appview/db/repos.go
+1
appview/db/repos.go
+2
-9
appview/pages/pages.go
+2
-9
appview/pages/pages.go
···
272
272
meta := make(map[string]any)
273
273
274
274
meta["issues"] = r.Stats.IssueCount.Open
275
+
meta["pulls"] = r.Stats.PullCount.Open
275
276
276
277
// more stuff?
277
278
···
524
525
Pulls []db.Pull
525
526
Active string
526
527
DidHandleMap map[string]string
528
+
FilteringBy db.PullState
527
529
}
528
530
529
531
func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error {
···
536
538
RepoInfo RepoInfo
537
539
DidHandleMap map[string]string
538
540
Pull db.Pull
539
-
State string
540
541
PullOwnerHandle string
541
542
Comments []db.PullComment
542
543
Active string
···
544
545
}
545
546
546
547
func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
547
-
switch params.Pull.Open {
548
-
case 0:
549
-
params.State = "close"
550
-
case 1:
551
-
params.State = "open"
552
-
case 2:
553
-
params.State = "merged"
554
-
}
555
548
params.Active = "pulls"
556
549
return p.executeRepo("repo/pulls/pull", w, params)
557
550
}
+1
-1
appview/pages/templates/repo/pulls/new.html
+1
-1
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 class="p-1 border border-gray-200 bg-white">
18
+
<select name="targetBranch" class="p-1 border border-gray-200 bg-white">
19
19
<option disabled selected>Select a branch</option>
20
20
{{ range .Branches }}
21
21
<option
+9
-7
appview/pages/templates/repo/pulls/pull.html
+9
-7
appview/pages/templates/repo/pulls/pull.html
···
14
14
15
15
{{ $bgColor := "bg-gray-800" }}
16
16
{{ $icon := "ban" }}
17
-
{{ if eq .State "open" }}
17
+
18
+
{{ if .Pull.State.IsOpen }}
18
19
{{ $bgColor = "bg-green-600" }}
19
20
{{ $icon = "circle-dot" }}
20
-
{{ else if eq .State "merged" }}
21
+
{{ else if .Pull.State.IsMerged }}
21
22
{{ $bgColor = "bg-purple-600" }}
22
23
{{ $icon = "git-merge" }}
23
24
{{ end }}
···
33
34
data-lucide="{{ $icon }}"
34
35
class="w-4 h-4 mr-1.5 text-white"
35
36
></i>
36
-
<span class="text-white">{{ .State }}</span>
37
+
<span class="text-white">{{ .Pull.State.String }}</span>
37
38
</div>
38
39
<span class="text-gray-400 text-sm">
39
40
opened by
···
41
42
<a href="/{{ $owner }}" class="no-underline hover:underline"
42
43
>{{ $owner }}</a
43
44
>
44
-
<span class="px-1 select-none before:content-['\00B7']"></span>
45
+
<span class="select-none before:content-['\00B7']"></span>
45
46
<time>{{ .Pull.Created | timeFmt }}</time>
47
+
<span class="select-none before:content-['\00B7']"></span>
48
+
<time>targeting branch {{ .Pull.TargetBranch }}</time>
46
49
</span>
47
50
</div>
48
51
···
257
260
{{ $action := "close" }}
258
261
{{ $icon := "circle-x" }}
259
262
{{ $hoverColor := "red" }}
260
-
{{ if eq .State "closed" }}
263
+
{{ if .Pull.State.IsClosed }}
261
264
{{ $action = "reopen" }}
262
265
{{ $icon = "circle-dot" }}
263
266
{{ $hoverColor = "green" }}
···
265
268
<form
266
269
hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/{{ $action }}"
267
270
hx-swap="none"
268
-
class="mt-8"
269
-
>
271
+
class="mt-8">
270
272
<button type="submit" class="btn hover:bg-{{ $hoverColor }}-300">
271
273
<i
272
274
data-lucide="{{ $icon }}"
+9
-21
appview/pages/templates/repo/pulls/pulls.html
+9
-21
appview/pages/templates/repo/pulls/pulls.html
···
8
8
class="border px-1 bg-white border-gray-200"
9
9
onchange="window.location.href = '/{{ .RepoInfo.FullName }}/pulls?state=' + this.value"
10
10
>
11
-
<option value="open" {{ if .FilteringByOpen }}selected{{ end }}>
11
+
<option value="open" {{ if .FilteringBy.IsOpen }}selected{{ end }}>
12
12
open
13
13
</option>
14
-
<option
15
-
value="closed"
16
-
{{ if eq .FilteringState "closed" }}selected{{ end }}
17
-
>
18
-
closed
19
-
</option>
20
-
<option
21
-
value="merged"
22
-
{{ if eq .FilteringState "merged" }}selected{{ end }}
23
-
>
14
+
<option value="merged" {{ if .FilteringBy.IsMerged }}selected{{ end }}>
24
15
merged
25
16
</option>
17
+
<option value="closed" {{ if .FilteringBy.IsClosed }}selected{{ end }}>
18
+
closed
19
+
</option>
26
20
</select>
27
21
pull requests
28
22
</p>
···
42
36
{{ range .Pulls }}
43
37
<div class="rounded drop-shadow-sm bg-white px-6 py-4">
44
38
<div class="pb-2">
45
-
<a
46
-
href="/{{ $.RepoInfo.FullName }}/pulls/{{ .PullId }}"
47
-
class="no-underline hover:underline"
48
-
>
39
+
<a href="/{{ $.RepoInfo.FullName }}/pulls/{{ .PullId }}">
49
40
{{ .Title }}
50
41
<span class="text-gray-500">#{{ .PullId }}</span>
51
42
</a>
···
53
44
<p class="text-sm text-gray-500">
54
45
{{ $bgColor := "bg-gray-800" }}
55
46
{{ $icon := "ban" }}
56
-
{{ $state := "closed" }}
57
47
58
-
{{ if eq .Open 1 }}
48
+
{{ if .State.IsOpen }}
59
49
{{ $bgColor = "bg-green-600" }}
60
50
{{ $icon = "git-pull-request" }}
61
-
{{ $state = "open" }}
62
-
{{ else if eq .Open 2 }}
51
+
{{ else if .State.IsMerged }}
63
52
{{ $bgColor = "bg-purple-600" }}
64
53
{{ $icon = "git-merge" }}
65
-
{{ $state = "merged" }}
66
54
{{ end }}
67
55
68
56
···
73
61
data-lucide="{{ $icon }}"
74
62
class="w-3 h-3 mr-1.5 text-white"
75
63
></i>
76
-
<span class="text-white">{{ $state }}</span>
64
+
<span class="text-white">{{ .State.String }}</span>
77
65
</span>
78
66
79
67
<span>
+18
-2
appview/state/repo.go
+18
-2
appview/state/repo.go
···
413
413
targetBranch := r.FormValue("targetBranch")
414
414
patch := r.FormValue("patch")
415
415
416
-
if title == "" || body == "" || patch == "" {
416
+
if title == "" || body == "" || patch == "" || targetBranch == "" {
417
417
s.pages.Notice(w, "pull", "Title, body and patch diff are required.")
418
418
return
419
419
}
···
990
990
if err != nil {
991
991
log.Println("failed to get issue count for ", f.RepoAt)
992
992
}
993
+
pullCount, err := db.GetPullCount(s.db, f.RepoAt)
994
+
if err != nil {
995
+
log.Println("failed to get issue count for ", f.RepoAt)
996
+
}
993
997
994
998
knot := f.Knot
995
999
if knot == "knot1.tangled.sh" {
···
1008
1012
Stats: db.RepoStats{
1009
1013
StarCount: starCount,
1010
1014
IssueCount: issueCount,
1015
+
PullCount: pullCount,
1011
1016
},
1012
1017
}
1013
1018
}
···
1383
1388
1384
1389
func (s *State) RepoPulls(w http.ResponseWriter, r *http.Request) {
1385
1390
user := s.auth.GetUser(r)
1391
+
params := r.URL.Query()
1392
+
1393
+
state := db.PullOpen
1394
+
switch params.Get("state") {
1395
+
case "closed":
1396
+
state = db.PullClosed
1397
+
case "merged":
1398
+
state = db.PullMerged
1399
+
}
1400
+
1386
1401
f, err := fullyResolvedRepo(r)
1387
1402
if err != nil {
1388
1403
log.Println("failed to get repo and knot", err)
1389
1404
return
1390
1405
}
1391
1406
1392
-
pulls, err := db.GetPulls(s.db, f.RepoAt)
1407
+
pulls, err := db.GetPulls(s.db, f.RepoAt, state)
1393
1408
if err != nil {
1394
1409
log.Println("failed to get pulls", err)
1395
1410
s.pages.Notice(w, "pulls", "Failed to load pulls. Try again later.")
···
1415
1430
RepoInfo: f.RepoInfo(s, user),
1416
1431
Pulls: pulls,
1417
1432
DidHandleMap: didHandleMap,
1433
+
FilteringBy: state,
1418
1434
})
1419
1435
return
1420
1436
}