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 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
··· 350 350 351 351 return mergeable 352 352 } 353 - 354 - type BranchDeleteStatus struct { 355 - Repo *Repo 356 - Branch string 357 - }
+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
··· 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
··· 114 114 policy.AllowNoAttrs().OnElements(mathElements...) 115 115 policy.AllowAttrs(mathAttrs...).OnElements(mathElements...) 116 116 117 - // goldmark-callout 118 - policy.AllowAttrs("data-callout").OnElements("details") 119 - 120 117 return policy 121 118 } 122 119
+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
··· 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/footer.html
··· 1 1 {{ define "layouts/fragments/footer" }} 2 - <div class="w-full p-8 bg-white dark:bg-gray-800"> 2 + <div class="w-full p-8"> 3 3 <div class="mx-auto px-4"> 4 4 <div class="flex flex-col text-gray-600 dark:text-gray-400 gap-8"> 5 5 <!-- Desktop layout: grid with 3 columns -->
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 19 19 }) 20 20 r.Get("/commit/{ref}", rp.RepoCommit) 21 21 r.Get("/branches", rp.RepoBranches) 22 - r.Delete("/branches", rp.DeleteBranch) 23 22 r.Route("/tags", func(r chi.Router) { 24 23 r.Get("/", rp.RepoTags) 25 24 r.Route("/{tag}", func(r chi.Router) {
+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
··· 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
··· 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
··· 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
··· 110 110 slices.Reverse(branches) 111 111 return branches, nil 112 112 } 113 - 114 - func (g *GitRepo) DeleteBranch(branch string) error { 115 - ref := plumbing.NewBranchReferenceName(branch) 116 - return g.r.Storer.RemoveReference(ref) 117 - }
-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
··· 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
··· 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
··· 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 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
··· 51 51 return 52 52 } 53 53 54 - err = gr.MergeCheck(data.Patch, data.Branch) 54 + err = gr.MergeCheck([]byte(data.Patch), data.Branch) 55 55 56 56 response := tangled.RepoMergeCheck_Output{ 57 57 Is_conflicted: false,
-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
··· 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 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="