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