Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

knotmirror/xrpc: deprecate `repo.blob`

Signed-off-by: Seongmin Lee <git@boltless.me>

author
Seongmin Lee
committer
Tangled
date (Jun 11, 2026, 10:52 AM +0300) commit 1591ed85 parent e99c34b8 change-id zkrznnzs
-189
-1
knotmirror/xrpc/proxy.go
··· 25 25 tangled.GitTempGetBranchNSID: tangled.RepoBranchNSID, 26 26 tangled.GitTempGetTagNSID: tangled.RepoTagNSID, 27 27 tangled.GitTempGetArchiveNSID: tangled.RepoArchiveNSID, 28 - tangled.RepoBlobNSID: tangled.RepoBlobNSID, 29 28 tangled.GitTempListLanguagesNSID: tangled.RepoLanguagesNSID, 30 29 } 31 30
-187
knotmirror/xrpc/repo_blob.go
··· 1 - package xrpc 2 - 3 - import ( 4 - "context" 5 - "encoding/base64" 6 - "fmt" 7 - "io" 8 - "net/http" 9 - "path/filepath" 10 - "strings" 11 - "time" 12 - 13 - "github.com/bluesky-social/indigo/atproto/atclient" 14 - "github.com/bluesky-social/indigo/atproto/syntax" 15 - "github.com/go-git/go-git/v5/plumbing/filemode" 16 - "tangled.org/core/api/tangled" 17 - "tangled.org/core/knotmirror/xrpc/gitea" 18 - "tangled.org/core/knotserver/git" 19 - ) 20 - 21 - // TODO(boltless): rewrite lexicon in new NSID 22 - func (x *Xrpc) RepoBlob(w http.ResponseWriter, r *http.Request) { 23 - var ( 24 - repoQuery = r.URL.Query().Get("repo") 25 - ref = r.URL.Query().Get("ref") // ref can be empty (git.Open handles this) 26 - path = r.URL.Query().Get("path") 27 - ) 28 - 29 - repo, err := syntax.ParseDID(repoQuery) 30 - if err != nil { 31 - writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)}) 32 - return 33 - } 34 - 35 - l := x.logger.With("method", "repo.blob", "repo", repo, "ref", ref, "path", path) 36 - 37 - if path == "" { 38 - writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "missing path parameter"}) 39 - return 40 - } 41 - 42 - gr, err := x.getRepo(r.Context(), repo, ref) 43 - if err != nil { 44 - l.Warn("local mirror failed, trying proxy", "err", err) 45 - if x.proxyToKnot(w, r, repo) { 46 - return 47 - } 48 - writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get blob"}) 49 - return 50 - } 51 - 52 - ctx := r.Context() 53 - 54 - repoPath, err := x.makeRepoPath(ctx, repo) 55 - if err != nil { 56 - writeJson(w, http.StatusNotFound, atclient.ErrorBody{Name: "RepoNotFound", Message: fmt.Sprintf("unknown repository: %s", repo)}) 57 - return 58 - } 59 - 60 - entry, err := gitea.GetEntry(ctx, repoPath, ref, path) 61 - if err != nil { 62 - l.Warn("local mirror failed, trying proxy", "err", err) 63 - if x.proxyToKnot(w, r, repo) { 64 - return 65 - } 66 - writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get blob"}) 67 - return 68 - } 69 - 70 - if entry.Mode == filemode.Submodule { 71 - submodule, err := gr.Submodule(path) 72 - if err != nil { 73 - l.Warn("failed to load submodule", "err", err) 74 - writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to load submodule"}) 75 - return 76 - } 77 - writeJson(w, http.StatusOK, tangled.RepoBlob_Output{ 78 - Ref: ref, 79 - Path: path, 80 - Submodule: &tangled.RepoBlob_Submodule{ 81 - Name: submodule.Name, 82 - Url: submodule.URL, 83 - Branch: &submodule.Branch, 84 - }, 85 - }) 86 - return 87 - } 88 - 89 - size, reader, err := gitea.ReadBlob(ctx, repoPath, entry.Hash) 90 - if err != nil { 91 - l.Warn("local mirror failed, trying proxy", "err", err) 92 - if x.proxyToKnot(w, r, repo) { 93 - return 94 - } 95 - writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get blob"}) 96 - return 97 - } 98 - defer reader.Close() 99 - 100 - if size > 1000*1000 { // 1MB 101 - fileTooLarge := true 102 - writeJson(w, http.StatusOK, tangled.RepoBlob_Output{ 103 - Ref: ref, 104 - Path: path, 105 - Size: &size, 106 - FileTooLarge: &fileTooLarge, 107 - }) 108 - return 109 - } 110 - 111 - contents, err := io.ReadAll(reader) 112 - if err != nil { 113 - l.Error("failed to read blob content", "err", err) 114 - writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to read the blob"}) 115 - return 116 - } 117 - 118 - mimeType := http.DetectContentType(contents) 119 - // override MIME types for formats that http.DetectContentType does not recognize 120 - switch filepath.Ext(path) { 121 - case ".svg": 122 - mimeType = "image/svg+xml" 123 - case ".avif": 124 - mimeType = "image/avif" 125 - case ".jxl": 126 - mimeType = "image/jxl" 127 - case ".heic", ".heif": 128 - mimeType = "image/heif" 129 - } 130 - 131 - isBinary := !(strings.HasPrefix(mimeType, "text/") || isTextualMimeType(mimeType)) 132 - 133 - // include content for text blob or svg 134 - var content *string 135 - if !isBinary { 136 - content = new(string) 137 - *content = string(contents) 138 - } else if filepath.Ext(path) == ".svg" { 139 - content = new(string) 140 - *content = base64.StdEncoding.EncodeToString(contents) 141 - } 142 - 143 - response := tangled.RepoBlob_Output{ 144 - Ref: ref, 145 - Path: path, 146 - Size: &size, 147 - IsBinary: &isBinary, 148 - Content: content, 149 - } 150 - 151 - ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second) 152 - defer cancel() 153 - 154 - lastCommit, err := gr.LastCommitFile(ctx, path) 155 - if err == nil && lastCommit != nil { 156 - response.LastCommit = &tangled.RepoBlob_LastCommit{ 157 - Hash: lastCommit.Hash.String(), 158 - Message: lastCommit.Message, 159 - When: lastCommit.When.Format(time.RFC3339), 160 - } 161 - 162 - // try to get author information 163 - commit, err := gr.Commit(lastCommit.Hash) 164 - if err == nil { 165 - response.LastCommit.Author = &tangled.RepoBlob_Signature{ 166 - Name: commit.Author.Name, 167 - Email: commit.Author.Email, 168 - } 169 - } 170 - } 171 - 172 - writeJson(w, http.StatusOK, response) 173 - } 174 - 175 - func (x *Xrpc) getRepo(ctx context.Context, repo syntax.DID, ref string) (*git.GitRepo, error) { 176 - repoPath, err := x.makeRepoPath(ctx, repo) 177 - if err != nil { 178 - return nil, fmt.Errorf("resolving repo did: %w", err) 179 - } 180 - 181 - gr, err := git.Open(repoPath, ref) 182 - if err != nil { 183 - return nil, fmt.Errorf("opening git repo: %w", err) 184 - } 185 - 186 - return gr, nil 187 - }
-1
knotmirror/xrpc/xrpc.go
··· 72 72 r.Get("/"+tangled.GitTempListCommitsNSID, x.ListCommits) 73 73 r.Get("/"+tangled.GitTempListLanguagesNSID, x.ListLanguages) 74 74 r.Get("/"+tangled.GitTempListTagsNSID, x.ListTags) 75 - r.Get("/"+tangled.RepoBlobNSID, x.RepoBlob) 76 75 r.Post("/"+tangled.SyncRequestCrawlNSID, x.RequestCrawl) 77 76 }) 78 77