forked from tangled.org/core
Monorepo for Tangled

Compare changes

Choose any two refs to compare.

+2
appview/db/follow.go
··· 167 if err != nil { 168 return nil, err 169 } 170 for rows.Next() { 171 var follow models.Follow 172 var followedAt string
··· 167 if err != nil { 168 return nil, err 169 } 170 + defer rows.Close() 171 + 172 for rows.Next() { 173 var follow models.Follow 174 var followedAt string
+1
appview/db/issues.go
··· 452 if err != nil { 453 return nil, err 454 } 455 456 for rows.Next() { 457 var comment models.IssueComment
··· 452 if err != nil { 453 return nil, err 454 } 455 + defer rows.Close() 456 457 for rows.Next() { 458 var comment models.IssueComment
+1 -1
appview/db/language.go
··· 28 whereClause, 29 ) 30 rows, err := e.Query(query, args...) 31 - 32 if err != nil { 33 return nil, fmt.Errorf("failed to execute query: %w ", err) 34 } 35 36 var langs []models.RepoLanguage 37 for rows.Next() {
··· 28 whereClause, 29 ) 30 rows, err := e.Query(query, args...) 31 if err != nil { 32 return nil, fmt.Errorf("failed to execute query: %w ", err) 33 } 34 + defer rows.Close() 35 36 var langs []models.RepoLanguage 37 for rows.Next() {
+5
appview/db/profile.go
··· 230 if err != nil { 231 return nil, err 232 } 233 234 profileMap := make(map[string]*models.Profile) 235 for rows.Next() { ··· 270 if err != nil { 271 return nil, err 272 } 273 idxs := make(map[string]int) 274 for did := range profileMap { 275 idxs[did] = 0 ··· 290 if err != nil { 291 return nil, err 292 } 293 idxs = make(map[string]int) 294 for did := range profileMap { 295 idxs[did] = 0
··· 230 if err != nil { 231 return nil, err 232 } 233 + defer rows.Close() 234 235 profileMap := make(map[string]*models.Profile) 236 for rows.Next() { ··· 271 if err != nil { 272 return nil, err 273 } 274 + defer rows.Close() 275 + 276 idxs := make(map[string]int) 277 for did := range profileMap { 278 idxs[did] = 0 ··· 293 if err != nil { 294 return nil, err 295 } 296 + defer rows.Close() 297 + 298 idxs = make(map[string]int) 299 for did := range profileMap { 300 idxs[did] = 0
+1
appview/db/registration.go
··· 38 if err != nil { 39 return nil, err 40 } 41 42 for rows.Next() { 43 var createdAt string
··· 38 if err != nil { 39 return nil, err 40 } 41 + defer rows.Close() 42 43 for rows.Next() { 44 var createdAt string
+11 -1
appview/db/repos.go
··· 56 limitClause, 57 ) 58 rows, err := e.Query(repoQuery, args...) 59 - 60 if err != nil { 61 return nil, fmt.Errorf("failed to execute repo query: %w ", err) 62 } 63 64 for rows.Next() { 65 var repo models.Repo ··· 128 if err != nil { 129 return nil, fmt.Errorf("failed to execute labels query: %w ", err) 130 } 131 for rows.Next() { 132 var repoat, labelat string 133 if err := rows.Scan(&repoat, &labelat); err != nil { ··· 165 if err != nil { 166 return nil, fmt.Errorf("failed to execute lang query: %w ", err) 167 } 168 for rows.Next() { 169 var repoat, lang string 170 if err := rows.Scan(&repoat, &lang); err != nil { ··· 191 if err != nil { 192 return nil, fmt.Errorf("failed to execute star-count query: %w ", err) 193 } 194 for rows.Next() { 195 var repoat string 196 var count int ··· 220 if err != nil { 221 return nil, fmt.Errorf("failed to execute issue-count query: %w ", err) 222 } 223 for rows.Next() { 224 var repoat string 225 var open, closed int ··· 261 if err != nil { 262 return nil, fmt.Errorf("failed to execute pulls-count query: %w ", err) 263 } 264 for rows.Next() { 265 var repoat string 266 var open, merged, closed, deleted int
··· 56 limitClause, 57 ) 58 rows, err := e.Query(repoQuery, args...) 59 if err != nil { 60 return nil, fmt.Errorf("failed to execute repo query: %w ", err) 61 } 62 + defer rows.Close() 63 64 for rows.Next() { 65 var repo models.Repo ··· 128 if err != nil { 129 return nil, fmt.Errorf("failed to execute labels query: %w ", err) 130 } 131 + defer rows.Close() 132 + 133 for rows.Next() { 134 var repoat, labelat string 135 if err := rows.Scan(&repoat, &labelat); err != nil { ··· 167 if err != nil { 168 return nil, fmt.Errorf("failed to execute lang query: %w ", err) 169 } 170 + defer rows.Close() 171 + 172 for rows.Next() { 173 var repoat, lang string 174 if err := rows.Scan(&repoat, &lang); err != nil { ··· 195 if err != nil { 196 return nil, fmt.Errorf("failed to execute star-count query: %w ", err) 197 } 198 + defer rows.Close() 199 + 200 for rows.Next() { 201 var repoat string 202 var count int ··· 226 if err != nil { 227 return nil, fmt.Errorf("failed to execute issue-count query: %w ", err) 228 } 229 + defer rows.Close() 230 + 231 for rows.Next() { 232 var repoat string 233 var open, closed int ··· 269 if err != nil { 270 return nil, fmt.Errorf("failed to execute pulls-count query: %w ", err) 271 } 272 + defer rows.Close() 273 + 274 for rows.Next() { 275 var repoat string 276 var open, merged, closed, deleted int
+1
appview/db/star.go
··· 165 if err != nil { 166 return nil, err 167 } 168 169 starMap := make(map[string][]models.Star) 170 for rows.Next() {
··· 165 if err != nil { 166 return nil, err 167 } 168 + defer rows.Close() 169 170 starMap := make(map[string][]models.Star) 171 for rows.Next() {
-1
appview/notify/merged_notifier.go
··· 39 v.Call(in) 40 }(n) 41 } 42 - wg.Wait() 43 } 44 45 func (m *mergedNotifier) NewRepo(ctx context.Context, repo *models.Repo) {
··· 39 v.Call(in) 40 }(n) 41 } 42 } 43 44 func (m *mergedNotifier) NewRepo(ctx context.Context, repo *models.Repo) {
+6 -1
appview/pages/funcmap.go
··· 25 "github.com/dustin/go-humanize" 26 "github.com/go-enry/go-enry/v2" 27 "github.com/yuin/goldmark" 28 "tangled.org/core/appview/filetree" 29 "tangled.org/core/appview/models" 30 "tangled.org/core/appview/pages/markup" ··· 261 }, 262 "description": func(text string) template.HTML { 263 p.rctx.RendererType = markup.RendererTypeDefault 264 - htmlString := p.rctx.RenderMarkdownWith(text, goldmark.New()) 265 sanitized := p.rctx.SanitizeDescription(htmlString) 266 return template.HTML(sanitized) 267 },
··· 25 "github.com/dustin/go-humanize" 26 "github.com/go-enry/go-enry/v2" 27 "github.com/yuin/goldmark" 28 + emoji "github.com/yuin/goldmark-emoji" 29 "tangled.org/core/appview/filetree" 30 "tangled.org/core/appview/models" 31 "tangled.org/core/appview/pages/markup" ··· 262 }, 263 "description": func(text string) template.HTML { 264 p.rctx.RendererType = markup.RendererTypeDefault 265 + htmlString := p.rctx.RenderMarkdownWith(text, goldmark.New( 266 + goldmark.WithExtensions( 267 + emoji.Emoji, 268 + ), 269 + )) 270 sanitized := p.rctx.SanitizeDescription(htmlString) 271 return template.HTML(sanitized) 272 },
+2
appview/pages/markup/markdown.go
··· 13 chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 14 "github.com/alecthomas/chroma/v2/styles" 15 "github.com/yuin/goldmark" 16 highlighting "github.com/yuin/goldmark-highlighting/v2" 17 "github.com/yuin/goldmark/ast" 18 "github.com/yuin/goldmark/extension" ··· 66 ), 67 callout.CalloutExtention, 68 textension.AtExt, 69 ), 70 goldmark.WithParserOptions( 71 parser.WithAutoHeadingID(),
··· 13 chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 14 "github.com/alecthomas/chroma/v2/styles" 15 "github.com/yuin/goldmark" 16 + "github.com/yuin/goldmark-emoji" 17 highlighting "github.com/yuin/goldmark-highlighting/v2" 18 "github.com/yuin/goldmark/ast" 19 "github.com/yuin/goldmark/extension" ··· 67 ), 68 callout.CalloutExtention, 69 textension.AtExt, 70 + emoji.Emoji, 71 ), 72 goldmark.WithParserOptions( 73 parser.WithAutoHeadingID(),
+1 -1
appview/pages/pages.go
··· 640 } 641 642 func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 643 - return p.executePlain("fragments/starBtn", w, params) 644 } 645 646 type RepoIndexParams struct {
··· 640 } 641 642 func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 643 + return p.executePlain("fragments/starBtn-oob", w, params) 644 } 645 646 type RepoIndexParams struct {
+5
appview/pages/templates/fragments/starBtn-oob.html
···
··· 1 + {{ define "fragments/starBtn-oob" }} 2 + <div hx-swap-oob='outerHTML:#starBtn[data-star-subject-at="{{ .SubjectAt }}"]'> 3 + {{ template "fragments/starBtn" . }} 4 + </div> 5 + {{ end }}
+1 -3
appview/pages/templates/fragments/starBtn.html
··· 1 {{ define "fragments/starBtn" }} 2 <button 3 id="starBtn" 4 class="btn disabled:opacity-50 disabled:cursor-not-allowed flex gap-2 items-center group" ··· 10 {{ end }} 11 12 hx-trigger="click" 13 - hx-target="this" 14 - hx-swap="outerHTML" 15 - hx-swap-oob='outerHTML:#starBtn[data-star-subject-at="{{ .SubjectAt }}"]' 16 hx-disabled-elt="#starBtn" 17 > 18 {{ if .IsStarred }}
··· 1 {{ define "fragments/starBtn" }} 2 + {{/* NOTE: this fragment is always replaced with hx-swap-oob */}} 3 <button 4 id="starBtn" 5 class="btn disabled:opacity-50 disabled:cursor-not-allowed flex gap-2 items-center group" ··· 11 {{ end }} 12 13 hx-trigger="click" 14 hx-disabled-elt="#starBtn" 15 > 16 {{ if .IsStarred }}
+1 -1
appview/pages/templates/knots/index.html
··· 105 {{ define "docsButton" }} 106 <a 107 class="btn flex items-center gap-2" 108 - href="https://tangled.org/@tangled.org/core/blob/master/docs/spindle/hosting.md"> 109 {{ i "book" "size-4" }} 110 docs 111 </a>
··· 105 {{ define "docsButton" }} 106 <a 107 class="btn flex items-center gap-2" 108 + href="https://tangled.org/@tangled.org/core/blob/master/docs/knot-hosting.md"> 109 {{ i "book" "size-4" }} 110 docs 111 </a>
+6 -6
appview/pages/templates/repo/fragments/backlinks.html
··· 14 <div class="flex gap-2 items-center"> 15 {{ if .State.IsClosed }} 16 <span class="text-gray-500 dark:text-gray-400"> 17 - {{ i "ban" "w-4 h-4" }} 18 </span> 19 {{ else if eq .Kind.String "issues" }} 20 <span class="text-green-600 dark:text-green-500"> 21 - {{ i "circle-dot" "w-4 h-4" }} 22 </span> 23 {{ else if .State.IsOpen }} 24 <span class="text-green-600 dark:text-green-500"> 25 - {{ i "git-pull-request" "w-4 h-4" }} 26 </span> 27 {{ else if .State.IsMerged }} 28 <span class="text-purple-600 dark:text-purple-500"> 29 - {{ i "git-merge" "w-4 h-4" }} 30 </span> 31 {{ else }} 32 <span class="text-gray-600 dark:text-gray-300"> 33 - {{ i "git-pull-request-closed" "w-4 h-4" }} 34 </span> 35 {{ end }} 36 - <a href="{{ . }}"><span class="text-gray-500 dark:text-gray-400">#{{ .SubjectId }}</span> {{ .Title }}</a> 37 </div> 38 {{ if not (eq $.RepoInfo.FullName $repoUrl) }} 39 <div>
··· 14 <div class="flex gap-2 items-center"> 15 {{ if .State.IsClosed }} 16 <span class="text-gray-500 dark:text-gray-400"> 17 + {{ i "ban" "size-3" }} 18 </span> 19 {{ else if eq .Kind.String "issues" }} 20 <span class="text-green-600 dark:text-green-500"> 21 + {{ i "circle-dot" "size-3" }} 22 </span> 23 {{ else if .State.IsOpen }} 24 <span class="text-green-600 dark:text-green-500"> 25 + {{ i "git-pull-request" "size-3" }} 26 </span> 27 {{ else if .State.IsMerged }} 28 <span class="text-purple-600 dark:text-purple-500"> 29 + {{ i "git-merge" "size-3" }} 30 </span> 31 {{ else }} 32 <span class="text-gray-600 dark:text-gray-300"> 33 + {{ i "git-pull-request-closed" "size-3" }} 34 </span> 35 {{ end }} 36 + <a href="{{ . }}" class="line-clamp-1 text-sm"><span class="text-gray-500 dark:text-gray-400">#{{ .SubjectId }}</span> {{ .Title }}</a> 37 </div> 38 {{ if not (eq $.RepoInfo.FullName $repoUrl) }} 39 <div>
+1 -1
appview/pages/templates/strings/string.html
··· 17 <span class="select-none">/</span> 18 <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 19 </div> 20 - <div class="flex gap-2 text-base"> 21 {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 22 <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 23 hx-boost="true"
··· 17 <span class="select-none">/</span> 18 <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 19 </div> 20 + <div class="flex gap-2 items-stretch text-base"> 21 {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 22 <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 23 hx-boost="true"
+8
appview/pulls/pulls.go
··· 1366 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1367 return 1368 } 1369 } 1370 1371 if err = tx.Commit(); err != nil { 1372 log.Println("failed to create pull request", err) 1373 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1374 return 1375 } 1376 1377 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, repo)
··· 1366 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1367 return 1368 } 1369 + 1370 } 1371 1372 if err = tx.Commit(); err != nil { 1373 log.Println("failed to create pull request", err) 1374 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1375 return 1376 + } 1377 + 1378 + // notify about each pull 1379 + // 1380 + // this is performed after tx.Commit, because it could result in a locked DB otherwise 1381 + for _, p := range stack { 1382 + s.notifier.NewPull(r.Context(), p) 1383 } 1384 1385 ownerSlashRepo := reporesolver.GetBaseRepoPath(r, repo)
+17
appview/state/git_http.go
··· 25 26 } 27 28 func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 29 user, ok := r.Context().Value("resolvedId").(identity.Identity) 30 if !ok {
··· 25 26 } 27 28 + func (s *State) UploadArchive(w http.ResponseWriter, r *http.Request) { 29 + user, ok := r.Context().Value("resolvedId").(identity.Identity) 30 + if !ok { 31 + http.Error(w, "failed to resolve user", http.StatusInternalServerError) 32 + return 33 + } 34 + repo := r.Context().Value("repo").(*models.Repo) 35 + 36 + scheme := "https" 37 + if s.config.Core.Dev { 38 + scheme = "http" 39 + } 40 + 41 + targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-archive?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 42 + s.proxyRequest(w, r, targetURL) 43 + } 44 + 45 func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 46 user, ok := r.Context().Value("resolvedId").(identity.Identity) 47 if !ok {
+1
appview/state/router.go
··· 101 102 // These routes get proxied to the knot 103 r.Get("/info/refs", s.InfoRefs) 104 r.Post("/git-upload-pack", s.UploadPack) 105 r.Post("/git-receive-pack", s.ReceivePack) 106
··· 101 102 // These routes get proxied to the knot 103 r.Get("/info/refs", s.InfoRefs) 104 + r.Post("/git-upload-archive", s.UploadArchive) 105 r.Post("/git-upload-pack", s.UploadPack) 106 r.Post("/git-receive-pack", s.ReceivePack) 107
+1
go.mod
··· 190 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 191 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 192 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 193 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 194 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 195 go.etcd.io/bbolt v1.4.0 // indirect
··· 190 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 191 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 192 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 193 + github.com/yuin/goldmark-emoji v1.0.6 // indirect 194 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 195 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 196 go.etcd.io/bbolt v1.4.0 // indirect
+2
go.sum
··· 505 github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 506 github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= 507 github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 508 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 509 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= 510 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab h1:gK9tS6QJw5F0SIhYJnGG2P83kuabOdmWBbSmZhJkz2A=
··· 505 github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 506 github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= 507 github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 508 + github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= 509 + github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= 510 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 511 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= 512 gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab h1:gK9tS6QJw5F0SIhYJnGG2P83kuabOdmWBbSmZhJkz2A=
+4 -4
hook/hook.go
··· 48 }, 49 Commands: []*cli.Command{ 50 { 51 - Name: "post-recieve", 52 - Usage: "sends a post-recieve hook to the knot (waits for stdin)", 53 - Action: postRecieve, 54 }, 55 }, 56 } 57 } 58 59 - func postRecieve(ctx context.Context, cmd *cli.Command) error { 60 gitDir := cmd.String("git-dir") 61 userDid := cmd.String("user-did") 62 userHandle := cmd.String("user-handle")
··· 48 }, 49 Commands: []*cli.Command{ 50 { 51 + Name: "post-receive", 52 + Usage: "sends a post-receive hook to the knot (waits for stdin)", 53 + Action: postReceive, 54 }, 55 }, 56 } 57 } 58 59 + func postReceive(ctx context.Context, cmd *cli.Command) error { 60 gitDir := cmd.String("git-dir") 61 userDid := cmd.String("user-did") 62 userHandle := cmd.String("user-handle")
+1 -1
hook/setup.go
··· 138 option_var="GIT_PUSH_OPTION_$i" 139 push_options+=(-push-option "${!option_var}") 140 done 141 - %s hook -git-dir "$GIT_DIR" -user-did "$GIT_USER_DID" -user-handle "$GIT_USER_HANDLE" -internal-api "%s" "${push_options[@]}" post-recieve 142 `, executablePath, config.internalApi) 143 144 return os.WriteFile(hookPath, []byte(hookContent), 0755)
··· 138 option_var="GIT_PUSH_OPTION_$i" 139 push_options+=(-push-option "${!option_var}") 140 done 141 + %s hook -git-dir "$GIT_DIR" -user-did "$GIT_USER_DID" -user-handle "$GIT_USER_HANDLE" -internal-api "%s" "${push_options[@]}" post-receive 142 `, executablePath, config.internalApi) 143 144 return os.WriteFile(hookPath, []byte(hookContent), 0755)
+24 -7
knotserver/db/db.go
··· 1 package db 2 3 import ( 4 "database/sql" 5 "strings" 6 7 _ "github.com/mattn/go-sqlite3" 8 ) 9 10 type DB struct { 11 - db *sql.DB 12 } 13 14 - func Setup(dbPath string) (*DB, error) { 15 // https://github.com/mattn/go-sqlite3#connection-string 16 opts := []string{ 17 "_foreign_keys=1", ··· 20 "_auto_vacuum=incremental", 21 } 22 23 db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&")) 24 if err != nil { 25 return nil, err 26 } 27 28 - // NOTE: If any other migration is added here, you MUST 29 - // copy the pattern in appview: use a single sql.Conn 30 - // for every migration. 31 32 - _, err = db.Exec(` 33 create table if not exists known_dids ( 34 did text primary key 35 ); ··· 55 created integer not null default (strftime('%s', 'now')), 56 primary key (rkey, nsid) 57 ); 58 `) 59 if err != nil { 60 return nil, err 61 } 62 63 - return &DB{db: db}, nil 64 }
··· 1 package db 2 3 import ( 4 + "context" 5 "database/sql" 6 + "log/slog" 7 "strings" 8 9 _ "github.com/mattn/go-sqlite3" 10 + "tangled.org/core/log" 11 ) 12 13 type DB struct { 14 + db *sql.DB 15 + logger *slog.Logger 16 } 17 18 + func Setup(ctx context.Context, dbPath string) (*DB, error) { 19 // https://github.com/mattn/go-sqlite3#connection-string 20 opts := []string{ 21 "_foreign_keys=1", ··· 24 "_auto_vacuum=incremental", 25 } 26 27 + logger := log.FromContext(ctx) 28 + logger = log.SubLogger(logger, "db") 29 + 30 db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&")) 31 if err != nil { 32 return nil, err 33 } 34 35 + conn, err := db.Conn(ctx) 36 + if err != nil { 37 + return nil, err 38 + } 39 + defer conn.Close() 40 41 + _, err = conn.ExecContext(ctx, ` 42 create table if not exists known_dids ( 43 did text primary key 44 ); ··· 64 created integer not null default (strftime('%s', 'now')), 65 primary key (rkey, nsid) 66 ); 67 + 68 + create table if not exists migrations ( 69 + id integer primary key autoincrement, 70 + name text unique 71 + ); 72 `) 73 if err != nil { 74 return nil, err 75 } 76 77 + return &DB{ 78 + db: db, 79 + logger: logger, 80 + }, nil 81 }
+13 -1
knotserver/git/service/service.go
··· 95 return c.RunService(cmd) 96 } 97 98 func (c *ServiceCommand) UploadPack() error { 99 cmd := exec.Command("git", []string{ 100 - "-c", "uploadpack.allowFilter=true", 101 "upload-pack", 102 "--stateless-rpc", 103 ".",
··· 95 return c.RunService(cmd) 96 } 97 98 + func (c *ServiceCommand) UploadArchive() error { 99 + cmd := exec.Command("git", []string{ 100 + "upload-archive", 101 + ".", 102 + }...) 103 + 104 + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 105 + cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_PROTOCOL=%s", c.GitProtocol)) 106 + cmd.Dir = c.Dir 107 + 108 + return c.RunService(cmd) 109 + } 110 + 111 func (c *ServiceCommand) UploadPack() error { 112 cmd := exec.Command("git", []string{ 113 "upload-pack", 114 "--stateless-rpc", 115 ".",
+47
knotserver/git.go
··· 56 } 57 } 58 59 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 60 did := chi.URLParam(r, "did") 61 name := chi.URLParam(r, "name")
··· 56 } 57 } 58 59 + func (h *Knot) UploadArchive(w http.ResponseWriter, r *http.Request) { 60 + did := chi.URLParam(r, "did") 61 + name := chi.URLParam(r, "name") 62 + repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 63 + if err != nil { 64 + gitError(w, err.Error(), http.StatusInternalServerError) 65 + h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 66 + return 67 + } 68 + 69 + const expectedContentType = "application/x-git-upload-archive-request" 70 + contentType := r.Header.Get("Content-Type") 71 + if contentType != expectedContentType { 72 + gitError(w, fmt.Sprintf("Expected Content-Type: '%s', but received '%s'.", expectedContentType, contentType), http.StatusUnsupportedMediaType) 73 + } 74 + 75 + var bodyReader io.ReadCloser = r.Body 76 + if r.Header.Get("Content-Encoding") == "gzip" { 77 + gzipReader, err := gzip.NewReader(r.Body) 78 + if err != nil { 79 + gitError(w, err.Error(), http.StatusInternalServerError) 80 + h.l.Error("git: failed to create gzip reader", "handler", "UploadArchive", "error", err) 81 + return 82 + } 83 + defer gzipReader.Close() 84 + bodyReader = gzipReader 85 + } 86 + 87 + w.Header().Set("Content-Type", "application/x-git-upload-archive-result") 88 + 89 + h.l.Info("git: executing git-upload-archive", "handler", "UploadArchive", "repo", repo) 90 + 91 + cmd := service.ServiceCommand{ 92 + GitProtocol: r.Header.Get("Git-Protocol"), 93 + Dir: repo, 94 + Stdout: w, 95 + Stdin: bodyReader, 96 + } 97 + 98 + w.WriteHeader(http.StatusOK) 99 + 100 + if err := cmd.UploadArchive(); err != nil { 101 + h.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err) 102 + return 103 + } 104 + } 105 + 106 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 107 did := chi.URLParam(r, "did") 108 name := chi.URLParam(r, "name")
+1
knotserver/router.go
··· 82 r.Route("/{name}", func(r chi.Router) { 83 // routes for git operations 84 r.Get("/info/refs", h.InfoRefs) 85 r.Post("/git-upload-pack", h.UploadPack) 86 r.Post("/git-receive-pack", h.ReceivePack) 87 })
··· 82 r.Route("/{name}", func(r chi.Router) { 83 // routes for git operations 84 r.Get("/info/refs", h.InfoRefs) 85 + r.Post("/git-upload-archive", h.UploadArchive) 86 r.Post("/git-upload-pack", h.UploadPack) 87 r.Post("/git-receive-pack", h.ReceivePack) 88 })
+1 -1
knotserver/server.go
··· 64 logger.Info("running in dev mode, signature verification is disabled") 65 } 66 67 - db, err := db.Setup(c.Server.DBPath) 68 if err != nil { 69 return fmt.Errorf("failed to load db: %w", err) 70 }
··· 64 logger.Info("running in dev mode, signature verification is disabled") 65 } 66 67 + db, err := db.Setup(ctx, c.Server.DBPath) 68 if err != nil { 69 return fmt.Errorf("failed to load db: %w", err) 70 }
+1
spindle/db/repos.go
··· 16 if err != nil { 17 return nil, err 18 } 19 20 var knots []string 21 for rows.Next() {
··· 16 if err != nil { 17 return nil, err 18 } 19 + defer rows.Close() 20 21 var knots []string 22 for rows.Next() {
+17 -20
spindle/engine/engine.go
··· 3 import ( 4 "context" 5 "errors" 6 - "fmt" 7 "log/slog" 8 9 securejoin "github.com/cyphar/filepath-securejoin" 10 - "golang.org/x/sync/errgroup" 11 "tangled.org/core/notifier" 12 "tangled.org/core/spindle/config" 13 "tangled.org/core/spindle/db" ··· 31 } 32 } 33 34 - eg, ctx := errgroup.WithContext(ctx) 35 for eng, wfs := range pipeline.Workflows { 36 workflowTimeout := eng.WorkflowTimeout() 37 l.Info("using workflow timeout", "timeout", workflowTimeout) 38 39 for _, w := range wfs { 40 - eg.Go(func() error { 41 wid := models.WorkflowId{ 42 PipelineId: pipelineId, 43 Name: w.Name, ··· 45 46 err := db.StatusRunning(wid, n) 47 if err != nil { 48 - return err 49 } 50 51 err = eng.SetupWorkflow(ctx, wid, &w) ··· 61 62 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 63 if dbErr != nil { 64 - return dbErr 65 } 66 - return err 67 } 68 defer eng.DestroyWorkflow(ctx, wid) 69 ··· 99 if errors.Is(err, ErrTimedOut) { 100 dbErr := db.StatusTimeout(wid, n) 101 if dbErr != nil { 102 - return dbErr 103 } 104 } else { 105 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 106 if dbErr != nil { 107 - return dbErr 108 } 109 } 110 - 111 - return fmt.Errorf("starting steps image: %w", err) 112 } 113 } 114 115 err = db.StatusSuccess(wid, n) 116 if err != nil { 117 - return err 118 } 119 - 120 - return nil 121 - }) 122 } 123 } 124 125 - if err := eg.Wait(); err != nil { 126 - l.Error("failed to run one or more workflows", "err", err) 127 - } else { 128 - l.Info("successfully ran full pipeline") 129 - } 130 }
··· 3 import ( 4 "context" 5 "errors" 6 "log/slog" 7 + "sync" 8 9 securejoin "github.com/cyphar/filepath-securejoin" 10 "tangled.org/core/notifier" 11 "tangled.org/core/spindle/config" 12 "tangled.org/core/spindle/db" ··· 30 } 31 } 32 33 + var wg sync.WaitGroup 34 for eng, wfs := range pipeline.Workflows { 35 workflowTimeout := eng.WorkflowTimeout() 36 l.Info("using workflow timeout", "timeout", workflowTimeout) 37 38 for _, w := range wfs { 39 + wg.Add(1) 40 + go func() { 41 + defer wg.Done() 42 + 43 wid := models.WorkflowId{ 44 PipelineId: pipelineId, 45 Name: w.Name, ··· 47 48 err := db.StatusRunning(wid, n) 49 if err != nil { 50 + l.Error("failed to set workflow status to running", "wid", wid, "err", err) 51 + return 52 } 53 54 err = eng.SetupWorkflow(ctx, wid, &w) ··· 64 65 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 66 if dbErr != nil { 67 + l.Error("failed to set workflow status to failed", "wid", wid, "err", dbErr) 68 } 69 + return 70 } 71 defer eng.DestroyWorkflow(ctx, wid) 72 ··· 102 if errors.Is(err, ErrTimedOut) { 103 dbErr := db.StatusTimeout(wid, n) 104 if dbErr != nil { 105 + l.Error("failed to set workflow status to timeout", "wid", wid, "err", dbErr) 106 } 107 } else { 108 dbErr := db.StatusFailed(wid, err.Error(), -1, n) 109 if dbErr != nil { 110 + l.Error("failed to set workflow status to failed", "wid", wid, "err", dbErr) 111 } 112 } 113 + return 114 } 115 } 116 117 err = db.StatusSuccess(wid, n) 118 if err != nil { 119 + l.Error("failed to set workflow status to success", "wid", wid, "err", err) 120 } 121 + }() 122 } 123 } 124 125 + wg.Wait() 126 + l.Info("all workflows completed") 127 }