appview,knotserver: immutable nix flakeref link header #741

open
opened by boltless.me targeting master from push-ptrrwwvnkmxq
Changed files
+251 -353
api
tangled
appview
knotserver
lexicons
nix
-41
api/tangled/repoarchive.go
··· 1 - // Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. 2 - 3 - package tangled 4 - 5 - // schema: sh.tangled.repo.archive 6 - 7 - import ( 8 - "bytes" 9 - "context" 10 - 11 - "github.com/bluesky-social/indigo/lex/util" 12 - ) 13 - 14 - const ( 15 - RepoArchiveNSID = "sh.tangled.repo.archive" 16 - ) 17 - 18 - // RepoArchive calls the XRPC method "sh.tangled.repo.archive". 19 - // 20 - // format: Archive format 21 - // prefix: Prefix for files in the archive 22 - // ref: Git reference (branch, tag, or commit SHA) 23 - // repo: Repository identifier in format 'did:plc:.../repoName' 24 - func RepoArchive(ctx context.Context, c util.LexClient, format string, prefix string, ref string, repo string) ([]byte, error) { 25 - buf := new(bytes.Buffer) 26 - 27 - params := map[string]interface{}{} 28 - if format != "" { 29 - params["format"] = format 30 - } 31 - if prefix != "" { 32 - params["prefix"] = prefix 33 - } 34 - params["ref"] = ref 35 - params["repo"] = repo 36 - if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.archive", params, nil, buf); err != nil { 37 - return nil, err 38 - } 39 - 40 - return buf.Bytes(), nil 41 - }
-49
appview/repo/archive.go
··· 1 - package repo 2 - 3 - import ( 4 - "fmt" 5 - "net/http" 6 - "net/url" 7 - "strings" 8 - 9 - "tangled.org/core/api/tangled" 10 - xrpcclient "tangled.org/core/appview/xrpcclient" 11 - 12 - indigoxrpc "github.com/bluesky-social/indigo/xrpc" 13 - "github.com/go-chi/chi/v5" 14 - "github.com/go-git/go-git/v5/plumbing" 15 - ) 16 - 17 - func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) { 18 - l := rp.logger.With("handler", "DownloadArchive") 19 - ref := chi.URLParam(r, "ref") 20 - ref, _ = url.PathUnescape(ref) 21 - f, err := rp.repoResolver.Resolve(r) 22 - if err != nil { 23 - l.Error("failed to get repo and knot", "err", err) 24 - return 25 - } 26 - scheme := "http" 27 - if !rp.config.Core.Dev { 28 - scheme = "https" 29 - } 30 - host := fmt.Sprintf("%s://%s", scheme, f.Knot) 31 - xrpcc := &indigoxrpc.Client{ 32 - Host: host, 33 - } 34 - didSlashRepo := f.DidSlashRepo() 35 - archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, didSlashRepo) 36 - if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 37 - l.Error("failed to call XRPC repo.archive", "err", xrpcerr) 38 - rp.pages.Error503(w) 39 - return 40 - } 41 - // Set headers for file download, just pass along whatever the knot specifies 42 - safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 43 - filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename) 44 - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 45 - w.Header().Set("Content-Type", "application/gzip") 46 - w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes))) 47 - // Write the archive data directly 48 - w.Write(archiveBytes) 49 - }
-4
appview/repo/router.go
··· 40 40 r.Get("/blob/{ref}/*", rp.Blob) 41 41 r.Get("/raw/{ref}/*", rp.RepoBlobRaw) 42 42 43 - // intentionally doesn't use /* as this isn't 44 - // a file path 45 - r.Get("/archive/{ref}", rp.DownloadArchive) 46 - 47 43 r.Route("/fork", func(r chi.Router) { 48 44 r.Use(middleware.AuthMiddleware(rp.oauth)) 49 45 r.Get("/", rp.ForkRepo)
-97
appview/state/git_http.go
··· 1 - package state 2 - 3 - import ( 4 - "fmt" 5 - "io" 6 - "maps" 7 - "net/http" 8 - 9 - "github.com/bluesky-social/indigo/atproto/identity" 10 - "github.com/go-chi/chi/v5" 11 - "tangled.org/core/appview/models" 12 - ) 13 - 14 - func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) { 15 - user := r.Context().Value("resolvedId").(identity.Identity) 16 - repo := r.Context().Value("repo").(*models.Repo) 17 - 18 - scheme := "https" 19 - if s.config.Core.Dev { 20 - scheme = "http" 21 - } 22 - 23 - targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 24 - s.proxyRequest(w, r, targetURL) 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 { 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-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 42 - s.proxyRequest(w, r, targetURL) 43 - } 44 - 45 - func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) { 46 - user, ok := r.Context().Value("resolvedId").(identity.Identity) 47 - if !ok { 48 - http.Error(w, "failed to resolve user", http.StatusInternalServerError) 49 - return 50 - } 51 - repo := r.Context().Value("repo").(*models.Repo) 52 - 53 - scheme := "https" 54 - if s.config.Core.Dev { 55 - scheme = "http" 56 - } 57 - 58 - targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 59 - s.proxyRequest(w, r, targetURL) 60 - } 61 - 62 - func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string) { 63 - client := &http.Client{} 64 - 65 - // Create new request 66 - proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body) 67 - if err != nil { 68 - http.Error(w, err.Error(), http.StatusInternalServerError) 69 - return 70 - } 71 - 72 - // Copy original headers 73 - proxyReq.Header = r.Header 74 - 75 - repoOwnerHandle := chi.URLParam(r, "user") 76 - proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle) 77 - 78 - // Execute request 79 - resp, err := client.Do(proxyReq) 80 - if err != nil { 81 - http.Error(w, err.Error(), http.StatusInternalServerError) 82 - return 83 - } 84 - defer resp.Body.Close() 85 - 86 - // Copy response headers 87 - maps.Copy(w.Header(), resp.Header) 88 - 89 - // Set response status code 90 - w.WriteHeader(resp.StatusCode) 91 - 92 - // Copy response body 93 - if _, err := io.Copy(w, resp.Body); err != nil { 94 - http.Error(w, err.Error(), http.StatusInternalServerError) 95 - return 96 - } 97 - }
+167
appview/state/proxy_knot.go
··· 1 + package state 2 + 3 + import ( 4 + "fmt" 5 + "io" 6 + "maps" 7 + "net/http" 8 + "strings" 9 + 10 + "github.com/bluesky-social/indigo/atproto/identity" 11 + indigoxrpc "github.com/bluesky-social/indigo/xrpc" 12 + "github.com/go-chi/chi/v5" 13 + "github.com/go-git/go-git/v5/plumbing" 14 + "github.com/hashicorp/go-version" 15 + "tangled.org/core/api/tangled" 16 + "tangled.org/core/appview/models" 17 + xrpcclient "tangled.org/core/appview/xrpcclient" 18 + ) 19 + 20 + func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) { 21 + user := r.Context().Value("resolvedId").(identity.Identity) 22 + repo := r.Context().Value("repo").(*models.Repo) 23 + 24 + scheme := "https" 25 + if s.config.Core.Dev { 26 + scheme = "http" 27 + } 28 + 29 + targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 30 + s.proxyRequest(w, r, targetURL) 31 + 32 + } 33 + 34 + func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) { 35 + user, ok := r.Context().Value("resolvedId").(identity.Identity) 36 + if !ok { 37 + http.Error(w, "failed to resolve user", http.StatusInternalServerError) 38 + return 39 + } 40 + repo := r.Context().Value("repo").(*models.Repo) 41 + 42 + scheme := "https" 43 + if s.config.Core.Dev { 44 + scheme = "http" 45 + } 46 + 47 + targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 48 + s.proxyRequest(w, r, targetURL) 49 + } 50 + 51 + func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) { 52 + user, ok := r.Context().Value("resolvedId").(identity.Identity) 53 + if !ok { 54 + http.Error(w, "failed to resolve user", http.StatusInternalServerError) 55 + return 56 + } 57 + repo := r.Context().Value("repo").(*models.Repo) 58 + 59 + scheme := "https" 60 + if s.config.Core.Dev { 61 + scheme = "http" 62 + } 63 + 64 + targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery) 65 + s.proxyRequest(w, r, targetURL) 66 + } 67 + 68 + var knotVersionDownloadArchiveConstraint = version.MustConstraints(version.NewConstraint(">= 1.12")) 69 + 70 + func (s *State) DownloadArchive(w http.ResponseWriter, r *http.Request) { 71 + l := s.logger.With("handler", "DownloadArchive") 72 + ref := chi.URLParam(r, "ref") 73 + 74 + user, ok := r.Context().Value("resolvedId").(identity.Identity) 75 + if !ok { 76 + l.Error("failed to resolve user") 77 + http.Error(w, "failed to resolve user", http.StatusInternalServerError) 78 + return 79 + } 80 + repo := r.Context().Value("repo").(*models.Repo) 81 + 82 + scheme := "https" 83 + if s.config.Core.Dev { 84 + scheme = "http" 85 + } 86 + 87 + xrpcc := &indigoxrpc.Client{ 88 + Host: repo.Knot, 89 + } 90 + l = l.With("knot", repo.Knot) 91 + 92 + isCompatible := func() bool { 93 + out, err := tangled.KnotVersion(r.Context(), xrpcc) 94 + if err != nil { 95 + l.Warn("failed to get knot version", "err", err) 96 + return false 97 + } 98 + 99 + v, err := version.NewVersion(out.Version) 100 + if err != nil { 101 + l.Warn("failed to parse knot version", "version", out.Version, "err", err) 102 + return false 103 + } 104 + 105 + if !knotVersionDownloadArchiveConstraint.Check(v) { 106 + l.Warn("knot version incompatible.", "version", v) 107 + return false 108 + } 109 + return true 110 + }() 111 + l.Debug("knot compatibility check", "isCompatible", isCompatible) 112 + if isCompatible { 113 + targetURL := fmt.Sprintf("%s://%s/%s/%s/archive/%s", scheme, repo.Knot, user.DID, repo.Name, ref) 114 + s.proxyRequest(w, r, targetURL) 115 + } else { 116 + l.Debug("requesting xrpc/sh.tangled.repo.archive") 117 + archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo.DidSlashRepo()) 118 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 119 + l.Error("failed to call XRPC repo.archive", "err", xrpcerr) 120 + s.pages.Error503(w) 121 + return 122 + } 123 + safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 124 + filename := fmt.Sprintf("%s-%s.tar.gz", repo.Name, safeRefFilename) 125 + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 126 + w.Header().Set("Content-Type", "application/gzip") 127 + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes))) 128 + w.Write(archiveBytes) 129 + } 130 + } 131 + 132 + func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string) { 133 + client := &http.Client{} 134 + 135 + // Create new request 136 + proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body) 137 + if err != nil { 138 + http.Error(w, err.Error(), http.StatusInternalServerError) 139 + return 140 + } 141 + 142 + // Copy original headers 143 + proxyReq.Header = r.Header 144 + 145 + repoOwnerHandle := chi.URLParam(r, "user") 146 + proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle) 147 + 148 + // Execute request 149 + resp, err := client.Do(proxyReq) 150 + if err != nil { 151 + http.Error(w, err.Error(), http.StatusInternalServerError) 152 + return 153 + } 154 + defer resp.Body.Close() 155 + 156 + // Copy response headers 157 + maps.Copy(w.Header(), resp.Header) 158 + 159 + // Set response status code 160 + w.WriteHeader(resp.StatusCode) 161 + 162 + // Copy response body 163 + if _, err := io.Copy(w, resp.Body); err != nil { 164 + http.Error(w, err.Error(), http.StatusInternalServerError) 165 + return 166 + } 167 + }
+3 -1
appview/state/router.go
··· 103 103 r.Get("/info/refs", s.InfoRefs) 104 104 r.Post("/git-upload-pack", s.UploadPack) 105 105 r.Post("/git-receive-pack", s.ReceivePack) 106 - 106 + // intentionally doesn't use /* as this isn't 107 + // a file path 108 + r.Get("/archive/{ref}", s.DownloadArchive) 107 109 }) 108 110 }) 109 111
+1
go.mod
··· 132 132 github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect 133 133 github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 134 134 github.com/hashicorp/go-sockaddr v1.0.7 // indirect 135 + github.com/hashicorp/go-version v1.8.0 // indirect 135 136 github.com/hashicorp/golang-lru v1.0.2 // indirect 136 137 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 137 138 github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
+2
go.sum
··· 264 264 github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= 265 265 github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= 266 266 github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= 267 + github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= 268 + github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 267 269 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 268 270 github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 269 271 github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+69
knotserver/archive.go
··· 1 + package knotserver 2 + 3 + import ( 4 + "compress/gzip" 5 + "fmt" 6 + "net/http" 7 + "strings" 8 + 9 + securejoin "github.com/cyphar/filepath-securejoin" 10 + "github.com/go-chi/chi/v5" 11 + "github.com/go-git/go-git/v5/plumbing" 12 + "tangled.org/core/knotserver/git" 13 + ) 14 + 15 + func (h *Knot) Archive(w http.ResponseWriter, r *http.Request) { 16 + var ( 17 + did = chi.URLParam(r, "did") 18 + name = chi.URLParam(r, "name") 19 + ref = chi.URLParam(r, "ref") 20 + ) 21 + repo, err := securejoin.SecureJoin(did, name) 22 + if err != nil { 23 + gitError(w, "repository not found", http.StatusNotFound) 24 + h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 25 + return 26 + } 27 + 28 + repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, repo) 29 + if err != nil { 30 + gitError(w, "repository not found", http.StatusNotFound) 31 + h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 32 + return 33 + } 34 + 35 + gr, err := git.Open(repoPath, ref) 36 + 37 + immutableLink := fmt.Sprintf( 38 + "https://%s/%s/%s/archive/%s", 39 + h.c.Server.Hostname, 40 + did, 41 + name, 42 + gr.Hash(), 43 + ) 44 + 45 + safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 46 + filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename) 47 + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 48 + w.Header().Set("Content-Type", "application/gzip") 49 + w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink)) 50 + 51 + gw := gzip.NewWriter(w) 52 + defer gw.Close() 53 + 54 + err = gr.WriteTar(gw, "") 55 + if err != nil { 56 + // once we start writing to the body we can't report error anymore 57 + // so we are only left with logging the error 58 + h.l.Error("writing tar file", "error", err) 59 + return 60 + } 61 + 62 + err = gw.Flush() 63 + if err != nil { 64 + // once we start writing to the body we can't report error anymore 65 + // so we are only left with logging the error 66 + h.l.Error("flushing", "error", err.Error()) 67 + return 68 + } 69 + }
+4
knotserver/git/git.go
··· 76 76 return &g, nil 77 77 } 78 78 79 + func (g *GitRepo) Hash() plumbing.Hash { 80 + return g.h 81 + } 82 + 79 83 // re-open a repository and update references 80 84 func (g *GitRepo) Refresh() error { 81 85 refreshed, err := PlainOpen(g.path)
+2
knotserver/router.go
··· 84 84 r.Get("/info/refs", h.InfoRefs) 85 85 r.Post("/git-upload-pack", h.UploadPack) 86 86 r.Post("/git-receive-pack", h.ReceivePack) 87 + // convenience routes 88 + r.Get("/archive/{ref}", h.Archive) 87 89 }) 88 90 }) 89 91
-81
knotserver/xrpc/repo_archive.go
··· 1 - package xrpc 2 - 3 - import ( 4 - "compress/gzip" 5 - "fmt" 6 - "net/http" 7 - "strings" 8 - 9 - "github.com/go-git/go-git/v5/plumbing" 10 - 11 - "tangled.org/core/knotserver/git" 12 - xrpcerr "tangled.org/core/xrpc/errors" 13 - ) 14 - 15 - func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) { 16 - repo := r.URL.Query().Get("repo") 17 - repoPath, err := x.parseRepoParam(repo) 18 - if err != nil { 19 - writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest) 20 - return 21 - } 22 - 23 - ref := r.URL.Query().Get("ref") 24 - // ref can be empty (git.Open handles this) 25 - 26 - format := r.URL.Query().Get("format") 27 - if format == "" { 28 - format = "tar.gz" // default 29 - } 30 - 31 - prefix := r.URL.Query().Get("prefix") 32 - 33 - if format != "tar.gz" { 34 - writeError(w, xrpcerr.NewXrpcError( 35 - xrpcerr.WithTag("InvalidRequest"), 36 - xrpcerr.WithMessage("only tar.gz format is supported"), 37 - ), http.StatusBadRequest) 38 - return 39 - } 40 - 41 - gr, err := git.Open(repoPath, ref) 42 - if err != nil { 43 - writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 44 - return 45 - } 46 - 47 - repoParts := strings.Split(repo, "/") 48 - repoName := repoParts[len(repoParts)-1] 49 - 50 - safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 51 - 52 - var archivePrefix string 53 - if prefix != "" { 54 - archivePrefix = prefix 55 - } else { 56 - archivePrefix = fmt.Sprintf("%s-%s", repoName, safeRefFilename) 57 - } 58 - 59 - filename := fmt.Sprintf("%s-%s.tar.gz", repoName, safeRefFilename) 60 - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 61 - w.Header().Set("Content-Type", "application/gzip") 62 - 63 - gw := gzip.NewWriter(w) 64 - defer gw.Close() 65 - 66 - err = gr.WriteTar(gw, archivePrefix) 67 - if err != nil { 68 - // once we start writing to the body we can't report error anymore 69 - // so we are only left with logging the error 70 - x.Logger.Error("writing tar file", "error", err.Error()) 71 - return 72 - } 73 - 74 - err = gw.Flush() 75 - if err != nil { 76 - // once we start writing to the body we can't report error anymore 77 - // so we are only left with logging the error 78 - x.Logger.Error("flushing", "error", err.Error()) 79 - return 80 - } 81 - }
-1
knotserver/xrpc/xrpc.go
··· 64 64 r.Get("/"+tangled.RepoCompareNSID, x.RepoCompare) 65 65 r.Get("/"+tangled.RepoGetDefaultBranchNSID, x.RepoGetDefaultBranch) 66 66 r.Get("/"+tangled.RepoBranchNSID, x.RepoBranch) 67 - r.Get("/"+tangled.RepoArchiveNSID, x.RepoArchive) 68 67 r.Get("/"+tangled.RepoLanguagesNSID, x.RepoLanguages) 69 68 70 69 // knot query endpoints (no auth required)
-55
lexicons/repo/archive.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "sh.tangled.repo.archive", 4 - "defs": { 5 - "main": { 6 - "type": "query", 7 - "parameters": { 8 - "type": "params", 9 - "required": ["repo", "ref"], 10 - "properties": { 11 - "repo": { 12 - "type": "string", 13 - "description": "Repository identifier in format 'did:plc:.../repoName'" 14 - }, 15 - "ref": { 16 - "type": "string", 17 - "description": "Git reference (branch, tag, or commit SHA)" 18 - }, 19 - "format": { 20 - "type": "string", 21 - "description": "Archive format", 22 - "enum": ["tar", "zip", "tar.gz", "tar.bz2", "tar.xz"], 23 - "default": "tar.gz" 24 - }, 25 - "prefix": { 26 - "type": "string", 27 - "description": "Prefix for files in the archive" 28 - } 29 - } 30 - }, 31 - "output": { 32 - "encoding": "*/*", 33 - "description": "Binary archive data" 34 - }, 35 - "errors": [ 36 - { 37 - "name": "RepoNotFound", 38 - "description": "Repository not found or access denied" 39 - }, 40 - { 41 - "name": "RefNotFound", 42 - "description": "Git reference not found" 43 - }, 44 - { 45 - "name": "InvalidRequest", 46 - "description": "Invalid request parameters" 47 - }, 48 - { 49 - "name": "ArchiveError", 50 - "description": "Failed to create archive" 51 - } 52 - ] 53 - } 54 - } 55 - }
+3 -24
nix/gomod2nix.toml
··· 165 165 [mod."github.com/davecgh/go-spew"] 166 166 version = "v1.1.2-0.20180830191138-d8f796af33cc" 167 167 hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc=" 168 - [mod."github.com/decred/dcrd/dcrec/secp256k1/v4"] 169 - version = "v4.4.0" 170 - hash = "sha256-qrhEIwhDll3cxoVpMbm1NQ9/HTI42S7ms8Buzlo5HCg=" 171 168 [mod."github.com/dgraph-io/ristretto"] 172 169 version = "v0.2.0" 173 170 hash = "sha256-bnpxX+oO/Qf7IJevA0gsbloVoqRx+5bh7RQ9d9eLNYw=" ··· 307 304 [mod."github.com/hashicorp/go-sockaddr"] 308 305 version = "v1.0.7" 309 306 hash = "sha256-p6eDOrGzN1jMmT/F/f/VJMq0cKNFhUcEuVVwTE6vSrs=" 307 + [mod."github.com/hashicorp/go-version"] 308 + version = "v1.8.0" 309 + hash = "sha256-KXtqERmYrWdpqPCViWcHbe6jnuH7k16bvBIcuJuevj8=" 310 310 [mod."github.com/hashicorp/golang-lru"] 311 311 version = "v1.0.2" 312 312 hash = "sha256-yy+5botc6T5wXgOe2mfNXJP3wr+MkVlUZ2JBkmmrA48=" ··· 373 373 [mod."github.com/klauspost/cpuid/v2"] 374 374 version = "v2.3.0" 375 375 hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc=" 376 - [mod."github.com/lestrrat-go/blackmagic"] 377 - version = "v1.0.4" 378 - hash = "sha256-HmWOpwoPDNMwLdOi7onNn3Sb+ZsAa3Ai3gVBbXmQ0e8=" 379 - [mod."github.com/lestrrat-go/httpcc"] 380 - version = "v1.0.1" 381 - hash = "sha256-SMRSwJpqDIs/xL0l2e8vP0W65qtCHX2wigcOeqPJmos=" 382 - [mod."github.com/lestrrat-go/httprc"] 383 - version = "v1.0.6" 384 - hash = "sha256-mfZzePEhrmyyu/avEBd2MsDXyto8dq5+fyu5lA8GUWM=" 385 - [mod."github.com/lestrrat-go/iter"] 386 - version = "v1.0.2" 387 - hash = "sha256-30tErRf7Qu/NOAt1YURXY/XJSA6sCr6hYQfO8QqHrtw=" 388 - [mod."github.com/lestrrat-go/jwx/v2"] 389 - version = "v2.1.6" 390 - hash = "sha256-0LszXRZIba+X8AOrs3T4uanAUafBdlVB8/MpUNEFpbc=" 391 - [mod."github.com/lestrrat-go/option"] 392 - version = "v1.0.1" 393 - hash = "sha256-jVcIYYVsxElIS/l2akEw32vdEPR8+anR6oeT1FoYULI=" 394 376 [mod."github.com/lucasb-eyer/go-colorful"] 395 377 version = "v1.2.0" 396 378 hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE=" ··· 511 493 [mod."github.com/ryanuber/go-glob"] 512 494 version = "v1.0.0" 513 495 hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY=" 514 - [mod."github.com/segmentio/asm"] 515 - version = "v1.2.0" 516 - hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs=" 517 496 [mod."github.com/sergi/go-diff"] 518 497 version = "v1.1.0" 519 498 hash = "sha256-8NJMabldpf40uwQN20T6QXx5KORDibCBJL02KD661xY="