-30
api/tangled/repodeleteBranch.go
-30
api/tangled/repodeleteBranch.go
···
1
-
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2
-
3
-
package tangled
4
-
5
-
// schema: sh.tangled.repo.deleteBranch
6
-
7
-
import (
8
-
"context"
9
-
10
-
"github.com/bluesky-social/indigo/lex/util"
11
-
)
12
-
13
-
const (
14
-
RepoDeleteBranchNSID = "sh.tangled.repo.deleteBranch"
15
-
)
16
-
17
-
// RepoDeleteBranch_Input is the input argument to a sh.tangled.repo.deleteBranch call.
18
-
type RepoDeleteBranch_Input struct {
19
-
Branch string `json:"branch" cborgen:"branch"`
20
-
Repo string `json:"repo" cborgen:"repo"`
21
-
}
22
-
23
-
// RepoDeleteBranch calls the XRPC method "sh.tangled.repo.deleteBranch".
24
-
func RepoDeleteBranch(ctx context.Context, c util.LexClient, input *RepoDeleteBranch_Input) error {
25
-
if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.repo.deleteBranch", nil, input, nil); err != nil {
26
-
return err
27
-
}
28
-
29
-
return nil
30
-
}
+10
-38
appview/db/timeline.go
+10
-38
appview/db/timeline.go
···
9
9
10
10
// TODO: this gathers heterogenous events from different sources and aggregates
11
11
// them in code; if we did this entirely in sql, we could order and limit and paginate easily
12
-
func MakeTimeline(e Execer, limit int, loggedInUserDid string, limitToUsersIsFollowing bool) ([]models.TimelineEvent, error) {
12
+
func MakeTimeline(e Execer, limit int, loggedInUserDid string) ([]models.TimelineEvent, error) {
13
13
var events []models.TimelineEvent
14
14
15
-
var userIsFollowing []string
16
-
if limitToUsersIsFollowing {
17
-
following, err := GetFollowing(e, loggedInUserDid)
18
-
if err != nil {
19
-
return nil, err
20
-
}
21
-
22
-
userIsFollowing = make([]string, 0, len(following))
23
-
for _, follow := range following {
24
-
userIsFollowing = append(userIsFollowing, follow.SubjectDid)
25
-
}
26
-
}
27
-
28
-
repos, err := getTimelineRepos(e, limit, loggedInUserDid, userIsFollowing)
15
+
repos, err := getTimelineRepos(e, limit, loggedInUserDid)
29
16
if err != nil {
30
17
return nil, err
31
18
}
32
19
33
-
stars, err := getTimelineStars(e, limit, loggedInUserDid, userIsFollowing)
20
+
stars, err := getTimelineStars(e, limit, loggedInUserDid)
34
21
if err != nil {
35
22
return nil, err
36
23
}
37
24
38
-
follows, err := getTimelineFollows(e, limit, loggedInUserDid, userIsFollowing)
25
+
follows, err := getTimelineFollows(e, limit, loggedInUserDid)
39
26
if err != nil {
40
27
return nil, err
41
28
}
···
83
70
return isStarred, starCount
84
71
}
85
72
86
-
func getTimelineRepos(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
87
-
filters := make([]filter, 0)
88
-
if userIsFollowing != nil {
89
-
filters = append(filters, FilterIn("did", userIsFollowing))
90
-
}
91
-
92
-
repos, err := GetRepos(e, limit, filters...)
73
+
func getTimelineRepos(e Execer, limit int, loggedInUserDid string) ([]models.TimelineEvent, error) {
74
+
repos, err := GetRepos(e, limit)
93
75
if err != nil {
94
76
return nil, err
95
77
}
···
143
125
return events, nil
144
126
}
145
127
146
-
func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
147
-
filters := make([]filter, 0)
148
-
if userIsFollowing != nil {
149
-
filters = append(filters, FilterIn("starred_by_did", userIsFollowing))
150
-
}
151
-
152
-
stars, err := GetStars(e, limit, filters...)
128
+
func getTimelineStars(e Execer, limit int, loggedInUserDid string) ([]models.TimelineEvent, error) {
129
+
stars, err := GetStars(e, limit)
153
130
if err != nil {
154
131
return nil, err
155
132
}
···
189
166
return events, nil
190
167
}
191
168
192
-
func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
193
-
filters := make([]filter, 0)
194
-
if userIsFollowing != nil {
195
-
filters = append(filters, FilterIn("user_did", userIsFollowing))
196
-
}
197
-
198
-
follows, err := GetFollows(e, limit, filters...)
169
+
func getTimelineFollows(e Execer, limit int, loggedInUserDid string) ([]models.TimelineEvent, error) {
170
+
follows, err := GetFollows(e, limit)
199
171
if err != nil {
200
172
return nil, err
201
173
}
-5
appview/models/pull.go
-5
appview/models/pull.go
+4
-4
appview/pages/funcmap.go
+4
-4
appview/pages/funcmap.go
···
265
265
return nil
266
266
},
267
267
"i": func(name string, classes ...string) template.HTML {
268
-
data, err := p.icon(name, classes)
268
+
data, err := icon(name, classes)
269
269
if err != nil {
270
270
log.Printf("icon %s does not exist", name)
271
-
data, _ = p.icon("airplay", classes)
271
+
data, _ = icon("airplay", classes)
272
272
}
273
273
return template.HTML(data)
274
274
},
275
-
"cssContentHash": p.CssContentHash,
275
+
"cssContentHash": CssContentHash,
276
276
"fileTree": filetree.FileTree,
277
277
"pathEscape": func(s string) string {
278
278
return url.PathEscape(s)
···
325
325
return fmt.Sprintf("%s/%s/%s?%s", p.avatar.Host, signature, handle, sizeArg)
326
326
}
327
327
328
-
func (p *Pages) icon(name string, classes []string) (template.HTML, error) {
328
+
func icon(name string, classes []string) (template.HTML, error) {
329
329
iconPath := filepath.Join("static", "icons", name)
330
330
331
331
if filepath.Ext(name) == "" {
+1
-6
appview/pages/markup/markdown.go
+1
-6
appview/pages/markup/markdown.go
···
5
5
"bytes"
6
6
"fmt"
7
7
"io"
8
-
"io/fs"
9
8
"net/url"
10
9
"path"
11
10
"strings"
···
21
20
"github.com/yuin/goldmark/renderer/html"
22
21
"github.com/yuin/goldmark/text"
23
22
"github.com/yuin/goldmark/util"
24
-
callout "gitlab.com/staticnoise/goldmark-callout"
25
23
htmlparse "golang.org/x/net/html"
26
24
27
25
"tangled.org/core/api/tangled"
···
47
45
IsDev bool
48
46
RendererType RendererType
49
47
Sanitizer Sanitizer
50
-
Files fs.FS
51
48
}
52
49
53
50
func (rctx *RenderContext) RenderMarkdown(source string) string {
···
65
62
extension.WithFootnoteIDPrefix([]byte("footnote")),
66
63
),
67
64
treeblood.MathML(),
68
-
callout.CalloutExtention,
69
65
),
70
66
goldmark.WithParserOptions(
71
67
parser.WithAutoHeadingID(),
···
144
140
func visitNode(ctx *RenderContext, node *htmlparse.Node) {
145
141
switch node.Type {
146
142
case htmlparse.ElementNode:
147
-
switch node.Data {
148
-
case "img", "source":
143
+
if node.Data == "img" || node.Data == "source" {
149
144
for i, attr := range node.Attr {
150
145
if attr.Key != "src" {
151
146
continue
-3
appview/pages/markup/sanitizer.go
-3
appview/pages/markup/sanitizer.go
+19
-22
appview/pages/pages.go
+19
-22
appview/pages/pages.go
···
61
61
CamoUrl: config.Camo.Host,
62
62
CamoSecret: config.Camo.SharedSecret,
63
63
Sanitizer: markup.NewSanitizer(),
64
-
Files: Files,
65
64
}
66
65
67
66
p := &Pages{
···
1129
1128
}
1130
1129
1131
1130
type RepoSinglePullParams struct {
1132
-
LoggedInUser *oauth.User
1133
-
RepoInfo repoinfo.RepoInfo
1134
-
Active string
1135
-
Pull *models.Pull
1136
-
Stack models.Stack
1137
-
AbandonedPulls []*models.Pull
1138
-
BranchDeleteStatus *models.BranchDeleteStatus
1139
-
MergeCheck types.MergeCheckResponse
1140
-
ResubmitCheck ResubmitResult
1141
-
Pipelines map[string]models.Pipeline
1131
+
LoggedInUser *oauth.User
1132
+
RepoInfo repoinfo.RepoInfo
1133
+
Active string
1134
+
Pull *models.Pull
1135
+
Stack models.Stack
1136
+
AbandonedPulls []*models.Pull
1137
+
MergeCheck types.MergeCheckResponse
1138
+
ResubmitCheck ResubmitResult
1139
+
Pipelines map[string]models.Pipeline
1142
1140
1143
1141
OrderedReactionKinds []models.ReactionKind
1144
1142
Reactions map[models.ReactionKind]models.ReactionDisplayData
···
1234
1232
}
1235
1233
1236
1234
type PullActionsParams struct {
1237
-
LoggedInUser *oauth.User
1238
-
RepoInfo repoinfo.RepoInfo
1239
-
Pull *models.Pull
1240
-
RoundNumber int
1241
-
MergeCheck types.MergeCheckResponse
1242
-
ResubmitCheck ResubmitResult
1243
-
BranchDeleteStatus *models.BranchDeleteStatus
1244
-
Stack models.Stack
1235
+
LoggedInUser *oauth.User
1236
+
RepoInfo repoinfo.RepoInfo
1237
+
Pull *models.Pull
1238
+
RoundNumber int
1239
+
MergeCheck types.MergeCheckResponse
1240
+
ResubmitCheck ResubmitResult
1241
+
Stack models.Stack
1245
1242
}
1246
1243
1247
1244
func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error {
···
1478
1475
return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static")))
1479
1476
}
1480
1477
1481
-
sub, err := fs.Sub(p.embedFS, "static")
1478
+
sub, err := fs.Sub(Files, "static")
1482
1479
if err != nil {
1483
1480
p.logger.Error("no static dir found? that's crazy", "err", err)
1484
1481
panic(err)
···
1501
1498
})
1502
1499
}
1503
1500
1504
-
func (p *Pages) CssContentHash() string {
1505
-
cssFile, err := p.embedFS.Open("static/tw.css")
1501
+
func CssContentHash() string {
1502
+
cssFile, err := Files.Open("static/tw.css")
1506
1503
if err != nil {
1507
1504
slog.Debug("Error opening CSS file", "err", err)
1508
1505
return ""
+2
-2
appview/pages/templates/layouts/base.html
+2
-2
appview/pages/templates/layouts/base.html
···
26
26
</head>
27
27
<body class="min-h-screen flex flex-col gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white transition-colors duration-200">
28
28
{{ block "topbarLayout" . }}
29
-
<header class="w-full col-span-full md:col-span-1 md:col-start-2" style="z-index: 20;">
29
+
<header class="w-full bg-white dark:bg-gray-800 col-span-full md:col-span-1 md:col-start-2" style="z-index: 20;">
30
30
31
31
{{ if .LoggedInUser }}
32
32
<div id="upgrade-banner"
···
58
58
{{ end }}
59
59
60
60
{{ block "footerLayout" . }}
61
-
<footer class="mt-12">
61
+
<footer class="bg-white dark:bg-gray-800 mt-12">
62
62
{{ template "layouts/fragments/footer" . }}
63
63
</footer>
64
64
{{ end }}
+1
-1
appview/pages/templates/layouts/fragments/topbar.html
+1
-1
appview/pages/templates/layouts/fragments/topbar.html
···
1
1
{{ define "layouts/fragments/topbar" }}
2
-
<nav class="mx-auto space-x-4 px-6 py-2 rounded-b dark:text-white drop-shadow-sm bg-white dark:bg-gray-800">
2
+
<nav class="mx-auto space-x-4 px-6 py-2 rounded-b dark:text-white drop-shadow-sm">
3
3
<div class="flex justify-between p-0 items-center">
4
4
<div id="left-items">
5
5
<a href="/" hx-boost="true" class="text-2xl no-underline hover:no-underline flex items-center gap-2">
+3
-3
appview/pages/templates/repo/commit.html
+3
-3
appview/pages/templates/repo/commit.html
···
80
80
{{end}}
81
81
82
82
{{ define "topbarLayout" }}
83
-
<header class="col-span-full" style="z-index: 20;">
83
+
<header class="px-1 col-span-full" style="z-index: 20;">
84
84
{{ template "layouts/fragments/topbar" . }}
85
85
</header>
86
86
{{ end }}
87
87
88
88
{{ define "mainLayout" }}
89
-
<div class="px-1 flex-grow col-span-full flex flex-col gap-4">
89
+
<div class="px-1 col-span-full flex flex-col gap-4">
90
90
{{ block "contentLayout" . }}
91
91
{{ block "content" . }}{{ end }}
92
92
{{ end }}
···
105
105
{{ end }}
106
106
107
107
{{ define "footerLayout" }}
108
-
<footer class="col-span-full mt-12">
108
+
<footer class="px-1 col-span-full mt-12">
109
109
{{ template "layouts/fragments/footer" . }}
110
110
</footer>
111
111
{{ end }}
-11
appview/pages/templates/repo/pulls/fragments/pullActions.html
-11
appview/pages/templates/repo/pulls/fragments/pullActions.html
···
33
33
<span>comment</span>
34
34
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
35
35
</button>
36
-
{{ if .BranchDeleteStatus }}
37
-
<button
38
-
hx-delete="/{{ .BranchDeleteStatus.Repo.Did }}/{{ .BranchDeleteStatus.Repo.Name }}/branches"
39
-
hx-vals='{"branch": "{{ .BranchDeleteStatus.Branch }}" }'
40
-
hx-swap="none"
41
-
class="btn p-2 flex items-center gap-2 no-underline hover:no-underline group text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300">
42
-
{{ i "git-branch" "w-4 h-4" }}
43
-
<span>delete branch</span>
44
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
45
-
</button>
46
-
{{ end }}
47
36
{{ if and $isPushAllowed $isOpen $isLastRound }}
48
37
{{ $disabled := "" }}
49
38
{{ if $isConflicted }}
+14
-1
appview/pages/templates/repo/pulls/interdiff.html
+14
-1
appview/pages/templates/repo/pulls/interdiff.html
···
28
28
29
29
{{ end }}
30
30
31
+
{{ define "topbarLayout" }}
32
+
<header class="px-1 col-span-full" style="z-index: 20;">
33
+
{{ template "layouts/fragments/topbar" . }}
34
+
</header>
35
+
{{ end }}
36
+
31
37
{{ define "mainLayout" }}
32
-
<div class="px-1 col-span-full flex-grow flex flex-col gap-4">
38
+
<div class="px-1 col-span-full flex flex-col gap-4">
33
39
{{ block "contentLayout" . }}
34
40
{{ block "content" . }}{{ end }}
35
41
{{ end }}
···
46
52
{{ end }}
47
53
</div>
48
54
{{ end }}
55
+
56
+
{{ define "footerLayout" }}
57
+
<footer class="px-1 col-span-full mt-12">
58
+
{{ template "layouts/fragments/footer" . }}
59
+
</footer>
60
+
{{ end }}
61
+
49
62
50
63
{{ define "contentAfter" }}
51
64
{{ template "repo/fragments/interdiff" (list .RepoInfo.FullName .Interdiff .DiffOpts) }}
+13
-1
appview/pages/templates/repo/pulls/patch.html
+13
-1
appview/pages/templates/repo/pulls/patch.html
···
34
34
</section>
35
35
{{ end }}
36
36
37
+
{{ define "topbarLayout" }}
38
+
<header class="px-1 col-span-full" style="z-index: 20;">
39
+
{{ template "layouts/fragments/topbar" . }}
40
+
</header>
41
+
{{ end }}
42
+
37
43
{{ define "mainLayout" }}
38
-
<div class="px-1 col-span-full flex-grow flex flex-col gap-4">
44
+
<div class="px-1 col-span-full flex flex-col gap-4">
39
45
{{ block "contentLayout" . }}
40
46
{{ block "content" . }}{{ end }}
41
47
{{ end }}
···
51
57
</div>
52
58
{{ end }}
53
59
</div>
60
+
{{ end }}
61
+
62
+
{{ define "footerLayout" }}
63
+
<footer class="px-1 col-span-full mt-12">
64
+
{{ template "layouts/fragments/footer" . }}
65
+
</footer>
54
66
{{ end }}
55
67
56
68
{{ define "contentAfter" }}
+1
-10
appview/pages/templates/repo/pulls/pull.html
+1
-10
appview/pages/templates/repo/pulls/pull.html
···
187
187
{{ end }}
188
188
189
189
{{ if $.LoggedInUser }}
190
-
{{ template "repo/pulls/fragments/pullActions"
191
-
(dict
192
-
"LoggedInUser" $.LoggedInUser
193
-
"Pull" $.Pull
194
-
"RepoInfo" $.RepoInfo
195
-
"RoundNumber" .RoundNumber
196
-
"MergeCheck" $.MergeCheck
197
-
"ResubmitCheck" $.ResubmitCheck
198
-
"BranchDeleteStatus" $.BranchDeleteStatus
199
-
"Stack" $.Stack) }}
190
+
{{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck "Stack" $.Stack) }}
200
191
{{ else }}
201
192
<div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded drop-shadow-sm p-2 relative flex gap-2 items-center w-fit">
202
193
<a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2">
+19
-69
appview/pulls/pulls.go
+19
-69
appview/pulls/pulls.go
···
98
98
}
99
99
100
100
mergeCheckResponse := s.mergeCheck(r, f, pull, stack)
101
-
branchDeleteStatus := s.branchDeleteStatus(r, f, pull)
102
101
resubmitResult := pages.Unknown
103
102
if user.Did == pull.OwnerDid {
104
103
resubmitResult = s.resubmitCheck(r, f, pull, stack)
105
104
}
106
105
107
106
s.pages.PullActionsFragment(w, pages.PullActionsParams{
108
-
LoggedInUser: user,
109
-
RepoInfo: f.RepoInfo(user),
110
-
Pull: pull,
111
-
RoundNumber: roundNumber,
112
-
MergeCheck: mergeCheckResponse,
113
-
ResubmitCheck: resubmitResult,
114
-
BranchDeleteStatus: branchDeleteStatus,
115
-
Stack: stack,
107
+
LoggedInUser: user,
108
+
RepoInfo: f.RepoInfo(user),
109
+
Pull: pull,
110
+
RoundNumber: roundNumber,
111
+
MergeCheck: mergeCheckResponse,
112
+
ResubmitCheck: resubmitResult,
113
+
Stack: stack,
116
114
})
117
115
return
118
116
}
···
155
153
}
156
154
157
155
mergeCheckResponse := s.mergeCheck(r, f, pull, stack)
158
-
branchDeleteStatus := s.branchDeleteStatus(r, f, pull)
159
156
resubmitResult := pages.Unknown
160
157
if user != nil && user.Did == pull.OwnerDid {
161
158
resubmitResult = s.resubmitCheck(r, f, pull, stack)
···
220
217
}
221
218
222
219
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
223
-
LoggedInUser: user,
224
-
RepoInfo: repoInfo,
225
-
Pull: pull,
226
-
Stack: stack,
227
-
AbandonedPulls: abandonedPulls,
228
-
BranchDeleteStatus: branchDeleteStatus,
229
-
MergeCheck: mergeCheckResponse,
230
-
ResubmitCheck: resubmitResult,
231
-
Pipelines: m,
220
+
LoggedInUser: user,
221
+
RepoInfo: repoInfo,
222
+
Pull: pull,
223
+
Stack: stack,
224
+
AbandonedPulls: abandonedPulls,
225
+
MergeCheck: mergeCheckResponse,
226
+
ResubmitCheck: resubmitResult,
227
+
Pipelines: m,
232
228
233
229
OrderedReactionKinds: models.OrderedReactionKinds,
234
230
Reactions: reactionMap,
···
305
301
return result
306
302
}
307
303
308
-
func (s *Pulls) branchDeleteStatus(r *http.Request, f *reporesolver.ResolvedRepo, pull *models.Pull) *models.BranchDeleteStatus {
309
-
if pull.State != models.PullMerged {
310
-
return nil
311
-
}
312
-
313
-
user := s.oauth.GetUser(r)
314
-
if user == nil {
315
-
return nil
316
-
}
317
-
318
-
var branch string
319
-
var repo *models.Repo
320
-
// check if the branch exists
321
-
// NOTE: appview could cache branches/tags etc. for every repo by listening for gitRefUpdates
322
-
if pull.IsBranchBased() {
323
-
branch = pull.PullSource.Branch
324
-
repo = &f.Repo
325
-
} else if pull.IsForkBased() {
326
-
branch = pull.PullSource.Branch
327
-
repo = pull.PullSource.Repo
328
-
} else {
329
-
return nil
330
-
}
331
-
332
-
scheme := "http"
333
-
if !s.config.Core.Dev {
334
-
scheme = "https"
335
-
}
336
-
host := fmt.Sprintf("%s://%s", scheme, repo.Knot)
337
-
xrpcc := &indigoxrpc.Client{
338
-
Host: host,
339
-
}
340
-
341
-
resp, err := tangled.RepoBranch(r.Context(), xrpcc, branch, fmt.Sprintf("%s/%s", repo.Did, repo.Name))
342
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
343
-
return nil
344
-
}
345
-
346
-
return &models.BranchDeleteStatus{
347
-
Repo: repo,
348
-
Branch: resp.Name,
349
-
}
350
-
}
351
-
352
304
func (s *Pulls) resubmitCheck(r *http.Request, f *reporesolver.ResolvedRepo, pull *models.Pull, stack models.Stack) pages.ResubmitResult {
353
305
if pull.State == models.PullMerged || pull.State == models.PullDeleted || pull.PullSource == nil {
354
306
return pages.Unknown
···
1201
1153
Repo: string(f.RepoAt()),
1202
1154
Branch: targetBranch,
1203
1155
},
1204
-
Patch: patch,
1205
-
Source: recordPullSource,
1206
-
CreatedAt: time.Now().Format(time.RFC3339),
1156
+
Patch: patch,
1157
+
Source: recordPullSource,
1207
1158
},
1208
1159
},
1209
1160
})
···
1854
1805
Repo: string(f.RepoAt()),
1855
1806
Branch: pull.TargetBranch,
1856
1807
},
1857
-
Patch: patch, // new patch
1858
-
Source: recordPullSource,
1859
-
CreatedAt: time.Now().Format(time.RFC3339),
1808
+
Patch: patch, // new patch
1809
+
Source: recordPullSource,
1860
1810
},
1861
1811
},
1862
1812
})
+3
-3
appview/repo/opengraph.go
+3
-3
appview/repo/opengraph.go
···
30
30
contentCard, bottomArea := mainCard.Split(false, 75)
31
31
32
32
// Add padding to content
33
-
contentCard.SetMargin(50)
33
+
contentCard.SetMargin(30)
34
34
35
35
// Split content horizontally: main content (80%) and avatar area (20%)
36
36
mainContent, avatarArea := contentCard.Split(true, 80)
···
82
82
// Draw description (DrawText handles multi-line wrapping automatically)
83
83
descriptionCard.SetMargin(10)
84
84
description := repo.Description
85
-
if len(description) > 70 {
86
-
description = description[:70] + "…"
85
+
if len(description) > 80 {
86
+
description = description[:100] + "…"
87
87
}
88
88
89
89
_, err = descriptionCard.DrawText(description, color.RGBA{88, 96, 105, 255}, 36, ogcard.Top, ogcard.Left)
-47
appview/repo/repo.go
-47
appview/repo/repo.go
···
628
628
})
629
629
}
630
630
631
-
func (rp *Repo) DeleteBranch(w http.ResponseWriter, r *http.Request) {
632
-
f, err := rp.repoResolver.Resolve(r)
633
-
if err != nil {
634
-
log.Println("failed to get repo and knot", err)
635
-
return
636
-
}
637
-
638
-
noticeId := "delete-branch-error"
639
-
fail := func(msg string, err error) {
640
-
log.Println(msg, "err", err)
641
-
rp.pages.Notice(w, noticeId, msg)
642
-
}
643
-
644
-
branch := r.FormValue("branch")
645
-
if branch == "" {
646
-
fail("No branch provided.", nil)
647
-
return
648
-
}
649
-
650
-
client, err := rp.oauth.ServiceClient(
651
-
r,
652
-
oauth.WithService(f.Knot),
653
-
oauth.WithLxm(tangled.RepoDeleteBranchNSID),
654
-
oauth.WithDev(rp.config.Core.Dev),
655
-
)
656
-
if err != nil {
657
-
fail("Failed to connect to knotserver", nil)
658
-
return
659
-
}
660
-
661
-
err = tangled.RepoDeleteBranch(
662
-
r.Context(),
663
-
client,
664
-
&tangled.RepoDeleteBranch_Input{
665
-
Branch: branch,
666
-
Repo: f.RepoAt().String(),
667
-
},
668
-
)
669
-
if err := xrpcclient.HandleXrpcErr(err); err != nil {
670
-
fail(fmt.Sprintf("Failed to delete branch: %s", err), err)
671
-
return
672
-
}
673
-
log.Println("deleted branch from knot", "branch", branch, "repo", f.RepoAt())
674
-
675
-
rp.pages.HxRefresh(w)
676
-
}
677
-
678
631
func (rp *Repo) RepoBlob(w http.ResponseWriter, r *http.Request) {
679
632
f, err := rp.repoResolver.Resolve(r)
680
633
if err != nil {
-1
appview/repo/router.go
-1
appview/repo/router.go
+2
-8
appview/state/state.go
+2
-8
appview/state/state.go
···
268
268
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
269
269
user := s.oauth.GetUser(r)
270
270
271
-
// TODO: set this flag based on the UI
272
-
filtered := false
273
-
274
271
var userDid string
275
272
if user != nil {
276
273
userDid = user.Did
277
274
}
278
-
timeline, err := db.MakeTimeline(s.db, 50, userDid, filtered)
275
+
timeline, err := db.MakeTimeline(s.db, 50, userDid)
279
276
if err != nil {
280
277
log.Println(err)
281
278
s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
···
339
336
}
340
337
341
338
func (s *State) Home(w http.ResponseWriter, r *http.Request) {
342
-
// TODO: set this flag based on the UI
343
-
filtered := false
344
-
345
-
timeline, err := db.MakeTimeline(s.db, 5, "", filtered)
339
+
timeline, err := db.MakeTimeline(s.db, 5, "")
346
340
if err != nil {
347
341
log.Println(err)
348
342
s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
-1
go.mod
-1
go.mod
···
160
160
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
161
161
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
162
162
github.com/wyatt915/treeblood v0.1.15 // indirect
163
-
gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab // indirect
164
163
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
165
164
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
166
165
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
-2
go.sum
-2
go.sum
···
444
444
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
445
445
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
446
446
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
447
-
gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab h1:gK9tS6QJw5F0SIhYJnGG2P83kuabOdmWBbSmZhJkz2A=
448
-
gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab/go.mod h1:SPu13/NPe1kMrbGoJldQwqtpNhXsmIuHCfm/aaGjU0c=
449
447
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
450
448
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
451
449
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
+12
-71
input.css
+12
-71
input.css
···
134
134
}
135
135
136
136
.prose hr {
137
-
@apply my-2;
137
+
@apply my-2;
138
138
}
139
139
140
140
.prose li:has(input) {
141
-
@apply list-none;
141
+
@apply list-none;
142
142
}
143
143
144
144
.prose ul:has(input) {
145
-
@apply pl-2;
145
+
@apply pl-2;
146
146
}
147
147
148
148
.prose .heading .anchor {
149
-
@apply no-underline mx-2 opacity-0;
149
+
@apply no-underline mx-2 opacity-0;
150
150
}
151
151
152
152
.prose .heading:hover .anchor {
153
-
@apply opacity-70;
153
+
@apply opacity-70;
154
154
}
155
155
156
156
.prose .heading .anchor:hover {
157
-
@apply opacity-70;
157
+
@apply opacity-70;
158
158
}
159
159
160
160
.prose a.footnote-backref {
161
-
@apply no-underline;
161
+
@apply no-underline;
162
162
}
163
163
164
164
.prose li {
165
-
@apply my-0 py-0;
165
+
@apply my-0 py-0;
166
166
}
167
167
168
-
.prose ul,
169
-
.prose ol {
170
-
@apply my-1 py-0;
168
+
.prose ul, .prose ol {
169
+
@apply my-1 py-0;
171
170
}
172
171
173
172
.prose img {
···
177
176
}
178
177
179
178
.prose input {
180
-
@apply inline-block my-0 mb-1 mx-1;
179
+
@apply inline-block my-0 mb-1 mx-1;
181
180
}
182
181
183
182
.prose input[type="checkbox"] {
184
183
@apply disabled:accent-blue-500 checked:accent-blue-500 disabled:checked:accent-blue-500;
185
184
}
186
-
187
-
/* Base callout */
188
-
details[data-callout] {
189
-
@apply border-l-4 pl-3 py-2 text-gray-800 dark:text-gray-200 my-4;
190
-
}
191
-
192
-
details[data-callout] > summary {
193
-
@apply font-bold cursor-pointer mb-1;
194
-
}
195
-
196
-
details[data-callout] > .callout-content {
197
-
@apply text-sm leading-snug;
198
-
}
199
-
200
-
/* Note (blue) */
201
-
details[data-callout="note" i] {
202
-
@apply border-blue-400 dark:border-blue-500;
203
-
}
204
-
details[data-callout="note" i] > summary {
205
-
@apply text-blue-700 dark:text-blue-400;
206
-
}
207
-
208
-
/* Important (purple) */
209
-
details[data-callout="important" i] {
210
-
@apply border-purple-400 dark:border-purple-500;
211
-
}
212
-
details[data-callout="important" i] > summary {
213
-
@apply text-purple-700 dark:text-purple-400;
214
-
}
215
-
216
-
/* Warning (yellow) */
217
-
details[data-callout="warning" i] {
218
-
@apply border-yellow-400 dark:border-yellow-500;
219
-
}
220
-
details[data-callout="warning" i] > summary {
221
-
@apply text-yellow-700 dark:text-yellow-400;
222
-
}
223
-
224
-
/* Caution (red) */
225
-
details[data-callout="caution" i] {
226
-
@apply border-red-400 dark:border-red-500;
227
-
}
228
-
details[data-callout="caution" i] > summary {
229
-
@apply text-red-700 dark:text-red-400;
230
-
}
231
-
232
-
/* Tip (green) */
233
-
details[data-callout="tip" i] {
234
-
@apply border-green-400 dark:border-green-500;
235
-
}
236
-
details[data-callout="tip" i] > summary {
237
-
@apply text-green-700 dark:text-green-400;
238
-
}
239
-
240
-
/* Optional: hide the disclosure arrow like GitHub */
241
-
details[data-callout] > summary::-webkit-details-marker {
242
-
display: none;
243
-
}
244
185
}
245
186
@layer utilities {
246
187
.error {
···
287
228
}
288
229
/* LineHighlight */
289
230
.chroma .hl {
290
-
@apply bg-amber-400/30 dark:bg-amber-500/20;
231
+
@apply bg-amber-400/30 dark:bg-amber-500/20;
291
232
}
292
233
293
234
/* LineNumbersTable */
-5
knotserver/git/branch.go
-5
knotserver/git/branch.go
-11
knotserver/git/git.go
-11
knotserver/git/git.go
···
71
71
return &g, nil
72
72
}
73
73
74
-
// re-open a repository and update references
75
-
func (g *GitRepo) Refresh() error {
76
-
refreshed, err := PlainOpen(g.path)
77
-
if err != nil {
78
-
return err
79
-
}
80
-
81
-
*g = *refreshed
82
-
return nil
83
-
}
84
-
85
74
func (g *GitRepo) Commits(offset, limit int) ([]*object.Commit, error) {
86
75
commits := []*object.Commit{}
87
76
+37
-150
knotserver/git/merge.go
+37
-150
knotserver/git/merge.go
···
4
4
"bytes"
5
5
"crypto/sha256"
6
6
"fmt"
7
-
"log"
8
7
"os"
9
8
"os/exec"
10
9
"regexp"
···
13
12
"github.com/dgraph-io/ristretto"
14
13
"github.com/go-git/go-git/v5"
15
14
"github.com/go-git/go-git/v5/plumbing"
16
-
"tangled.org/core/patchutil"
17
-
"tangled.org/core/types"
18
15
)
19
16
20
17
type MergeCheckCache struct {
···
35
32
mergeCheckCache = MergeCheckCache{cache}
36
33
}
37
34
38
-
func (m *MergeCheckCache) cacheKey(g *GitRepo, patch string, targetBranch string) string {
35
+
func (m *MergeCheckCache) cacheKey(g *GitRepo, patch []byte, targetBranch string) string {
39
36
sep := byte(':')
40
37
hash := sha256.Sum256(fmt.Append([]byte{}, g.path, sep, g.h.String(), sep, patch, sep, targetBranch))
41
38
return fmt.Sprintf("%x", hash)
···
52
49
}
53
50
}
54
51
55
-
func (m *MergeCheckCache) Set(g *GitRepo, patch string, targetBranch string, mergeCheck error) {
52
+
func (m *MergeCheckCache) Set(g *GitRepo, patch []byte, targetBranch string, mergeCheck error) {
56
53
key := m.cacheKey(g, patch, targetBranch)
57
54
val := m.cacheVal(mergeCheck)
58
55
m.cache.Set(key, val, 0)
59
56
}
60
57
61
-
func (m *MergeCheckCache) Get(g *GitRepo, patch string, targetBranch string) (error, bool) {
58
+
func (m *MergeCheckCache) Get(g *GitRepo, patch []byte, targetBranch string) (error, bool) {
62
59
key := m.cacheKey(g, patch, targetBranch)
63
60
if val, ok := m.cache.Get(key); ok {
64
61
if val == struct{}{} {
···
107
104
return fmt.Sprintf("merge failed: %s", e.Message)
108
105
}
109
106
110
-
func (g *GitRepo) createTempFileWithPatch(patchData string) (string, error) {
107
+
func (g *GitRepo) createTempFileWithPatch(patchData []byte) (string, error) {
111
108
tmpFile, err := os.CreateTemp("", "git-patch-*.patch")
112
109
if err != nil {
113
110
return "", fmt.Errorf("failed to create temporary patch file: %w", err)
114
111
}
115
112
116
-
if _, err := tmpFile.Write([]byte(patchData)); err != nil {
113
+
if _, err := tmpFile.Write(patchData); err != nil {
117
114
tmpFile.Close()
118
115
os.Remove(tmpFile.Name())
119
116
return "", fmt.Errorf("failed to write patch data to temporary file: %w", err)
···
165
162
return nil
166
163
}
167
164
168
-
func (g *GitRepo) applyPatch(patchData, patchFile string, opts MergeOptions) error {
165
+
func (g *GitRepo) applyPatch(tmpDir, patchFile string, opts MergeOptions) error {
169
166
var stderr bytes.Buffer
170
167
var cmd *exec.Cmd
171
168
172
169
// configure default git user before merge
173
-
exec.Command("git", "-C", g.path, "config", "user.name", opts.CommitterName).Run()
174
-
exec.Command("git", "-C", g.path, "config", "user.email", opts.CommitterEmail).Run()
175
-
exec.Command("git", "-C", g.path, "config", "advice.mergeConflict", "false").Run()
170
+
exec.Command("git", "-C", tmpDir, "config", "user.name", opts.CommitterName).Run()
171
+
exec.Command("git", "-C", tmpDir, "config", "user.email", opts.CommitterEmail).Run()
172
+
exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run()
176
173
177
174
// if patch is a format-patch, apply using 'git am'
178
175
if opts.FormatPatch {
179
-
return g.applyMailbox(patchData)
180
-
}
176
+
cmd = exec.Command("git", "-C", tmpDir, "am", patchFile)
177
+
} else {
178
+
// else, apply using 'git apply' and commit it manually
179
+
applyCmd := exec.Command("git", "-C", tmpDir, "apply", patchFile)
180
+
applyCmd.Stderr = &stderr
181
+
if err := applyCmd.Run(); err != nil {
182
+
return fmt.Errorf("patch application failed: %s", stderr.String())
183
+
}
181
184
182
-
// else, apply using 'git apply' and commit it manually
183
-
applyCmd := exec.Command("git", "-C", g.path, "apply", patchFile)
184
-
applyCmd.Stderr = &stderr
185
-
if err := applyCmd.Run(); err != nil {
186
-
return fmt.Errorf("patch application failed: %s", stderr.String())
187
-
}
185
+
stageCmd := exec.Command("git", "-C", tmpDir, "add", ".")
186
+
if err := stageCmd.Run(); err != nil {
187
+
return fmt.Errorf("failed to stage changes: %w", err)
188
+
}
188
189
189
-
stageCmd := exec.Command("git", "-C", g.path, "add", ".")
190
-
if err := stageCmd.Run(); err != nil {
191
-
return fmt.Errorf("failed to stage changes: %w", err)
192
-
}
190
+
commitArgs := []string{"-C", tmpDir, "commit"}
193
191
194
-
commitArgs := []string{"-C", g.path, "commit"}
192
+
// Set author if provided
193
+
authorName := opts.AuthorName
194
+
authorEmail := opts.AuthorEmail
195
195
196
-
// Set author if provided
197
-
authorName := opts.AuthorName
198
-
authorEmail := opts.AuthorEmail
196
+
if authorName != "" && authorEmail != "" {
197
+
commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail))
198
+
}
199
+
// else, will default to knot's global user.name & user.email configured via `KNOT_GIT_USER_*` env variables
199
200
200
-
if authorName != "" && authorEmail != "" {
201
-
commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail))
202
-
}
203
-
// else, will default to knot's global user.name & user.email configured via `KNOT_GIT_USER_*` env variables
201
+
commitArgs = append(commitArgs, "-m", opts.CommitMessage)
204
202
205
-
commitArgs = append(commitArgs, "-m", opts.CommitMessage)
203
+
if opts.CommitBody != "" {
204
+
commitArgs = append(commitArgs, "-m", opts.CommitBody)
205
+
}
206
206
207
-
if opts.CommitBody != "" {
208
-
commitArgs = append(commitArgs, "-m", opts.CommitBody)
207
+
cmd = exec.Command("git", commitArgs...)
209
208
}
210
-
211
-
cmd = exec.Command("git", commitArgs...)
212
209
213
210
cmd.Stderr = &stderr
214
211
···
219
216
return nil
220
217
}
221
218
222
-
func (g *GitRepo) applyMailbox(patchData string) error {
223
-
fps, err := patchutil.ExtractPatches(patchData)
224
-
if err != nil {
225
-
return fmt.Errorf("failed to extract patches: %w", err)
226
-
}
227
-
228
-
// apply each patch one by one
229
-
// update the newly created commit object to add the change-id header
230
-
total := len(fps)
231
-
for i, p := range fps {
232
-
newCommit, err := g.applySingleMailbox(p)
233
-
if err != nil {
234
-
return err
235
-
}
236
-
237
-
log.Printf("applying mailbox patch %d/%d: committed %s\n", i+1, total, newCommit.String())
238
-
}
239
-
240
-
return nil
241
-
}
242
-
243
-
func (g *GitRepo) applySingleMailbox(singlePatch types.FormatPatch) (plumbing.Hash, error) {
244
-
tmpPatch, err := g.createTempFileWithPatch(singlePatch.Raw)
245
-
if err != nil {
246
-
return plumbing.ZeroHash, fmt.Errorf("failed to create temporary patch file for singluar mailbox patch: %w", err)
247
-
}
248
-
249
-
var stderr bytes.Buffer
250
-
cmd := exec.Command("git", "-C", g.path, "am", tmpPatch)
251
-
cmd.Stderr = &stderr
252
-
253
-
head, err := g.r.Head()
254
-
if err != nil {
255
-
return plumbing.ZeroHash, err
256
-
}
257
-
log.Println("head before apply", head.Hash().String())
258
-
259
-
if err := cmd.Run(); err != nil {
260
-
return plumbing.ZeroHash, fmt.Errorf("patch application failed: %s", stderr.String())
261
-
}
262
-
263
-
if err := g.Refresh(); err != nil {
264
-
return plumbing.ZeroHash, fmt.Errorf("failed to refresh repository state: %w", err)
265
-
}
266
-
267
-
head, err = g.r.Head()
268
-
if err != nil {
269
-
return plumbing.ZeroHash, err
270
-
}
271
-
log.Println("head after apply", head.Hash().String())
272
-
273
-
newHash := head.Hash()
274
-
if changeId, err := singlePatch.ChangeId(); err != nil {
275
-
// no change ID
276
-
} else if updatedHash, err := g.setChangeId(head.Hash(), changeId); err != nil {
277
-
return plumbing.ZeroHash, err
278
-
} else {
279
-
newHash = updatedHash
280
-
}
281
-
282
-
return newHash, nil
283
-
}
284
-
285
-
func (g *GitRepo) setChangeId(hash plumbing.Hash, changeId string) (plumbing.Hash, error) {
286
-
log.Printf("updating change ID of %s to %s\n", hash.String(), changeId)
287
-
obj, err := g.r.CommitObject(hash)
288
-
if err != nil {
289
-
return plumbing.ZeroHash, fmt.Errorf("failed to get commit object for hash %s: %w", hash.String(), err)
290
-
}
291
-
292
-
// write the change-id header
293
-
obj.ExtraHeaders["change-id"] = []byte(changeId)
294
-
295
-
// create a new object
296
-
dest := g.r.Storer.NewEncodedObject()
297
-
if err := obj.Encode(dest); err != nil {
298
-
return plumbing.ZeroHash, fmt.Errorf("failed to create new object: %w", err)
299
-
}
300
-
301
-
// store the new object
302
-
newHash, err := g.r.Storer.SetEncodedObject(dest)
303
-
if err != nil {
304
-
return plumbing.ZeroHash, fmt.Errorf("failed to store new object: %w", err)
305
-
}
306
-
307
-
log.Printf("hash changed from %s to %s\n", obj.Hash.String(), newHash.String())
308
-
309
-
// find the branch that HEAD is pointing to
310
-
ref, err := g.r.Head()
311
-
if err != nil {
312
-
return plumbing.ZeroHash, fmt.Errorf("failed to fetch HEAD: %w", err)
313
-
}
314
-
315
-
// and update that branch to point to new commit
316
-
if ref.Name().IsBranch() {
317
-
err = g.r.Storer.SetReference(plumbing.NewHashReference(ref.Name(), newHash))
318
-
if err != nil {
319
-
return plumbing.ZeroHash, fmt.Errorf("failed to update HEAD: %w", err)
320
-
}
321
-
}
322
-
323
-
// new hash of commit
324
-
return newHash, nil
325
-
}
326
-
327
-
func (g *GitRepo) MergeCheck(patchData string, targetBranch string) error {
219
+
func (g *GitRepo) MergeCheck(patchData []byte, targetBranch string) error {
328
220
if val, ok := mergeCheckCache.Get(g, patchData, targetBranch); ok {
329
221
return val
330
222
}
···
352
244
return result
353
245
}
354
246
355
-
func (g *GitRepo) MergeWithOptions(patchData string, targetBranch string, opts MergeOptions) error {
247
+
func (g *GitRepo) MergeWithOptions(patchData []byte, targetBranch string, opts MergeOptions) error {
356
248
patchFile, err := g.createTempFileWithPatch(patchData)
357
249
if err != nil {
358
250
return &ErrMerge{
···
371
263
}
372
264
defer os.RemoveAll(tmpDir)
373
265
374
-
tmpRepo, err := PlainOpen(tmpDir)
375
-
if err != nil {
376
-
return err
377
-
}
378
-
379
-
if err := tmpRepo.applyPatch(patchData, patchFile, opts); err != nil {
266
+
if err := g.applyPatch(tmpDir, patchFile, opts); err != nil {
380
267
return err
381
268
}
382
269
-46
knotserver/internal.go
-46
knotserver/internal.go
···
13
13
securejoin "github.com/cyphar/filepath-securejoin"
14
14
"github.com/go-chi/chi/v5"
15
15
"github.com/go-chi/chi/v5/middleware"
16
-
"github.com/go-git/go-git/v5/plumbing"
17
16
"tangled.org/core/api/tangled"
18
17
"tangled.org/core/hook"
19
-
"tangled.org/core/idresolver"
20
18
"tangled.org/core/knotserver/config"
21
19
"tangled.org/core/knotserver/db"
22
20
"tangled.org/core/knotserver/git"
···
120
118
// non-fatal
121
119
}
122
120
123
-
if (line.NewSha.String() != line.OldSha.String()) && line.OldSha.IsZero() {
124
-
msg, err := h.replyCompare(line, gitUserDid, gitRelativeDir, repoName, r.Context())
125
-
if err != nil {
126
-
l.Error("failed to reply with compare link", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir)
127
-
// non-fatal
128
-
} else {
129
-
for msgLine := range msg {
130
-
resp.Messages = append(resp.Messages, msg[msgLine])
131
-
}
132
-
}
133
-
}
134
-
135
121
err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions)
136
122
if err != nil {
137
123
l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir)
···
140
126
}
141
127
142
128
writeJSON(w, resp)
143
-
}
144
-
145
-
func (h *InternalHandle) replyCompare(line git.PostReceiveLine, gitUserDid string, gitRelativeDir string, repoName string, ctx context.Context) ([]string, error) {
146
-
l := h.l.With("handler", "replyCompare")
147
-
userIdent, err := idresolver.DefaultResolver().ResolveIdent(ctx, gitUserDid)
148
-
user := gitUserDid
149
-
if err != nil {
150
-
l.Error("Failed to fetch user identity", "err", err)
151
-
// non-fatal
152
-
} else {
153
-
user = userIdent.Handle.String()
154
-
}
155
-
gr, err := git.PlainOpen(gitRelativeDir)
156
-
if err != nil {
157
-
l.Error("Failed to open git repository", "err", err)
158
-
return []string{}, err
159
-
}
160
-
defaultBranch, err := gr.FindMainBranch()
161
-
if err != nil {
162
-
l.Error("Failed to fetch default branch", "err", err)
163
-
return []string{}, err
164
-
}
165
-
if line.Ref == plumbing.NewBranchReferenceName(defaultBranch).String() {
166
-
return []string{}, nil
167
-
}
168
-
ZWS := "\u200B"
169
-
var msg []string
170
-
msg = append(msg, ZWS)
171
-
msg = append(msg, fmt.Sprintf("Create a PR pointing to %s", defaultBranch))
172
-
msg = append(msg, fmt.Sprintf("\t%s/%s/%s/compare/%s...%s", h.c.AppViewEndpoint, user, repoName, defaultBranch, strings.TrimPrefix(line.Ref, "refs/heads/")))
173
-
msg = append(msg, ZWS)
174
-
return msg, nil
175
129
}
176
130
177
131
func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error {
-87
knotserver/xrpc/delete_branch.go
-87
knotserver/xrpc/delete_branch.go
···
1
-
package xrpc
2
-
3
-
import (
4
-
"encoding/json"
5
-
"fmt"
6
-
"net/http"
7
-
8
-
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
-
"github.com/bluesky-social/indigo/atproto/syntax"
10
-
"github.com/bluesky-social/indigo/xrpc"
11
-
securejoin "github.com/cyphar/filepath-securejoin"
12
-
"tangled.org/core/api/tangled"
13
-
"tangled.org/core/knotserver/git"
14
-
"tangled.org/core/rbac"
15
-
16
-
xrpcerr "tangled.org/core/xrpc/errors"
17
-
)
18
-
19
-
func (x *Xrpc) DeleteBranch(w http.ResponseWriter, r *http.Request) {
20
-
l := x.Logger
21
-
fail := func(e xrpcerr.XrpcError) {
22
-
l.Error("failed", "kind", e.Tag, "error", e.Message)
23
-
writeError(w, e, http.StatusBadRequest)
24
-
}
25
-
26
-
actorDid, ok := r.Context().Value(ActorDid).(syntax.DID)
27
-
if !ok {
28
-
fail(xrpcerr.MissingActorDidError)
29
-
return
30
-
}
31
-
32
-
var data tangled.RepoDeleteBranch_Input
33
-
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
34
-
fail(xrpcerr.GenericError(err))
35
-
return
36
-
}
37
-
38
-
// unfortunately we have to resolve repo-at here
39
-
repoAt, err := syntax.ParseATURI(data.Repo)
40
-
if err != nil {
41
-
fail(xrpcerr.InvalidRepoError(data.Repo))
42
-
return
43
-
}
44
-
45
-
// resolve this aturi to extract the repo record
46
-
ident, err := x.Resolver.ResolveIdent(r.Context(), repoAt.Authority().String())
47
-
if err != nil || ident.Handle.IsInvalidHandle() {
48
-
fail(xrpcerr.GenericError(fmt.Errorf("failed to resolve handle: %w", err)))
49
-
return
50
-
}
51
-
52
-
xrpcc := xrpc.Client{Host: ident.PDSEndpoint()}
53
-
resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())
54
-
if err != nil {
55
-
fail(xrpcerr.GenericError(err))
56
-
return
57
-
}
58
-
59
-
repo := resp.Value.Val.(*tangled.Repo)
60
-
didPath, err := securejoin.SecureJoin(actorDid.String(), repo.Name)
61
-
if err != nil {
62
-
fail(xrpcerr.GenericError(err))
63
-
return
64
-
}
65
-
66
-
if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {
67
-
l.Error("insufficent permissions", "did", actorDid.String())
68
-
writeError(w, xrpcerr.AccessControlError(actorDid.String()), http.StatusUnauthorized)
69
-
return
70
-
}
71
-
72
-
path, _ := securejoin.SecureJoin(x.Config.Repo.ScanPath, didPath)
73
-
gr, err := git.PlainOpen(path)
74
-
if err != nil {
75
-
fail(xrpcerr.GenericError(err))
76
-
return
77
-
}
78
-
79
-
err = gr.DeleteBranch(data.Branch)
80
-
if err != nil {
81
-
l.Error("deleting branch", "error", err.Error(), "branch", data.Branch)
82
-
writeError(w, xrpcerr.GitError(err), http.StatusInternalServerError)
83
-
return
84
-
}
85
-
86
-
w.WriteHeader(http.StatusOK)
87
-
}
+1
-1
knotserver/xrpc/merge.go
+1
-1
knotserver/xrpc/merge.go
···
85
85
mo.CommitterEmail = x.Config.Git.UserEmail
86
86
mo.FormatPatch = patchutil.IsFormatPatch(data.Patch)
87
87
88
-
err = gr.MergeWithOptions(data.Patch, data.Branch, mo)
88
+
err = gr.MergeWithOptions([]byte(data.Patch), data.Branch, mo)
89
89
if err != nil {
90
90
var mergeErr *git.ErrMerge
91
91
if errors.As(err, &mergeErr) {
+1
-1
knotserver/xrpc/merge_check.go
+1
-1
knotserver/xrpc/merge_check.go
-1
knotserver/xrpc/xrpc.go
-1
knotserver/xrpc/xrpc.go
···
38
38
r.Use(x.ServiceAuth.VerifyServiceAuth)
39
39
40
40
r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch)
41
-
r.Post("/"+tangled.RepoDeleteBranchNSID, x.DeleteBranch)
42
41
r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo)
43
42
r.Post("/"+tangled.RepoDeleteNSID, x.DeleteRepo)
44
43
r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus)
-30
lexicons/repo/deleteBranch.json
-30
lexicons/repo/deleteBranch.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.tangled.repo.deleteBranch",
4
-
"defs": {
5
-
"main": {
6
-
"type": "procedure",
7
-
"description": "Delete a branch on this repository",
8
-
"input": {
9
-
"encoding": "application/json",
10
-
"schema": {
11
-
"type": "object",
12
-
"required": [
13
-
"repo",
14
-
"branch"
15
-
],
16
-
"properties": {
17
-
"repo": {
18
-
"type": "string",
19
-
"format": "at-uri"
20
-
},
21
-
"branch": {
22
-
"type": "string"
23
-
}
24
-
}
25
-
}
26
-
}
27
-
}
28
-
}
29
-
}
30
-
+11
-23
nix/gomod2nix.toml
+11
-23
nix/gomod2nix.toml
···
40
40
hash = "sha256-GWm5i1ukuBukV0GMF1rffpbOSSXZdfg6/0pABMiGzLQ="
41
41
replaced = "tangled.sh/oppi.li/go-gitdiff"
42
42
[mod."github.com/bluesky-social/indigo"]
43
-
version = "v0.0.0-20251003000214-3259b215110e"
44
-
hash = "sha256-qi/GrquJznbLnnHVpd7IqoryCESbi6xE4X1SiEM2qlo="
43
+
version = "v0.0.0-20250724221105-5827c8fb61bb"
44
+
hash = "sha256-uDYmzP4/mT7xP62LIL4QIOlkaKWS/IT1uho+udyVOAI="
45
45
[mod."github.com/bluesky-social/jetstream"]
46
46
version = "v0.0.0-20241210005130-ea96859b93d1"
47
47
hash = "sha256-AiapbrkjXboIKc5QNiWH0KyNs0zKnn6UlGwWFlkUfm0="
···
163
163
[mod."github.com/gogo/protobuf"]
164
164
version = "v1.3.2"
165
165
hash = "sha256-pogILFrrk+cAtb0ulqn9+gRZJ7sGnnLLdtqITvxvG6c="
166
-
[mod."github.com/goki/freetype"]
167
-
version = "v1.0.5"
168
-
hash = "sha256-8ILVMx5w1/nV88RZPoG45QJ0jH1YEPJGLpZQdBJFqIs="
169
166
[mod."github.com/golang-jwt/jwt/v5"]
170
167
version = "v5.2.3"
171
168
hash = "sha256-dY2avNPPS3xokn5E+VCLxXcQk7DsM7err2QGrG0nXKo="
···
410
407
[mod."github.com/spaolacci/murmur3"]
411
408
version = "v1.1.0"
412
409
hash = "sha256-RWD4PPrlAsZZ8Xy356MBxpj+/NZI7w2XOU14Ob7/Y9M="
413
-
[mod."github.com/srwiley/oksvg"]
414
-
version = "v0.0.0-20221011165216-be6e8873101c"
415
-
hash = "sha256-lZb6Y8HkrDpx9pxS+QQTcXI2MDSSv9pUyVTat59OrSk="
416
-
[mod."github.com/srwiley/rasterx"]
417
-
version = "v0.0.0-20220730225603-2ab79fcdd4ef"
418
-
hash = "sha256-/XmSE/J+f6FLWXGvljh6uBK71uoCAK3h82XQEQ1Ki68="
419
410
[mod."github.com/stretchr/testify"]
420
411
version = "v1.10.0"
421
412
hash = "sha256-fJ4gnPr0vnrOhjQYQwJ3ARDKPsOtA7d4olQmQWR+wpI="
···
441
432
version = "v0.1.15"
442
433
hash = "sha256-hb99exdkoY2Qv8WdDxhwgPXGbEYimUr6wFtPXEvcO9g="
443
434
[mod."github.com/yuin/goldmark"]
444
-
version = "v1.7.13"
445
-
hash = "sha256-vBCxZrPYPc8x/nvAAv3Au59dCCyfS80Vw3/a9EXK7TE="
435
+
version = "v1.7.12"
436
+
hash = "sha256-thLYBS4woL2X5qRdo7vP+xCvjlGRDU0jXtDCUt6vvWM="
446
437
[mod."github.com/yuin/goldmark-highlighting/v2"]
447
438
version = "v2.0.0-20230729083705-37449abec8cc"
448
439
hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg="
449
-
[mod."gitlab.com/staticnoise/goldmark-callout"]
450
-
version = "v0.0.0-20240609120641-6366b799e4ab"
451
-
hash = "sha256-CgqBIYAuSmL2hcFu5OW18nWWaSy3pp3CNp5jlWzBX44="
452
440
[mod."gitlab.com/yawning/secp256k1-voi"]
453
441
version = "v0.0.0-20230925100816-f2616030848b"
454
442
hash = "sha256-X8INg01LTg13iOuwPI3uOhPN7r01sPZtmtwJ2sudjCA="
···
491
479
[mod."golang.org/x/exp"]
492
480
version = "v0.0.0-20250620022241-b7579e27df2b"
493
481
hash = "sha256-IsDTeuWLj4UkPO4NhWTvFeZ22WNtlxjoWiyAJh6zdig="
494
-
[mod."golang.org/x/image"]
495
-
version = "v0.31.0"
496
-
hash = "sha256-ZFTlu9+4QToPPLA8C5UcG2eq/lQylq81RoG/WtYo9rg="
497
482
[mod."golang.org/x/net"]
498
483
version = "v0.42.0"
499
484
hash = "sha256-YxileisIIez+kcAI+21kY5yk0iRuEqti2YdmS8jvP2s="
500
485
[mod."golang.org/x/sync"]
501
-
version = "v0.17.0"
502
-
hash = "sha256-M85lz4hK3/fzmcUViAp/CowHSxnr3BHSO7pjHp1O6i0="
486
+
version = "v0.16.0"
487
+
hash = "sha256-sqKDRESeMzLe0jWGWltLZL/JIgrn0XaIeBWCzVN3Bks="
503
488
[mod."golang.org/x/sys"]
504
489
version = "v0.34.0"
505
490
hash = "sha256-5rZ7p8IaGli5X1sJbfIKOcOEwY4c0yQhinJPh2EtK50="
506
491
[mod."golang.org/x/text"]
507
-
version = "v0.29.0"
508
-
hash = "sha256-2cWBtJje+Yc+AnSgCANqBlIwnOMZEGkpQ2cFI45VfLI="
492
+
version = "v0.27.0"
493
+
hash = "sha256-VX0rOh6L3qIvquKSGjfZQFU8URNtGvkNvxE7OZtboW8="
509
494
[mod."golang.org/x/time"]
510
495
version = "v0.12.0"
511
496
hash = "sha256-Cp3oxrCMH2wyxjzr5SHVmyhgaoUuSl56Uy00Q7DYEpw="
···
542
527
[mod."lukechampine.com/blake3"]
543
528
version = "v1.4.1"
544
529
hash = "sha256-HaZGo9L44ptPsgxIhvKy3+0KZZm1+xt+cZC1rDQA9Yc="
530
+
[mod."tangled.org/anirudh.fi/atproto-oauth"]
531
+
version = "v0.0.0-20250724194903-28e660378cb1"
532
+
hash = "sha256-z7huwCTTHqLb1hxQW62lz9GQ3Orqt4URfeOVhQVd1f8="