forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

Compare changes

Choose any two refs to compare.

-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
··· 9 10 // TODO: this gathers heterogenous events from different sources and aggregates 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) { 13 var events []models.TimelineEvent 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) 29 if err != nil { 30 return nil, err 31 } 32 33 - stars, err := getTimelineStars(e, limit, loggedInUserDid, userIsFollowing) 34 if err != nil { 35 return nil, err 36 } 37 38 - follows, err := getTimelineFollows(e, limit, loggedInUserDid, userIsFollowing) 39 if err != nil { 40 return nil, err 41 } ··· 83 return isStarred, starCount 84 } 85 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...) 93 if err != nil { 94 return nil, err 95 } ··· 143 return events, nil 144 } 145 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...) 153 if err != nil { 154 return nil, err 155 } ··· 189 return events, nil 190 } 191 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...) 199 if err != nil { 200 return nil, err 201 }
··· 9 10 // TODO: this gathers heterogenous events from different sources and aggregates 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) ([]models.TimelineEvent, error) { 13 var events []models.TimelineEvent 14 15 + repos, err := getTimelineRepos(e, limit, loggedInUserDid) 16 if err != nil { 17 return nil, err 18 } 19 20 + stars, err := getTimelineStars(e, limit, loggedInUserDid) 21 if err != nil { 22 return nil, err 23 } 24 25 + follows, err := getTimelineFollows(e, limit, loggedInUserDid) 26 if err != nil { 27 return nil, err 28 } ··· 70 return isStarred, starCount 71 } 72 73 + func getTimelineRepos(e Execer, limit int, loggedInUserDid string) ([]models.TimelineEvent, error) { 74 + repos, err := GetRepos(e, limit) 75 if err != nil { 76 return nil, err 77 } ··· 125 return events, nil 126 } 127 128 + func getTimelineStars(e Execer, limit int, loggedInUserDid string) ([]models.TimelineEvent, error) { 129 + stars, err := GetStars(e, limit) 130 if err != nil { 131 return nil, err 132 } ··· 166 return events, nil 167 } 168 169 + func getTimelineFollows(e Execer, limit int, loggedInUserDid string) ([]models.TimelineEvent, error) { 170 + follows, err := GetFollows(e, limit) 171 if err != nil { 172 return nil, err 173 }
-5
appview/models/pull.go
··· 350 351 return mergeable 352 } 353 - 354 - type BranchDeleteStatus struct { 355 - Repo *Repo 356 - Branch string 357 - }
··· 350 351 return mergeable 352 }
+4 -4
appview/pages/funcmap.go
··· 265 return nil 266 }, 267 "i": func(name string, classes ...string) template.HTML { 268 - data, err := p.icon(name, classes) 269 if err != nil { 270 log.Printf("icon %s does not exist", name) 271 - data, _ = p.icon("airplay", classes) 272 } 273 return template.HTML(data) 274 }, 275 - "cssContentHash": p.CssContentHash, 276 "fileTree": filetree.FileTree, 277 "pathEscape": func(s string) string { 278 return url.PathEscape(s) ··· 325 return fmt.Sprintf("%s/%s/%s?%s", p.avatar.Host, signature, handle, sizeArg) 326 } 327 328 - func (p *Pages) icon(name string, classes []string) (template.HTML, error) { 329 iconPath := filepath.Join("static", "icons", name) 330 331 if filepath.Ext(name) == "" {
··· 265 return nil 266 }, 267 "i": func(name string, classes ...string) template.HTML { 268 + data, err := icon(name, classes) 269 if err != nil { 270 log.Printf("icon %s does not exist", name) 271 + data, _ = icon("airplay", classes) 272 } 273 return template.HTML(data) 274 }, 275 + "cssContentHash": CssContentHash, 276 "fileTree": filetree.FileTree, 277 "pathEscape": func(s string) string { 278 return url.PathEscape(s) ··· 325 return fmt.Sprintf("%s/%s/%s?%s", p.avatar.Host, signature, handle, sizeArg) 326 } 327 328 + func icon(name string, classes []string) (template.HTML, error) { 329 iconPath := filepath.Join("static", "icons", name) 330 331 if filepath.Ext(name) == "" {
+1 -6
appview/pages/markup/markdown.go
··· 5 "bytes" 6 "fmt" 7 "io" 8 - "io/fs" 9 "net/url" 10 "path" 11 "strings" ··· 21 "github.com/yuin/goldmark/renderer/html" 22 "github.com/yuin/goldmark/text" 23 "github.com/yuin/goldmark/util" 24 - callout "gitlab.com/staticnoise/goldmark-callout" 25 htmlparse "golang.org/x/net/html" 26 27 "tangled.org/core/api/tangled" ··· 47 IsDev bool 48 RendererType RendererType 49 Sanitizer Sanitizer 50 - Files fs.FS 51 } 52 53 func (rctx *RenderContext) RenderMarkdown(source string) string { ··· 65 extension.WithFootnoteIDPrefix([]byte("footnote")), 66 ), 67 treeblood.MathML(), 68 - callout.CalloutExtention, 69 ), 70 goldmark.WithParserOptions( 71 parser.WithAutoHeadingID(), ··· 144 func visitNode(ctx *RenderContext, node *htmlparse.Node) { 145 switch node.Type { 146 case htmlparse.ElementNode: 147 - switch node.Data { 148 - case "img", "source": 149 for i, attr := range node.Attr { 150 if attr.Key != "src" { 151 continue
··· 5 "bytes" 6 "fmt" 7 "io" 8 "net/url" 9 "path" 10 "strings" ··· 20 "github.com/yuin/goldmark/renderer/html" 21 "github.com/yuin/goldmark/text" 22 "github.com/yuin/goldmark/util" 23 htmlparse "golang.org/x/net/html" 24 25 "tangled.org/core/api/tangled" ··· 45 IsDev bool 46 RendererType RendererType 47 Sanitizer Sanitizer 48 } 49 50 func (rctx *RenderContext) RenderMarkdown(source string) string { ··· 62 extension.WithFootnoteIDPrefix([]byte("footnote")), 63 ), 64 treeblood.MathML(), 65 ), 66 goldmark.WithParserOptions( 67 parser.WithAutoHeadingID(), ··· 140 func visitNode(ctx *RenderContext, node *htmlparse.Node) { 141 switch node.Type { 142 case htmlparse.ElementNode: 143 + if node.Data == "img" || node.Data == "source" { 144 for i, attr := range node.Attr { 145 if attr.Key != "src" { 146 continue
-3
appview/pages/markup/sanitizer.go
··· 114 policy.AllowNoAttrs().OnElements(mathElements...) 115 policy.AllowAttrs(mathAttrs...).OnElements(mathElements...) 116 117 - // goldmark-callout 118 - policy.AllowAttrs("data-callout").OnElements("details") 119 - 120 return policy 121 } 122
··· 114 policy.AllowNoAttrs().OnElements(mathElements...) 115 policy.AllowAttrs(mathAttrs...).OnElements(mathElements...) 116 117 return policy 118 } 119
+19 -22
appview/pages/pages.go
··· 61 CamoUrl: config.Camo.Host, 62 CamoSecret: config.Camo.SharedSecret, 63 Sanitizer: markup.NewSanitizer(), 64 - Files: Files, 65 } 66 67 p := &Pages{ ··· 1129 } 1130 1131 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 1142 1143 OrderedReactionKinds []models.ReactionKind 1144 Reactions map[models.ReactionKind]models.ReactionDisplayData ··· 1234 } 1235 1236 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 1245 } 1246 1247 func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error { ··· 1478 return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static"))) 1479 } 1480 1481 - sub, err := fs.Sub(p.embedFS, "static") 1482 if err != nil { 1483 p.logger.Error("no static dir found? that's crazy", "err", err) 1484 panic(err) ··· 1501 }) 1502 } 1503 1504 - func (p *Pages) CssContentHash() string { 1505 - cssFile, err := p.embedFS.Open("static/tw.css") 1506 if err != nil { 1507 slog.Debug("Error opening CSS file", "err", err) 1508 return ""
··· 61 CamoUrl: config.Camo.Host, 62 CamoSecret: config.Camo.SharedSecret, 63 Sanitizer: markup.NewSanitizer(), 64 } 65 66 p := &Pages{ ··· 1128 } 1129 1130 type RepoSinglePullParams struct { 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 1140 1141 OrderedReactionKinds []models.ReactionKind 1142 Reactions map[models.ReactionKind]models.ReactionDisplayData ··· 1232 } 1233 1234 type PullActionsParams struct { 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 1242 } 1243 1244 func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error { ··· 1475 return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static"))) 1476 } 1477 1478 + sub, err := fs.Sub(Files, "static") 1479 if err != nil { 1480 p.logger.Error("no static dir found? that's crazy", "err", err) 1481 panic(err) ··· 1498 }) 1499 } 1500 1501 + func CssContentHash() string { 1502 + cssFile, err := Files.Open("static/tw.css") 1503 if err != nil { 1504 slog.Debug("Error opening CSS file", "err", err) 1505 return ""
+2 -2
appview/pages/templates/layouts/base.html
··· 26 </head> 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 {{ block "topbarLayout" . }} 29 - <header class="w-full col-span-full md:col-span-1 md:col-start-2" style="z-index: 20;"> 30 31 {{ if .LoggedInUser }} 32 <div id="upgrade-banner" ··· 58 {{ end }} 59 60 {{ block "footerLayout" . }} 61 - <footer class="mt-12"> 62 {{ template "layouts/fragments/footer" . }} 63 </footer> 64 {{ end }}
··· 26 </head> 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 {{ block "topbarLayout" . }} 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 31 {{ if .LoggedInUser }} 32 <div id="upgrade-banner" ··· 58 {{ end }} 59 60 {{ block "footerLayout" . }} 61 + <footer class="bg-white dark:bg-gray-800 mt-12"> 62 {{ template "layouts/fragments/footer" . }} 63 </footer> 64 {{ end }}
+1 -1
appview/pages/templates/layouts/fragments/footer.html
··· 1 {{ define "layouts/fragments/footer" }} 2 - <div class="w-full p-8 bg-white dark:bg-gray-800"> 3 <div class="mx-auto px-4"> 4 <div class="flex flex-col text-gray-600 dark:text-gray-400 gap-8"> 5 <!-- Desktop layout: grid with 3 columns -->
··· 1 {{ define "layouts/fragments/footer" }} 2 + <div class="w-full p-8"> 3 <div class="mx-auto px-4"> 4 <div class="flex flex-col text-gray-600 dark:text-gray-400 gap-8"> 5 <!-- Desktop layout: grid with 3 columns -->
+1 -1
appview/pages/templates/layouts/fragments/topbar.html
··· 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"> 3 <div class="flex justify-between p-0 items-center"> 4 <div id="left-items"> 5 <a href="/" hx-boost="true" class="text-2xl no-underline hover:no-underline flex items-center gap-2">
··· 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"> 3 <div class="flex justify-between p-0 items-center"> 4 <div id="left-items"> 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
··· 80 {{end}} 81 82 {{ define "topbarLayout" }} 83 - <header class="col-span-full" style="z-index: 20;"> 84 {{ template "layouts/fragments/topbar" . }} 85 </header> 86 {{ end }} 87 88 {{ define "mainLayout" }} 89 - <div class="px-1 flex-grow col-span-full flex flex-col gap-4"> 90 {{ block "contentLayout" . }} 91 {{ block "content" . }}{{ end }} 92 {{ end }} ··· 105 {{ end }} 106 107 {{ define "footerLayout" }} 108 - <footer class="col-span-full mt-12"> 109 {{ template "layouts/fragments/footer" . }} 110 </footer> 111 {{ end }}
··· 80 {{end}} 81 82 {{ define "topbarLayout" }} 83 + <header class="px-1 col-span-full" style="z-index: 20;"> 84 {{ template "layouts/fragments/topbar" . }} 85 </header> 86 {{ end }} 87 88 {{ define "mainLayout" }} 89 + <div class="px-1 col-span-full flex flex-col gap-4"> 90 {{ block "contentLayout" . }} 91 {{ block "content" . }}{{ end }} 92 {{ end }} ··· 105 {{ end }} 106 107 {{ define "footerLayout" }} 108 + <footer class="px-1 col-span-full mt-12"> 109 {{ template "layouts/fragments/footer" . }} 110 </footer> 111 {{ end }}
-11
appview/pages/templates/repo/pulls/fragments/pullActions.html
··· 33 <span>comment</span> 34 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 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 {{ if and $isPushAllowed $isOpen $isLastRound }} 48 {{ $disabled := "" }} 49 {{ if $isConflicted }}
··· 33 <span>comment</span> 34 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 35 </button> 36 {{ if and $isPushAllowed $isOpen $isLastRound }} 37 {{ $disabled := "" }} 38 {{ if $isConflicted }}
+14 -1
appview/pages/templates/repo/pulls/interdiff.html
··· 28 29 {{ end }} 30 31 {{ define "mainLayout" }} 32 - <div class="px-1 col-span-full flex-grow flex flex-col gap-4"> 33 {{ block "contentLayout" . }} 34 {{ block "content" . }}{{ end }} 35 {{ end }} ··· 46 {{ end }} 47 </div> 48 {{ end }} 49 50 {{ define "contentAfter" }} 51 {{ template "repo/fragments/interdiff" (list .RepoInfo.FullName .Interdiff .DiffOpts) }}
··· 28 29 {{ end }} 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 + 37 {{ define "mainLayout" }} 38 + <div class="px-1 col-span-full flex flex-col gap-4"> 39 {{ block "contentLayout" . }} 40 {{ block "content" . }}{{ end }} 41 {{ end }} ··· 52 {{ end }} 53 </div> 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 + 62 63 {{ define "contentAfter" }} 64 {{ template "repo/fragments/interdiff" (list .RepoInfo.FullName .Interdiff .DiffOpts) }}
+13 -1
appview/pages/templates/repo/pulls/patch.html
··· 34 </section> 35 {{ end }} 36 37 {{ define "mainLayout" }} 38 - <div class="px-1 col-span-full flex-grow flex flex-col gap-4"> 39 {{ block "contentLayout" . }} 40 {{ block "content" . }}{{ end }} 41 {{ end }} ··· 51 </div> 52 {{ end }} 53 </div> 54 {{ end }} 55 56 {{ define "contentAfter" }}
··· 34 </section> 35 {{ end }} 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 + 43 {{ define "mainLayout" }} 44 + <div class="px-1 col-span-full flex flex-col gap-4"> 45 {{ block "contentLayout" . }} 46 {{ block "content" . }}{{ end }} 47 {{ end }} ··· 57 </div> 58 {{ end }} 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> 66 {{ end }} 67 68 {{ define "contentAfter" }}
+1 -10
appview/pages/templates/repo/pulls/pull.html
··· 187 {{ end }} 188 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) }} 200 {{ else }} 201 <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 <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2">
··· 187 {{ end }} 188 189 {{ if $.LoggedInUser }} 190 + {{ template "repo/pulls/fragments/pullActions" (dict "LoggedInUser" $.LoggedInUser "Pull" $.Pull "RepoInfo" $.RepoInfo "RoundNumber" .RoundNumber "MergeCheck" $.MergeCheck "ResubmitCheck" $.ResubmitCheck "Stack" $.Stack) }} 191 {{ else }} 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"> 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
··· 98 } 99 100 mergeCheckResponse := s.mergeCheck(r, f, pull, stack) 101 - branchDeleteStatus := s.branchDeleteStatus(r, f, pull) 102 resubmitResult := pages.Unknown 103 if user.Did == pull.OwnerDid { 104 resubmitResult = s.resubmitCheck(r, f, pull, stack) 105 } 106 107 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, 116 }) 117 return 118 } ··· 155 } 156 157 mergeCheckResponse := s.mergeCheck(r, f, pull, stack) 158 - branchDeleteStatus := s.branchDeleteStatus(r, f, pull) 159 resubmitResult := pages.Unknown 160 if user != nil && user.Did == pull.OwnerDid { 161 resubmitResult = s.resubmitCheck(r, f, pull, stack) ··· 220 } 221 222 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, 232 233 OrderedReactionKinds: models.OrderedReactionKinds, 234 Reactions: reactionMap, ··· 305 return result 306 } 307 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 func (s *Pulls) resubmitCheck(r *http.Request, f *reporesolver.ResolvedRepo, pull *models.Pull, stack models.Stack) pages.ResubmitResult { 353 if pull.State == models.PullMerged || pull.State == models.PullDeleted || pull.PullSource == nil { 354 return pages.Unknown ··· 1201 Repo: string(f.RepoAt()), 1202 Branch: targetBranch, 1203 }, 1204 - Patch: patch, 1205 - Source: recordPullSource, 1206 - CreatedAt: time.Now().Format(time.RFC3339), 1207 }, 1208 }, 1209 }) ··· 1854 Repo: string(f.RepoAt()), 1855 Branch: pull.TargetBranch, 1856 }, 1857 - Patch: patch, // new patch 1858 - Source: recordPullSource, 1859 - CreatedAt: time.Now().Format(time.RFC3339), 1860 }, 1861 }, 1862 })
··· 98 } 99 100 mergeCheckResponse := s.mergeCheck(r, f, pull, stack) 101 resubmitResult := pages.Unknown 102 if user.Did == pull.OwnerDid { 103 resubmitResult = s.resubmitCheck(r, f, pull, stack) 104 } 105 106 s.pages.PullActionsFragment(w, pages.PullActionsParams{ 107 + LoggedInUser: user, 108 + RepoInfo: f.RepoInfo(user), 109 + Pull: pull, 110 + RoundNumber: roundNumber, 111 + MergeCheck: mergeCheckResponse, 112 + ResubmitCheck: resubmitResult, 113 + Stack: stack, 114 }) 115 return 116 } ··· 153 } 154 155 mergeCheckResponse := s.mergeCheck(r, f, pull, stack) 156 resubmitResult := pages.Unknown 157 if user != nil && user.Did == pull.OwnerDid { 158 resubmitResult = s.resubmitCheck(r, f, pull, stack) ··· 217 } 218 219 s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 220 + LoggedInUser: user, 221 + RepoInfo: repoInfo, 222 + Pull: pull, 223 + Stack: stack, 224 + AbandonedPulls: abandonedPulls, 225 + MergeCheck: mergeCheckResponse, 226 + ResubmitCheck: resubmitResult, 227 + Pipelines: m, 228 229 OrderedReactionKinds: models.OrderedReactionKinds, 230 Reactions: reactionMap, ··· 301 return result 302 } 303 304 func (s *Pulls) resubmitCheck(r *http.Request, f *reporesolver.ResolvedRepo, pull *models.Pull, stack models.Stack) pages.ResubmitResult { 305 if pull.State == models.PullMerged || pull.State == models.PullDeleted || pull.PullSource == nil { 306 return pages.Unknown ··· 1153 Repo: string(f.RepoAt()), 1154 Branch: targetBranch, 1155 }, 1156 + Patch: patch, 1157 + Source: recordPullSource, 1158 }, 1159 }, 1160 }) ··· 1805 Repo: string(f.RepoAt()), 1806 Branch: pull.TargetBranch, 1807 }, 1808 + Patch: patch, // new patch 1809 + Source: recordPullSource, 1810 }, 1811 }, 1812 })
+3 -3
appview/repo/opengraph.go
··· 30 contentCard, bottomArea := mainCard.Split(false, 75) 31 32 // Add padding to content 33 - contentCard.SetMargin(50) 34 35 // Split content horizontally: main content (80%) and avatar area (20%) 36 mainContent, avatarArea := contentCard.Split(true, 80) ··· 82 // Draw description (DrawText handles multi-line wrapping automatically) 83 descriptionCard.SetMargin(10) 84 description := repo.Description 85 - if len(description) > 70 { 86 - description = description[:70] + "…" 87 } 88 89 _, err = descriptionCard.DrawText(description, color.RGBA{88, 96, 105, 255}, 36, ogcard.Top, ogcard.Left)
··· 30 contentCard, bottomArea := mainCard.Split(false, 75) 31 32 // Add padding to content 33 + contentCard.SetMargin(30) 34 35 // Split content horizontally: main content (80%) and avatar area (20%) 36 mainContent, avatarArea := contentCard.Split(true, 80) ··· 82 // Draw description (DrawText handles multi-line wrapping automatically) 83 descriptionCard.SetMargin(10) 84 description := repo.Description 85 + if len(description) > 80 { 86 + description = description[:100] + "…" 87 } 88 89 _, err = descriptionCard.DrawText(description, color.RGBA{88, 96, 105, 255}, 36, ogcard.Top, ogcard.Left)
-47
appview/repo/repo.go
··· 628 }) 629 } 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 func (rp *Repo) RepoBlob(w http.ResponseWriter, r *http.Request) { 679 f, err := rp.repoResolver.Resolve(r) 680 if err != nil {
··· 628 }) 629 } 630 631 func (rp *Repo) RepoBlob(w http.ResponseWriter, r *http.Request) { 632 f, err := rp.repoResolver.Resolve(r) 633 if err != nil {
-1
appview/repo/router.go
··· 19 }) 20 r.Get("/commit/{ref}", rp.RepoCommit) 21 r.Get("/branches", rp.RepoBranches) 22 - r.Delete("/branches", rp.DeleteBranch) 23 r.Route("/tags", func(r chi.Router) { 24 r.Get("/", rp.RepoTags) 25 r.Route("/{tag}", func(r chi.Router) {
··· 19 }) 20 r.Get("/commit/{ref}", rp.RepoCommit) 21 r.Get("/branches", rp.RepoBranches) 22 r.Route("/tags", func(r chi.Router) { 23 r.Get("/", rp.RepoTags) 24 r.Route("/{tag}", func(r chi.Router) {
+2 -8
appview/state/state.go
··· 268 func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 269 user := s.oauth.GetUser(r) 270 271 - // TODO: set this flag based on the UI 272 - filtered := false 273 - 274 var userDid string 275 if user != nil { 276 userDid = user.Did 277 } 278 - timeline, err := db.MakeTimeline(s.db, 50, userDid, filtered) 279 if err != nil { 280 log.Println(err) 281 s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.") ··· 339 } 340 341 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) 346 if err != nil { 347 log.Println(err) 348 s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
··· 268 func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 269 user := s.oauth.GetUser(r) 270 271 var userDid string 272 if user != nil { 273 userDid = user.Did 274 } 275 + timeline, err := db.MakeTimeline(s.db, 50, userDid) 276 if err != nil { 277 log.Println(err) 278 s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.") ··· 336 } 337 338 func (s *State) Home(w http.ResponseWriter, r *http.Request) { 339 + timeline, err := db.MakeTimeline(s.db, 5, "") 340 if err != nil { 341 log.Println(err) 342 s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
-1
go.mod
··· 160 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 161 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 162 github.com/wyatt915/treeblood v0.1.15 // indirect 163 - gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab // indirect 164 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 165 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 166 go.opentelemetry.io/auto/sdk v1.1.0 // indirect
··· 160 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 161 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 162 github.com/wyatt915/treeblood v0.1.15 // indirect 163 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 164 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 165 go.opentelemetry.io/auto/sdk v1.1.0 // indirect
-2
go.sum
··· 444 github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 445 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 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 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 450 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 451 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
··· 444 github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 445 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 446 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= 447 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 448 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 449 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
+12 -71
input.css
··· 134 } 135 136 .prose hr { 137 - @apply my-2; 138 } 139 140 .prose li:has(input) { 141 - @apply list-none; 142 } 143 144 .prose ul:has(input) { 145 - @apply pl-2; 146 } 147 148 .prose .heading .anchor { 149 - @apply no-underline mx-2 opacity-0; 150 } 151 152 .prose .heading:hover .anchor { 153 - @apply opacity-70; 154 } 155 156 .prose .heading .anchor:hover { 157 - @apply opacity-70; 158 } 159 160 .prose a.footnote-backref { 161 - @apply no-underline; 162 } 163 164 .prose li { 165 - @apply my-0 py-0; 166 } 167 168 - .prose ul, 169 - .prose ol { 170 - @apply my-1 py-0; 171 } 172 173 .prose img { ··· 177 } 178 179 .prose input { 180 - @apply inline-block my-0 mb-1 mx-1; 181 } 182 183 .prose input[type="checkbox"] { 184 @apply disabled:accent-blue-500 checked:accent-blue-500 disabled:checked:accent-blue-500; 185 } 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 } 245 @layer utilities { 246 .error { ··· 287 } 288 /* LineHighlight */ 289 .chroma .hl { 290 - @apply bg-amber-400/30 dark:bg-amber-500/20; 291 } 292 293 /* LineNumbersTable */
··· 134 } 135 136 .prose hr { 137 + @apply my-2; 138 } 139 140 .prose li:has(input) { 141 + @apply list-none; 142 } 143 144 .prose ul:has(input) { 145 + @apply pl-2; 146 } 147 148 .prose .heading .anchor { 149 + @apply no-underline mx-2 opacity-0; 150 } 151 152 .prose .heading:hover .anchor { 153 + @apply opacity-70; 154 } 155 156 .prose .heading .anchor:hover { 157 + @apply opacity-70; 158 } 159 160 .prose a.footnote-backref { 161 + @apply no-underline; 162 } 163 164 .prose li { 165 + @apply my-0 py-0; 166 } 167 168 + .prose ul, .prose ol { 169 + @apply my-1 py-0; 170 } 171 172 .prose img { ··· 176 } 177 178 .prose input { 179 + @apply inline-block my-0 mb-1 mx-1; 180 } 181 182 .prose input[type="checkbox"] { 183 @apply disabled:accent-blue-500 checked:accent-blue-500 disabled:checked:accent-blue-500; 184 } 185 } 186 @layer utilities { 187 .error { ··· 228 } 229 /* LineHighlight */ 230 .chroma .hl { 231 + @apply bg-amber-400/30 dark:bg-amber-500/20; 232 } 233 234 /* LineNumbersTable */
-5
knotserver/git/branch.go
··· 110 slices.Reverse(branches) 111 return branches, nil 112 } 113 - 114 - func (g *GitRepo) DeleteBranch(branch string) error { 115 - ref := plumbing.NewBranchReferenceName(branch) 116 - return g.r.Storer.RemoveReference(ref) 117 - }
··· 110 slices.Reverse(branches) 111 return branches, nil 112 }
-11
knotserver/git/git.go
··· 71 return &g, nil 72 } 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 func (g *GitRepo) Commits(offset, limit int) ([]*object.Commit, error) { 86 commits := []*object.Commit{} 87
··· 71 return &g, nil 72 } 73 74 func (g *GitRepo) Commits(offset, limit int) ([]*object.Commit, error) { 75 commits := []*object.Commit{} 76
+37 -150
knotserver/git/merge.go
··· 4 "bytes" 5 "crypto/sha256" 6 "fmt" 7 - "log" 8 "os" 9 "os/exec" 10 "regexp" ··· 13 "github.com/dgraph-io/ristretto" 14 "github.com/go-git/go-git/v5" 15 "github.com/go-git/go-git/v5/plumbing" 16 - "tangled.org/core/patchutil" 17 - "tangled.org/core/types" 18 ) 19 20 type MergeCheckCache struct { ··· 35 mergeCheckCache = MergeCheckCache{cache} 36 } 37 38 - func (m *MergeCheckCache) cacheKey(g *GitRepo, patch string, targetBranch string) string { 39 sep := byte(':') 40 hash := sha256.Sum256(fmt.Append([]byte{}, g.path, sep, g.h.String(), sep, patch, sep, targetBranch)) 41 return fmt.Sprintf("%x", hash) ··· 52 } 53 } 54 55 - func (m *MergeCheckCache) Set(g *GitRepo, patch string, targetBranch string, mergeCheck error) { 56 key := m.cacheKey(g, patch, targetBranch) 57 val := m.cacheVal(mergeCheck) 58 m.cache.Set(key, val, 0) 59 } 60 61 - func (m *MergeCheckCache) Get(g *GitRepo, patch string, targetBranch string) (error, bool) { 62 key := m.cacheKey(g, patch, targetBranch) 63 if val, ok := m.cache.Get(key); ok { 64 if val == struct{}{} { ··· 107 return fmt.Sprintf("merge failed: %s", e.Message) 108 } 109 110 - func (g *GitRepo) createTempFileWithPatch(patchData string) (string, error) { 111 tmpFile, err := os.CreateTemp("", "git-patch-*.patch") 112 if err != nil { 113 return "", fmt.Errorf("failed to create temporary patch file: %w", err) 114 } 115 116 - if _, err := tmpFile.Write([]byte(patchData)); err != nil { 117 tmpFile.Close() 118 os.Remove(tmpFile.Name()) 119 return "", fmt.Errorf("failed to write patch data to temporary file: %w", err) ··· 165 return nil 166 } 167 168 - func (g *GitRepo) applyPatch(patchData, patchFile string, opts MergeOptions) error { 169 var stderr bytes.Buffer 170 var cmd *exec.Cmd 171 172 // 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() 176 177 // if patch is a format-patch, apply using 'git am' 178 if opts.FormatPatch { 179 - return g.applyMailbox(patchData) 180 - } 181 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 - } 188 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 - } 193 194 - commitArgs := []string{"-C", g.path, "commit"} 195 196 - // Set author if provided 197 - authorName := opts.AuthorName 198 - authorEmail := opts.AuthorEmail 199 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 204 205 - commitArgs = append(commitArgs, "-m", opts.CommitMessage) 206 207 - if opts.CommitBody != "" { 208 - commitArgs = append(commitArgs, "-m", opts.CommitBody) 209 } 210 - 211 - cmd = exec.Command("git", commitArgs...) 212 213 cmd.Stderr = &stderr 214 ··· 219 return nil 220 } 221 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 { 328 if val, ok := mergeCheckCache.Get(g, patchData, targetBranch); ok { 329 return val 330 } ··· 352 return result 353 } 354 355 - func (g *GitRepo) MergeWithOptions(patchData string, targetBranch string, opts MergeOptions) error { 356 patchFile, err := g.createTempFileWithPatch(patchData) 357 if err != nil { 358 return &ErrMerge{ ··· 371 } 372 defer os.RemoveAll(tmpDir) 373 374 - tmpRepo, err := PlainOpen(tmpDir) 375 - if err != nil { 376 - return err 377 - } 378 - 379 - if err := tmpRepo.applyPatch(patchData, patchFile, opts); err != nil { 380 return err 381 } 382
··· 4 "bytes" 5 "crypto/sha256" 6 "fmt" 7 "os" 8 "os/exec" 9 "regexp" ··· 12 "github.com/dgraph-io/ristretto" 13 "github.com/go-git/go-git/v5" 14 "github.com/go-git/go-git/v5/plumbing" 15 ) 16 17 type MergeCheckCache struct { ··· 32 mergeCheckCache = MergeCheckCache{cache} 33 } 34 35 + func (m *MergeCheckCache) cacheKey(g *GitRepo, patch []byte, targetBranch string) string { 36 sep := byte(':') 37 hash := sha256.Sum256(fmt.Append([]byte{}, g.path, sep, g.h.String(), sep, patch, sep, targetBranch)) 38 return fmt.Sprintf("%x", hash) ··· 49 } 50 } 51 52 + func (m *MergeCheckCache) Set(g *GitRepo, patch []byte, targetBranch string, mergeCheck error) { 53 key := m.cacheKey(g, patch, targetBranch) 54 val := m.cacheVal(mergeCheck) 55 m.cache.Set(key, val, 0) 56 } 57 58 + func (m *MergeCheckCache) Get(g *GitRepo, patch []byte, targetBranch string) (error, bool) { 59 key := m.cacheKey(g, patch, targetBranch) 60 if val, ok := m.cache.Get(key); ok { 61 if val == struct{}{} { ··· 104 return fmt.Sprintf("merge failed: %s", e.Message) 105 } 106 107 + func (g *GitRepo) createTempFileWithPatch(patchData []byte) (string, error) { 108 tmpFile, err := os.CreateTemp("", "git-patch-*.patch") 109 if err != nil { 110 return "", fmt.Errorf("failed to create temporary patch file: %w", err) 111 } 112 113 + if _, err := tmpFile.Write(patchData); err != nil { 114 tmpFile.Close() 115 os.Remove(tmpFile.Name()) 116 return "", fmt.Errorf("failed to write patch data to temporary file: %w", err) ··· 162 return nil 163 } 164 165 + func (g *GitRepo) applyPatch(tmpDir, patchFile string, opts MergeOptions) error { 166 var stderr bytes.Buffer 167 var cmd *exec.Cmd 168 169 // configure default git user before merge 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() 173 174 // if patch is a format-patch, apply using 'git am' 175 if opts.FormatPatch { 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 + } 184 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 + } 189 190 + commitArgs := []string{"-C", tmpDir, "commit"} 191 192 + // Set author if provided 193 + authorName := opts.AuthorName 194 + authorEmail := opts.AuthorEmail 195 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 200 201 + commitArgs = append(commitArgs, "-m", opts.CommitMessage) 202 203 + if opts.CommitBody != "" { 204 + commitArgs = append(commitArgs, "-m", opts.CommitBody) 205 + } 206 207 + cmd = exec.Command("git", commitArgs...) 208 } 209 210 cmd.Stderr = &stderr 211 ··· 216 return nil 217 } 218 219 + func (g *GitRepo) MergeCheck(patchData []byte, targetBranch string) error { 220 if val, ok := mergeCheckCache.Get(g, patchData, targetBranch); ok { 221 return val 222 } ··· 244 return result 245 } 246 247 + func (g *GitRepo) MergeWithOptions(patchData []byte, targetBranch string, opts MergeOptions) error { 248 patchFile, err := g.createTempFileWithPatch(patchData) 249 if err != nil { 250 return &ErrMerge{ ··· 263 } 264 defer os.RemoveAll(tmpDir) 265 266 + if err := g.applyPatch(tmpDir, patchFile, opts); err != nil { 267 return err 268 } 269
-46
knotserver/internal.go
··· 13 securejoin "github.com/cyphar/filepath-securejoin" 14 "github.com/go-chi/chi/v5" 15 "github.com/go-chi/chi/v5/middleware" 16 - "github.com/go-git/go-git/v5/plumbing" 17 "tangled.org/core/api/tangled" 18 "tangled.org/core/hook" 19 - "tangled.org/core/idresolver" 20 "tangled.org/core/knotserver/config" 21 "tangled.org/core/knotserver/db" 22 "tangled.org/core/knotserver/git" ··· 120 // non-fatal 121 } 122 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 err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions) 136 if err != nil { 137 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) ··· 140 } 141 142 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 } 176 177 func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error {
··· 13 securejoin "github.com/cyphar/filepath-securejoin" 14 "github.com/go-chi/chi/v5" 15 "github.com/go-chi/chi/v5/middleware" 16 "tangled.org/core/api/tangled" 17 "tangled.org/core/hook" 18 "tangled.org/core/knotserver/config" 19 "tangled.org/core/knotserver/db" 20 "tangled.org/core/knotserver/git" ··· 118 // non-fatal 119 } 120 121 err = h.triggerPipeline(&resp.Messages, line, gitUserDid, repoDid, repoName, pushOptions) 122 if err != nil { 123 l.Error("failed to trigger pipeline", "err", err, "line", line, "did", gitUserDid, "repo", gitRelativeDir) ··· 126 } 127 128 writeJSON(w, resp) 129 } 130 131 func (h *InternalHandle) insertRefUpdate(line git.PostReceiveLine, gitUserDid, repoDid, repoName string) error {
-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
··· 85 mo.CommitterEmail = x.Config.Git.UserEmail 86 mo.FormatPatch = patchutil.IsFormatPatch(data.Patch) 87 88 - err = gr.MergeWithOptions(data.Patch, data.Branch, mo) 89 if err != nil { 90 var mergeErr *git.ErrMerge 91 if errors.As(err, &mergeErr) {
··· 85 mo.CommitterEmail = x.Config.Git.UserEmail 86 mo.FormatPatch = patchutil.IsFormatPatch(data.Patch) 87 88 + err = gr.MergeWithOptions([]byte(data.Patch), data.Branch, mo) 89 if err != nil { 90 var mergeErr *git.ErrMerge 91 if errors.As(err, &mergeErr) {
+1 -1
knotserver/xrpc/merge_check.go
··· 51 return 52 } 53 54 - err = gr.MergeCheck(data.Patch, data.Branch) 55 56 response := tangled.RepoMergeCheck_Output{ 57 Is_conflicted: false,
··· 51 return 52 } 53 54 + err = gr.MergeCheck([]byte(data.Patch), data.Branch) 55 56 response := tangled.RepoMergeCheck_Output{ 57 Is_conflicted: false,
-1
knotserver/xrpc/xrpc.go
··· 38 r.Use(x.ServiceAuth.VerifyServiceAuth) 39 40 r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch) 41 - r.Post("/"+tangled.RepoDeleteBranchNSID, x.DeleteBranch) 42 r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo) 43 r.Post("/"+tangled.RepoDeleteNSID, x.DeleteRepo) 44 r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus)
··· 38 r.Use(x.ServiceAuth.VerifyServiceAuth) 39 40 r.Post("/"+tangled.RepoSetDefaultBranchNSID, x.SetDefaultBranch) 41 r.Post("/"+tangled.RepoCreateNSID, x.CreateRepo) 42 r.Post("/"+tangled.RepoDeleteNSID, x.DeleteRepo) 43 r.Post("/"+tangled.RepoForkStatusNSID, x.ForkStatus)
-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
··· 40 hash = "sha256-GWm5i1ukuBukV0GMF1rffpbOSSXZdfg6/0pABMiGzLQ=" 41 replaced = "tangled.sh/oppi.li/go-gitdiff" 42 [mod."github.com/bluesky-social/indigo"] 43 - version = "v0.0.0-20251003000214-3259b215110e" 44 - hash = "sha256-qi/GrquJznbLnnHVpd7IqoryCESbi6xE4X1SiEM2qlo=" 45 [mod."github.com/bluesky-social/jetstream"] 46 version = "v0.0.0-20241210005130-ea96859b93d1" 47 hash = "sha256-AiapbrkjXboIKc5QNiWH0KyNs0zKnn6UlGwWFlkUfm0=" ··· 163 [mod."github.com/gogo/protobuf"] 164 version = "v1.3.2" 165 hash = "sha256-pogILFrrk+cAtb0ulqn9+gRZJ7sGnnLLdtqITvxvG6c=" 166 - [mod."github.com/goki/freetype"] 167 - version = "v1.0.5" 168 - hash = "sha256-8ILVMx5w1/nV88RZPoG45QJ0jH1YEPJGLpZQdBJFqIs=" 169 [mod."github.com/golang-jwt/jwt/v5"] 170 version = "v5.2.3" 171 hash = "sha256-dY2avNPPS3xokn5E+VCLxXcQk7DsM7err2QGrG0nXKo=" ··· 410 [mod."github.com/spaolacci/murmur3"] 411 version = "v1.1.0" 412 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 [mod."github.com/stretchr/testify"] 420 version = "v1.10.0" 421 hash = "sha256-fJ4gnPr0vnrOhjQYQwJ3ARDKPsOtA7d4olQmQWR+wpI=" ··· 441 version = "v0.1.15" 442 hash = "sha256-hb99exdkoY2Qv8WdDxhwgPXGbEYimUr6wFtPXEvcO9g=" 443 [mod."github.com/yuin/goldmark"] 444 - version = "v1.7.13" 445 - hash = "sha256-vBCxZrPYPc8x/nvAAv3Au59dCCyfS80Vw3/a9EXK7TE=" 446 [mod."github.com/yuin/goldmark-highlighting/v2"] 447 version = "v2.0.0-20230729083705-37449abec8cc" 448 hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg=" 449 - [mod."gitlab.com/staticnoise/goldmark-callout"] 450 - version = "v0.0.0-20240609120641-6366b799e4ab" 451 - hash = "sha256-CgqBIYAuSmL2hcFu5OW18nWWaSy3pp3CNp5jlWzBX44=" 452 [mod."gitlab.com/yawning/secp256k1-voi"] 453 version = "v0.0.0-20230925100816-f2616030848b" 454 hash = "sha256-X8INg01LTg13iOuwPI3uOhPN7r01sPZtmtwJ2sudjCA=" ··· 491 [mod."golang.org/x/exp"] 492 version = "v0.0.0-20250620022241-b7579e27df2b" 493 hash = "sha256-IsDTeuWLj4UkPO4NhWTvFeZ22WNtlxjoWiyAJh6zdig=" 494 - [mod."golang.org/x/image"] 495 - version = "v0.31.0" 496 - hash = "sha256-ZFTlu9+4QToPPLA8C5UcG2eq/lQylq81RoG/WtYo9rg=" 497 [mod."golang.org/x/net"] 498 version = "v0.42.0" 499 hash = "sha256-YxileisIIez+kcAI+21kY5yk0iRuEqti2YdmS8jvP2s=" 500 [mod."golang.org/x/sync"] 501 - version = "v0.17.0" 502 - hash = "sha256-M85lz4hK3/fzmcUViAp/CowHSxnr3BHSO7pjHp1O6i0=" 503 [mod."golang.org/x/sys"] 504 version = "v0.34.0" 505 hash = "sha256-5rZ7p8IaGli5X1sJbfIKOcOEwY4c0yQhinJPh2EtK50=" 506 [mod."golang.org/x/text"] 507 - version = "v0.29.0" 508 - hash = "sha256-2cWBtJje+Yc+AnSgCANqBlIwnOMZEGkpQ2cFI45VfLI=" 509 [mod."golang.org/x/time"] 510 version = "v0.12.0" 511 hash = "sha256-Cp3oxrCMH2wyxjzr5SHVmyhgaoUuSl56Uy00Q7DYEpw=" ··· 542 [mod."lukechampine.com/blake3"] 543 version = "v1.4.1" 544 hash = "sha256-HaZGo9L44ptPsgxIhvKy3+0KZZm1+xt+cZC1rDQA9Yc="
··· 40 hash = "sha256-GWm5i1ukuBukV0GMF1rffpbOSSXZdfg6/0pABMiGzLQ=" 41 replaced = "tangled.sh/oppi.li/go-gitdiff" 42 [mod."github.com/bluesky-social/indigo"] 43 + version = "v0.0.0-20250724221105-5827c8fb61bb" 44 + hash = "sha256-uDYmzP4/mT7xP62LIL4QIOlkaKWS/IT1uho+udyVOAI=" 45 [mod."github.com/bluesky-social/jetstream"] 46 version = "v0.0.0-20241210005130-ea96859b93d1" 47 hash = "sha256-AiapbrkjXboIKc5QNiWH0KyNs0zKnn6UlGwWFlkUfm0=" ··· 163 [mod."github.com/gogo/protobuf"] 164 version = "v1.3.2" 165 hash = "sha256-pogILFrrk+cAtb0ulqn9+gRZJ7sGnnLLdtqITvxvG6c=" 166 [mod."github.com/golang-jwt/jwt/v5"] 167 version = "v5.2.3" 168 hash = "sha256-dY2avNPPS3xokn5E+VCLxXcQk7DsM7err2QGrG0nXKo=" ··· 407 [mod."github.com/spaolacci/murmur3"] 408 version = "v1.1.0" 409 hash = "sha256-RWD4PPrlAsZZ8Xy356MBxpj+/NZI7w2XOU14Ob7/Y9M=" 410 [mod."github.com/stretchr/testify"] 411 version = "v1.10.0" 412 hash = "sha256-fJ4gnPr0vnrOhjQYQwJ3ARDKPsOtA7d4olQmQWR+wpI=" ··· 432 version = "v0.1.15" 433 hash = "sha256-hb99exdkoY2Qv8WdDxhwgPXGbEYimUr6wFtPXEvcO9g=" 434 [mod."github.com/yuin/goldmark"] 435 + version = "v1.7.12" 436 + hash = "sha256-thLYBS4woL2X5qRdo7vP+xCvjlGRDU0jXtDCUt6vvWM=" 437 [mod."github.com/yuin/goldmark-highlighting/v2"] 438 version = "v2.0.0-20230729083705-37449abec8cc" 439 hash = "sha256-HpiwU7jIeDUAg2zOpTIiviQir8dpRPuXYh2nqFFccpg=" 440 [mod."gitlab.com/yawning/secp256k1-voi"] 441 version = "v0.0.0-20230925100816-f2616030848b" 442 hash = "sha256-X8INg01LTg13iOuwPI3uOhPN7r01sPZtmtwJ2sudjCA=" ··· 479 [mod."golang.org/x/exp"] 480 version = "v0.0.0-20250620022241-b7579e27df2b" 481 hash = "sha256-IsDTeuWLj4UkPO4NhWTvFeZ22WNtlxjoWiyAJh6zdig=" 482 [mod."golang.org/x/net"] 483 version = "v0.42.0" 484 hash = "sha256-YxileisIIez+kcAI+21kY5yk0iRuEqti2YdmS8jvP2s=" 485 [mod."golang.org/x/sync"] 486 + version = "v0.16.0" 487 + hash = "sha256-sqKDRESeMzLe0jWGWltLZL/JIgrn0XaIeBWCzVN3Bks=" 488 [mod."golang.org/x/sys"] 489 version = "v0.34.0" 490 hash = "sha256-5rZ7p8IaGli5X1sJbfIKOcOEwY4c0yQhinJPh2EtK50=" 491 [mod."golang.org/x/text"] 492 + version = "v0.27.0" 493 + hash = "sha256-VX0rOh6L3qIvquKSGjfZQFU8URNtGvkNvxE7OZtboW8=" 494 [mod."golang.org/x/time"] 495 version = "v0.12.0" 496 hash = "sha256-Cp3oxrCMH2wyxjzr5SHVmyhgaoUuSl56Uy00Q7DYEpw=" ··· 527 [mod."lukechampine.com/blake3"] 528 version = "v1.4.1" 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="