Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

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

appview/repo: rework repo handlers to use xrpc calls

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

authored by

Anirudh Oppiliappan and committed by
oppiliappan
f25a51e4 f21c584e

+630 -180
+12
appview/pages/markup/format.go
··· 13 13 FormatMarkdown: []string{".md", ".markdown", ".mdown", ".mkdn", ".mkd"}, 14 14 } 15 15 16 + // ReadmeFilenames contains the list of common README filenames to search for, 17 + // in order of preference. Only includes well-supported formats. 18 + var ReadmeFilenames = []string{ 19 + "README.md", "readme.md", 20 + "README", 21 + "readme", 22 + "README.markdown", 23 + "readme.markdown", 24 + "README.txt", 25 + "readme.txt", 26 + } 27 + 16 28 func GetFormat(filename string) Format { 17 29 for format, extensions := range FileTypes { 18 30 for _, extension := range extensions {
+6 -1
appview/pages/pages.go
··· 763 763 ShowRendered bool 764 764 RenderToggle bool 765 765 RenderedContents template.HTML 766 - types.RepoBlobResponse 766 + *tangled.RepoBlob_Output 767 + // Computed fields for template compatibility 768 + Contents string 769 + Lines int 770 + SizeHint uint64 771 + IsBinary bool 767 772 } 768 773 769 774 func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error {
+6
appview/pages/templates/repo/fragments/diff.html
··· 11 11 {{ $last := sub (len $diff) 1 }} 12 12 13 13 <div class="flex flex-col gap-4"> 14 + {{ if eq (len $diff) 0 }} 15 + <div class="text-center text-gray-500 dark:text-gray-400 py-8"> 16 + <p>No differences found between the selected revisions.</p> 17 + </div> 18 + {{ else }} 14 19 {{ range $idx, $hunk := $diff }} 15 20 {{ with $hunk }} 16 21 <details open id="file-{{ .Name.New }}" class="group border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm" tabindex="{{ add $idx 1 }}"> ··· 54 49 </div> 55 50 </details> 56 51 {{ end }} 52 + {{ end }} 57 53 {{ end }} 58 54 </div> 59 55 {{ end }}
+26 -8
appview/repo/artifact.go
··· 1 1 package repo 2 2 3 3 import ( 4 + "context" 5 + "encoding/json" 4 6 "fmt" 5 7 "log" 6 8 "net/http" ··· 11 9 12 10 comatproto "github.com/bluesky-social/indigo/api/atproto" 13 11 lexutil "github.com/bluesky-social/indigo/lex/util" 12 + indigoxrpc "github.com/bluesky-social/indigo/xrpc" 14 13 "github.com/dustin/go-humanize" 15 14 "github.com/go-chi/chi/v5" 16 15 "github.com/go-git/go-git/v5/plumbing" ··· 20 17 "tangled.sh/tangled.sh/core/appview/db" 21 18 "tangled.sh/tangled.sh/core/appview/pages" 22 19 "tangled.sh/tangled.sh/core/appview/reporesolver" 23 - "tangled.sh/tangled.sh/core/knotclient" 20 + "tangled.sh/tangled.sh/core/appview/xrpcclient" 24 21 "tangled.sh/tangled.sh/core/tid" 25 22 "tangled.sh/tangled.sh/core/types" 26 23 ) ··· 36 33 return 37 34 } 38 35 39 - tag, err := rp.resolveTag(f, tagParam) 36 + tag, err := rp.resolveTag(r.Context(), f, tagParam) 40 37 if err != nil { 41 38 log.Println("failed to resolve tag", err) 42 39 rp.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution") ··· 143 140 return 144 141 } 145 142 146 - tag, err := rp.resolveTag(f, tagParam) 143 + tag, err := rp.resolveTag(r.Context(), f, tagParam) 147 144 if err != nil { 148 145 log.Println("failed to resolve tag", err) 149 146 rp.pages.Notice(w, "upload", "failed to upload artifact, error in tag resolution") ··· 262 259 w.Write([]byte{}) 263 260 } 264 261 265 - func (rp *Repo) resolveTag(f *reporesolver.ResolvedRepo, tagParam string) (*types.TagReference, error) { 262 + func (rp *Repo) resolveTag(ctx context.Context, f *reporesolver.ResolvedRepo, tagParam string) (*types.TagReference, error) { 266 263 tagParam, err := url.QueryUnescape(tagParam) 267 264 if err != nil { 268 265 return nil, err 269 266 } 270 267 271 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 268 + scheme := "http" 269 + if !rp.config.Core.Dev { 270 + scheme = "https" 271 + } 272 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 273 + xrpcc := &indigoxrpc.Client{ 274 + Host: host, 275 + } 276 + 277 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 278 + xrpcBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repo) 272 279 if err != nil { 280 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 281 + log.Println("failed to call XRPC repo.tags", xrpcerr) 282 + return nil, xrpcerr 283 + } 284 + log.Println("failed to reach knotserver", err) 273 285 return nil, err 274 286 } 275 287 276 - result, err := us.Tags(f.OwnerDid(), f.Name) 277 - if err != nil { 278 - log.Println("failed to reach knotserver", err) 288 + var result types.RepoTagsResponse 289 + if err := json.Unmarshal(xrpcBytes, &result); err != nil { 290 + log.Println("failed to decode XRPC tags response", err) 279 291 return nil, err 280 292 } 281 293
+220 -16
appview/repo/index.go
··· 1 1 package repo 2 2 3 3 import ( 4 + "fmt" 4 5 "log" 5 6 "net/http" 6 7 "slices" 7 8 "sort" 8 9 "strings" 10 + "sync" 11 + "time" 9 12 13 + "context" 14 + "encoding/json" 15 + 16 + indigoxrpc "github.com/bluesky-social/indigo/xrpc" 17 + "github.com/go-git/go-git/v5/plumbing" 18 + "tangled.sh/tangled.sh/core/api/tangled" 10 19 "tangled.sh/tangled.sh/core/appview/commitverify" 11 20 "tangled.sh/tangled.sh/core/appview/db" 12 21 "tangled.sh/tangled.sh/core/appview/pages" 22 + "tangled.sh/tangled.sh/core/appview/pages/markup" 13 23 "tangled.sh/tangled.sh/core/appview/reporesolver" 14 - "tangled.sh/tangled.sh/core/knotclient" 24 + "tangled.sh/tangled.sh/core/appview/xrpcclient" 15 25 "tangled.sh/tangled.sh/core/types" 16 26 17 27 "github.com/go-chi/chi/v5" ··· 37 27 return 38 28 } 39 29 40 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 41 - if err != nil { 42 - log.Printf("failed to create unsigned client for %s", f.Knot) 43 - rp.pages.Error503(w) 44 - return 30 + scheme := "http" 31 + if !rp.config.Core.Dev { 32 + scheme = "https" 33 + } 34 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 35 + xrpcc := &indigoxrpc.Client{ 36 + Host: host, 45 37 } 46 38 47 - result, err := us.Index(f.OwnerDid(), f.Name, ref) 39 + // Build index response from multiple XRPC calls 40 + result, err := rp.buildIndexResponse(r.Context(), xrpcc, f, ref) 48 41 if err != nil { 49 42 rp.pages.Error503(w) 50 - log.Println("failed to reach knotserver", err) 43 + log.Println("failed to build index response", err) 51 44 return 52 45 } 53 46 ··· 115 102 repoInfo := f.RepoInfo(user) 116 103 117 104 // TODO: a bit dirty 118 - languageInfo, err := rp.getLanguageInfo(f, us, result.Ref, ref == "") 105 + languageInfo, err := rp.getLanguageInfo(r.Context(), f, xrpcc, result.Ref, ref == "") 119 106 if err != nil { 120 107 log.Printf("failed to compute language percentages: %s", err) 121 108 // non-fatal ··· 148 135 } 149 136 150 137 func (rp *Repo) getLanguageInfo( 138 + ctx context.Context, 151 139 f *reporesolver.ResolvedRepo, 152 - us *knotclient.UnsignedClient, 140 + xrpcc *indigoxrpc.Client, 153 141 currentRef string, 154 142 isDefaultRef bool, 155 143 ) ([]types.RepoLanguageDetails, error) { ··· 162 148 ) 163 149 164 150 if err != nil || langs == nil { 165 - // non-fatal, fetch langs from ks 166 - ls, err := us.RepoLanguages(f.OwnerDid(), f.Name, currentRef) 151 + // non-fatal, fetch langs from ks via XRPC 152 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 153 + ls, err := tangled.RepoLanguages(ctx, xrpcc, currentRef, repo) 167 154 if err != nil { 155 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 156 + log.Println("failed to call XRPC repo.languages", xrpcerr) 157 + return nil, xrpcerr 158 + } 168 159 return nil, err 169 160 } 170 - if ls == nil { 161 + 162 + if ls == nil || ls.Languages == nil { 171 163 return nil, nil 172 164 } 173 165 174 - for l, s := range ls.Languages { 166 + for _, lang := range ls.Languages { 175 167 langs = append(langs, db.RepoLanguage{ 176 168 RepoAt: f.RepoAt(), 177 169 Ref: currentRef, 178 170 IsDefaultRef: isDefaultRef, 179 - Language: l, 180 - Bytes: s, 171 + Language: lang.Name, 172 + Bytes: lang.Size, 181 173 }) 182 174 } 183 175 ··· 225 205 }) 226 206 227 207 return languageStats, nil 208 + } 209 + 210 + // buildIndexResponse creates a RepoIndexResponse by combining multiple xrpc calls in parallel 211 + func (rp *Repo) buildIndexResponse(ctx context.Context, xrpcc *indigoxrpc.Client, f *reporesolver.ResolvedRepo, ref string) (*types.RepoIndexResponse, error) { 212 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 213 + 214 + // first get branches to determine the ref if not specified 215 + branchesBytes, err := tangled.RepoBranches(ctx, xrpcc, "", 0, repo) 216 + if err != nil { 217 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 218 + log.Println("failed to call XRPC repo.branches", xrpcerr) 219 + return nil, xrpcerr 220 + } 221 + return nil, err 222 + } 223 + 224 + var branchesResp types.RepoBranchesResponse 225 + if err := json.Unmarshal(branchesBytes, &branchesResp); err != nil { 226 + return nil, err 227 + } 228 + 229 + // if no ref specified, use default branch or first available 230 + if ref == "" && len(branchesResp.Branches) > 0 { 231 + for _, branch := range branchesResp.Branches { 232 + if branch.IsDefault { 233 + ref = branch.Name 234 + break 235 + } 236 + } 237 + if ref == "" { 238 + ref = branchesResp.Branches[0].Name 239 + } 240 + } 241 + 242 + // check if repo is empty 243 + if len(branchesResp.Branches) == 0 { 244 + return &types.RepoIndexResponse{ 245 + IsEmpty: true, 246 + Branches: branchesResp.Branches, 247 + }, nil 248 + } 249 + 250 + // now run the remaining queries in parallel 251 + var wg sync.WaitGroup 252 + var mu sync.Mutex 253 + var errs []error 254 + 255 + var ( 256 + tagsResp types.RepoTagsResponse 257 + treeResp *tangled.RepoTree_Output 258 + logResp types.RepoLogResponse 259 + readmeContent string 260 + readmeFileName string 261 + ) 262 + 263 + // tags 264 + wg.Add(1) 265 + go func() { 266 + defer wg.Done() 267 + tagsBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repo) 268 + if err != nil { 269 + mu.Lock() 270 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 271 + log.Println("failed to call XRPC repo.tags", xrpcerr) 272 + errs = append(errs, xrpcerr) 273 + } else { 274 + errs = append(errs, err) 275 + } 276 + mu.Unlock() 277 + return 278 + } 279 + 280 + if err := json.Unmarshal(tagsBytes, &tagsResp); err != nil { 281 + mu.Lock() 282 + errs = append(errs, err) 283 + mu.Unlock() 284 + } 285 + }() 286 + 287 + // tree/files 288 + wg.Add(1) 289 + go func() { 290 + defer wg.Done() 291 + resp, err := tangled.RepoTree(ctx, xrpcc, "", ref, repo) 292 + if err != nil { 293 + mu.Lock() 294 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 295 + log.Println("failed to call XRPC repo.tree", xrpcerr) 296 + errs = append(errs, xrpcerr) 297 + } else { 298 + errs = append(errs, err) 299 + } 300 + mu.Unlock() 301 + return 302 + } 303 + treeResp = resp 304 + }() 305 + 306 + // commits 307 + wg.Add(1) 308 + go func() { 309 + defer wg.Done() 310 + logBytes, err := tangled.RepoLog(ctx, xrpcc, "", 50, "", ref, repo) 311 + if err != nil { 312 + mu.Lock() 313 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 314 + log.Println("failed to call XRPC repo.log", xrpcerr) 315 + errs = append(errs, xrpcerr) 316 + } else { 317 + errs = append(errs, err) 318 + } 319 + mu.Unlock() 320 + return 321 + } 322 + 323 + if err := json.Unmarshal(logBytes, &logResp); err != nil { 324 + mu.Lock() 325 + errs = append(errs, err) 326 + mu.Unlock() 327 + } 328 + }() 329 + 330 + // readme content 331 + wg.Add(1) 332 + go func() { 333 + defer wg.Done() 334 + for _, filename := range markup.ReadmeFilenames { 335 + blobResp, err := tangled.RepoBlob(ctx, xrpcc, filename, false, ref, repo) 336 + if err != nil { 337 + continue 338 + } 339 + 340 + if blobResp == nil { 341 + continue 342 + } 343 + 344 + readmeContent = blobResp.Content 345 + readmeFileName = filename 346 + break 347 + } 348 + }() 349 + 350 + wg.Wait() 351 + 352 + if len(errs) > 0 { 353 + return nil, errs[0] // return first error 354 + } 355 + 356 + var files []types.NiceTree 357 + if treeResp != nil && treeResp.Files != nil { 358 + for _, file := range treeResp.Files { 359 + niceFile := types.NiceTree{ 360 + IsFile: file.Is_file, 361 + IsSubtree: file.Is_subtree, 362 + Name: file.Name, 363 + Mode: file.Mode, 364 + Size: file.Size, 365 + } 366 + if file.Last_commit != nil { 367 + when, _ := time.Parse(time.RFC3339, file.Last_commit.When) 368 + niceFile.LastCommit = &types.LastCommitInfo{ 369 + Hash: plumbing.NewHash(file.Last_commit.Hash), 370 + Message: file.Last_commit.Message, 371 + When: when, 372 + } 373 + } 374 + files = append(files, niceFile) 375 + } 376 + } 377 + 378 + result := &types.RepoIndexResponse{ 379 + IsEmpty: false, 380 + Ref: ref, 381 + Readme: readmeContent, 382 + ReadmeFileName: readmeFileName, 383 + Commits: logResp.Commits, 384 + Description: logResp.Description, 385 + Files: files, 386 + Branches: branchesResp.Branches, 387 + Tags: tagsResp.Tags, 388 + TotalCommits: logResp.Total, 389 + } 390 + 391 + return result, nil 228 392 }
+359 -154
appview/repo/repo.go
··· 11 11 "log/slog" 12 12 "net/http" 13 13 "net/url" 14 + "path" 14 15 "path/filepath" 15 16 "slices" 16 17 "strconv" ··· 20 19 21 20 comatproto "github.com/bluesky-social/indigo/api/atproto" 22 21 lexutil "github.com/bluesky-social/indigo/lex/util" 22 + indigoxrpc "github.com/bluesky-social/indigo/xrpc" 23 23 "tangled.sh/tangled.sh/core/api/tangled" 24 24 "tangled.sh/tangled.sh/core/appview/commitverify" 25 25 "tangled.sh/tangled.sh/core/appview/config" ··· 33 31 xrpcclient "tangled.sh/tangled.sh/core/appview/xrpcclient" 34 32 "tangled.sh/tangled.sh/core/eventconsumer" 35 33 "tangled.sh/tangled.sh/core/idresolver" 36 - "tangled.sh/tangled.sh/core/knotclient" 37 34 "tangled.sh/tangled.sh/core/patchutil" 38 35 "tangled.sh/tangled.sh/core/rbac" 39 36 "tangled.sh/tangled.sh/core/tid" ··· 93 92 return 94 93 } 95 94 96 - var uri string 97 - if rp.config.Core.Dev { 98 - uri = "http" 99 - } else { 100 - uri = "https" 95 + scheme := "http" 96 + if !rp.config.Core.Dev { 97 + scheme = "https" 101 98 } 102 - url := fmt.Sprintf("%s://%s/%s/%s/archive/%s.tar.gz", uri, f.Knot, f.OwnerDid(), f.Name, url.PathEscape(refParam)) 99 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 100 + xrpcc := &indigoxrpc.Client{ 101 + Host: host, 102 + } 103 103 104 - http.Redirect(w, r, url, http.StatusFound) 104 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 105 + archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", refParam, repo) 106 + if err != nil { 107 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 108 + log.Println("failed to call XRPC repo.archive", xrpcerr) 109 + rp.pages.Error503(w) 110 + return 111 + } 112 + rp.pages.Error404(w) 113 + return 114 + } 115 + 116 + // Set headers for file download 117 + filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, refParam) 118 + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 119 + w.Header().Set("Content-Type", "application/gzip") 120 + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes))) 121 + 122 + // Write the archive data directly 123 + w.Write(archiveBytes) 105 124 } 106 125 107 126 func (rp *Repo) RepoLog(w http.ResponseWriter, r *http.Request) { ··· 141 120 142 121 ref := chi.URLParam(r, "ref") 143 122 144 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 123 + scheme := "http" 124 + if !rp.config.Core.Dev { 125 + scheme = "https" 126 + } 127 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 128 + xrpcc := &indigoxrpc.Client{ 129 + Host: host, 130 + } 131 + 132 + limit := int64(60) 133 + cursor := "" 134 + if page > 1 { 135 + // Convert page number to cursor (offset) 136 + offset := (page - 1) * int(limit) 137 + cursor = strconv.Itoa(offset) 138 + } 139 + 140 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 141 + xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo) 145 142 if err != nil { 146 - log.Println("failed to create unsigned client", err) 143 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 144 + log.Println("failed to call XRPC repo.log", xrpcerr) 145 + rp.pages.Error503(w) 146 + return 147 + } 148 + rp.pages.Error404(w) 147 149 return 148 150 } 149 151 150 - repolog, err := us.Log(f.OwnerDid(), f.Name, ref, page) 151 - if err != nil { 152 + var xrpcResp types.RepoLogResponse 153 + if err := json.Unmarshal(xrpcBytes, &xrpcResp); err != nil { 154 + log.Println("failed to decode XRPC response", err) 152 155 rp.pages.Error503(w) 153 - log.Println("failed to reach knotserver", err) 154 156 return 155 157 } 156 158 157 - tagResult, err := us.Tags(f.OwnerDid(), f.Name) 159 + tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 158 160 if err != nil { 159 - rp.pages.Error503(w) 160 - log.Println("failed to reach knotserver", err) 161 - return 161 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 162 + log.Println("failed to call XRPC repo.tags", xrpcerr) 163 + rp.pages.Error503(w) 164 + return 165 + } 162 166 } 163 167 164 168 tagMap := make(map[string][]string) 165 - for _, tag := range tagResult.Tags { 166 - hash := tag.Hash 167 - if tag.Tag != nil { 168 - hash = tag.Tag.Target.String() 169 + if tagBytes != nil { 170 + var tagResp types.RepoTagsResponse 171 + if err := json.Unmarshal(tagBytes, &tagResp); err == nil { 172 + for _, tag := range tagResp.Tags { 173 + tagMap[tag.Hash] = append(tagMap[tag.Hash], tag.Name) 174 + } 169 175 } 170 - tagMap[hash] = append(tagMap[hash], tag.Name) 171 176 } 172 177 173 - branchResult, err := us.Branches(f.OwnerDid(), f.Name) 178 + branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 174 179 if err != nil { 175 - rp.pages.Error503(w) 176 - log.Println("failed to reach knotserver", err) 177 - return 180 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 181 + log.Println("failed to call XRPC repo.branches", xrpcerr) 182 + rp.pages.Error503(w) 183 + return 184 + } 178 185 } 179 186 180 - for _, branch := range branchResult.Branches { 181 - hash := branch.Hash 182 - tagMap[hash] = append(tagMap[hash], branch.Name) 187 + if branchBytes != nil { 188 + var branchResp types.RepoBranchesResponse 189 + if err := json.Unmarshal(branchBytes, &branchResp); err == nil { 190 + for _, branch := range branchResp.Branches { 191 + tagMap[branch.Hash] = append(tagMap[branch.Hash], branch.Name) 192 + } 193 + } 183 194 } 184 195 185 196 user := rp.oauth.GetUser(r) 186 197 187 - emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(repolog.Commits), true) 198 + emailToDidMap, err := db.GetEmailToDid(rp.db, uniqueEmails(xrpcResp.Commits), true) 188 199 if err != nil { 189 200 log.Println("failed to fetch email to did mapping", err) 190 201 } 191 202 192 - vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, repolog.Commits) 203 + vc, err := commitverify.GetVerifiedObjectCommits(rp.db, emailToDidMap, xrpcResp.Commits) 193 204 if err != nil { 194 205 log.Println(err) 195 206 } ··· 229 176 repoInfo := f.RepoInfo(user) 230 177 231 178 var shas []string 232 - for _, c := range repolog.Commits { 179 + for _, c := range xrpcResp.Commits { 233 180 shas = append(shas, c.Hash.String()) 234 181 } 235 182 pipelines, err := getPipelineStatuses(rp.db, repoInfo, shas) ··· 242 189 LoggedInUser: user, 243 190 TagMap: tagMap, 244 191 RepoInfo: repoInfo, 245 - RepoLogResponse: *repolog, 192 + RepoLogResponse: xrpcResp, 246 193 EmailToDidOrHandle: emailToDidOrHandle(rp, emailToDidMap), 247 194 VerifiedCommits: vc, 248 195 Pipelines: pipelines, ··· 354 301 return 355 302 } 356 303 ref := chi.URLParam(r, "ref") 357 - protocol := "http" 358 - if !rp.config.Core.Dev { 359 - protocol = "https" 360 - } 361 304 362 305 var diffOpts types.DiffOpts 363 306 if d := r.URL.Query().Get("diff"); d == "split" { ··· 365 316 return 366 317 } 367 318 368 - resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/commit/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref)) 369 - if err != nil { 370 - rp.pages.Error503(w) 371 - log.Println("failed to reach knotserver", err) 372 - return 319 + scheme := "http" 320 + if !rp.config.Core.Dev { 321 + scheme = "https" 322 + } 323 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 324 + xrpcc := &indigoxrpc.Client{ 325 + Host: host, 373 326 } 374 327 375 - body, err := io.ReadAll(resp.Body) 328 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 329 + xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo) 376 330 if err != nil { 377 - log.Printf("Error reading response body: %v", err) 331 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 332 + log.Println("failed to call XRPC repo.diff", xrpcerr) 333 + rp.pages.Error503(w) 334 + return 335 + } 336 + rp.pages.Error404(w) 378 337 return 379 338 } 380 339 381 340 var result types.RepoCommitResponse 382 - err = json.Unmarshal(body, &result) 383 - if err != nil { 384 - log.Println("failed to parse response:", err) 341 + if err := json.Unmarshal(xrpcBytes, &result); err != nil { 342 + log.Println("failed to decode XRPC response", err) 343 + rp.pages.Error503(w) 385 344 return 386 345 } 387 346 ··· 435 378 436 379 ref := chi.URLParam(r, "ref") 437 380 treePath := chi.URLParam(r, "*") 438 - protocol := "http" 439 - if !rp.config.Core.Dev { 440 - protocol = "https" 441 - } 442 381 443 382 // if the tree path has a trailing slash, let's strip it 444 383 // so we don't 404 445 384 treePath = strings.TrimSuffix(treePath, "/") 446 385 447 - resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, treePath)) 448 - if err != nil { 449 - rp.pages.Error503(w) 450 - log.Println("failed to reach knotserver", err) 451 - return 386 + scheme := "http" 387 + if !rp.config.Core.Dev { 388 + scheme = "https" 389 + } 390 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 391 + xrpcc := &indigoxrpc.Client{ 392 + Host: host, 452 393 } 453 394 454 - // uhhh so knotserver returns a 500 if the entry isn't found in 455 - // the requested tree path, so let's stick to not-OK here. 456 - // we can fix this once we build out the xrpc apis for these operations. 457 - if resp.StatusCode != http.StatusOK { 395 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 396 + xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo) 397 + if err != nil { 398 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 399 + log.Println("failed to call XRPC repo.tree", xrpcerr) 400 + rp.pages.Error503(w) 401 + return 402 + } 458 403 rp.pages.Error404(w) 459 404 return 460 405 } 461 406 462 - body, err := io.ReadAll(resp.Body) 463 - if err != nil { 464 - log.Printf("Error reading response body: %v", err) 465 - return 407 + // Convert XRPC response to internal types.RepoTreeResponse 408 + files := make([]types.NiceTree, len(xrpcResp.Files)) 409 + for i, xrpcFile := range xrpcResp.Files { 410 + file := types.NiceTree{ 411 + Name: xrpcFile.Name, 412 + Mode: xrpcFile.Mode, 413 + Size: int64(xrpcFile.Size), 414 + IsFile: xrpcFile.Is_file, 415 + IsSubtree: xrpcFile.Is_subtree, 416 + } 417 + 418 + // Convert last commit info if present 419 + if xrpcFile.Last_commit != nil { 420 + commitWhen, _ := time.Parse(time.RFC3339, xrpcFile.Last_commit.When) 421 + file.LastCommit = &types.LastCommitInfo{ 422 + Hash: plumbing.NewHash(xrpcFile.Last_commit.Hash), 423 + Message: xrpcFile.Last_commit.Message, 424 + When: commitWhen, 425 + } 426 + } 427 + 428 + files[i] = file 466 429 } 467 430 468 - var result types.RepoTreeResponse 469 - err = json.Unmarshal(body, &result) 470 - if err != nil { 471 - log.Println("failed to parse response:", err) 472 - return 431 + result := types.RepoTreeResponse{ 432 + Ref: xrpcResp.Ref, 433 + Files: files, 434 + } 435 + 436 + if xrpcResp.Parent != nil { 437 + result.Parent = *xrpcResp.Parent 438 + } 439 + if xrpcResp.Dotdot != nil { 440 + result.DotDot = *xrpcResp.Dotdot 473 441 } 474 442 475 443 // redirects tree paths trying to access a blob; in this case the result.Files is unpopulated, ··· 533 451 return 534 452 } 535 453 536 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 454 + scheme := "http" 455 + if !rp.config.Core.Dev { 456 + scheme = "https" 457 + } 458 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 459 + xrpcc := &indigoxrpc.Client{ 460 + Host: host, 461 + } 462 + 463 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 464 + xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 537 465 if err != nil { 538 - log.Println("failed to create unsigned client", err) 466 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 467 + log.Println("failed to call XRPC repo.tags", xrpcerr) 468 + rp.pages.Error503(w) 469 + return 470 + } 471 + rp.pages.Error404(w) 539 472 return 540 473 } 541 474 542 - result, err := us.Tags(f.OwnerDid(), f.Name) 543 - if err != nil { 475 + var result types.RepoTagsResponse 476 + if err := json.Unmarshal(xrpcBytes, &result); err != nil { 477 + log.Println("failed to decode XRPC response", err) 544 478 rp.pages.Error503(w) 545 - log.Println("failed to reach knotserver", err) 546 479 return 547 480 } 548 481 ··· 593 496 rp.pages.RepoTags(w, pages.RepoTagsParams{ 594 497 LoggedInUser: user, 595 498 RepoInfo: f.RepoInfo(user), 596 - RepoTagsResponse: *result, 499 + RepoTagsResponse: result, 597 500 ArtifactMap: artifactMap, 598 501 DanglingArtifacts: danglingArtifacts, 599 502 }) ··· 606 509 return 607 510 } 608 511 609 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 512 + scheme := "http" 513 + if !rp.config.Core.Dev { 514 + scheme = "https" 515 + } 516 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 517 + xrpcc := &indigoxrpc.Client{ 518 + Host: host, 519 + } 520 + 521 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 522 + xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 610 523 if err != nil { 611 - log.Println("failed to create unsigned client", err) 524 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 525 + log.Println("failed to call XRPC repo.branches", xrpcerr) 526 + rp.pages.Error503(w) 527 + return 528 + } 529 + rp.pages.Error404(w) 612 530 return 613 531 } 614 532 615 - result, err := us.Branches(f.OwnerDid(), f.Name) 616 - if err != nil { 533 + var result types.RepoBranchesResponse 534 + if err := json.Unmarshal(xrpcBytes, &result); err != nil { 535 + log.Println("failed to decode XRPC response", err) 617 536 rp.pages.Error503(w) 618 - log.Println("failed to reach knotserver", err) 619 537 return 620 538 } 621 539 ··· 640 528 rp.pages.RepoBranches(w, pages.RepoBranchesParams{ 641 529 LoggedInUser: user, 642 530 RepoInfo: f.RepoInfo(user), 643 - RepoBranchesResponse: *result, 531 + RepoBranchesResponse: result, 644 532 }) 645 533 } 646 534 ··· 653 541 654 542 ref := chi.URLParam(r, "ref") 655 543 filePath := chi.URLParam(r, "*") 656 - protocol := "http" 544 + 545 + scheme := "http" 657 546 if !rp.config.Core.Dev { 658 - protocol = "https" 547 + scheme = "https" 659 548 } 660 - resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath)) 661 - if err != nil { 662 - rp.pages.Error503(w) 663 - log.Println("failed to reach knotserver", err) 664 - return 549 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 550 + xrpcc := &indigoxrpc.Client{ 551 + Host: host, 665 552 } 666 553 667 - if resp.StatusCode == http.StatusNotFound { 554 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name) 555 + resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, repo) 556 + if err != nil { 557 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 558 + log.Println("failed to call XRPC repo.blob", xrpcerr) 559 + rp.pages.Error503(w) 560 + return 561 + } 668 562 rp.pages.Error404(w) 669 563 return 670 564 } 671 565 672 - body, err := io.ReadAll(resp.Body) 673 - if err != nil { 674 - log.Printf("Error reading response body: %v", err) 675 - return 676 - } 677 - 678 - var result types.RepoBlobResponse 679 - err = json.Unmarshal(body, &result) 680 - if err != nil { 681 - log.Println("failed to parse response:", err) 682 - return 683 - } 566 + // Use XRPC response directly instead of converting to internal types 684 567 685 568 var breadcrumbs [][]string 686 569 breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)}) ··· 688 581 showRendered := false 689 582 renderToggle := false 690 583 691 - if markup.GetFormat(result.Path) == markup.FormatMarkdown { 584 + if markup.GetFormat(resp.Path) == markup.FormatMarkdown { 692 585 renderToggle = true 693 586 showRendered = r.URL.Query().Get("code") != "true" 694 587 } ··· 698 591 var isVideo bool 699 592 var contentSrc string 700 593 701 - if result.IsBinary { 702 - ext := strings.ToLower(filepath.Ext(result.Path)) 594 + if resp.IsBinary != nil && *resp.IsBinary { 595 + ext := strings.ToLower(filepath.Ext(resp.Path)) 703 596 switch ext { 704 597 case ".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp": 705 598 isImage = true ··· 709 602 unsupported = true 710 603 } 711 604 712 - // fetch the actual binary content like in RepoBlobRaw 605 + // fetch the raw binary content using sh.tangled.repo.blob xrpc 606 + repoName := path.Join("%s/%s", f.OwnerDid(), f.Name) 607 + blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true", 608 + scheme, f.Knot, url.QueryEscape(repoName), url.QueryEscape(ref), url.QueryEscape(filePath)) 713 609 714 - blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Name, ref, filePath) 715 610 contentSrc = blobURL 716 611 if !rp.config.Core.Dev { 717 612 contentSrc = markup.GenerateCamoURL(rp.config.Camo.Host, rp.config.Camo.SharedSecret, blobURL) 718 613 } 719 614 } 720 615 616 + lines := 0 617 + if resp.IsBinary == nil || !*resp.IsBinary { 618 + lines = strings.Count(resp.Content, "\n") + 1 619 + } 620 + 621 + var sizeHint uint64 622 + if resp.Size != nil { 623 + sizeHint = uint64(*resp.Size) 624 + } else { 625 + sizeHint = uint64(len(resp.Content)) 626 + } 627 + 721 628 user := rp.oauth.GetUser(r) 629 + 630 + // Determine if content is binary (dereference pointer) 631 + isBinary := false 632 + if resp.IsBinary != nil { 633 + isBinary = *resp.IsBinary 634 + } 635 + 722 636 rp.pages.RepoBlob(w, pages.RepoBlobParams{ 723 - LoggedInUser: user, 724 - RepoInfo: f.RepoInfo(user), 725 - RepoBlobResponse: result, 726 - BreadCrumbs: breadcrumbs, 727 - ShowRendered: showRendered, 728 - RenderToggle: renderToggle, 729 - Unsupported: unsupported, 730 - IsImage: isImage, 731 - IsVideo: isVideo, 732 - ContentSrc: contentSrc, 637 + LoggedInUser: user, 638 + RepoInfo: f.RepoInfo(user), 639 + BreadCrumbs: breadcrumbs, 640 + ShowRendered: showRendered, 641 + RenderToggle: renderToggle, 642 + Unsupported: unsupported, 643 + IsImage: isImage, 644 + IsVideo: isVideo, 645 + ContentSrc: contentSrc, 646 + RepoBlob_Output: resp, 647 + Contents: resp.Content, 648 + Lines: lines, 649 + SizeHint: sizeHint, 650 + IsBinary: isBinary, 733 651 }) 734 652 } 735 653 ··· 769 637 ref := chi.URLParam(r, "ref") 770 638 filePath := chi.URLParam(r, "*") 771 639 772 - protocol := "http" 640 + scheme := "http" 773 641 if !rp.config.Core.Dev { 774 - protocol = "https" 642 + scheme = "https" 775 643 } 776 644 777 - blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath) 645 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name) 646 + blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true", 647 + scheme, f.Knot, url.QueryEscape(repo), url.QueryEscape(ref), url.QueryEscape(filePath)) 778 648 779 649 req, err := http.NewRequest("GET", blobURL, nil) 780 650 if err != nil { ··· 819 685 return 820 686 } 821 687 822 - // Safely serve content based on type 823 688 if strings.HasPrefix(contentType, "text/") || isTextualMimeType(contentType) { 824 - // Serve all textual content as text/plain for security 689 + // serve all textual content as text/plain 825 690 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 826 691 w.Write(body) 827 692 } else if strings.HasPrefix(contentType, "image/") || strings.HasPrefix(contentType, "video/") { 828 - // Serve images and videos with their original content type 693 + // serve images and videos with their original content type 829 694 w.Header().Set("Content-Type", contentType) 830 695 w.Write(body) 831 696 } else { 832 - // Block potentially dangerous content types 833 697 w.WriteHeader(http.StatusUnsupportedMediaType) 834 698 w.Write([]byte("unsupported content type")) 835 699 return ··· 848 716 "message/", 849 717 } 850 718 851 - for _, t := range textualTypes { 852 - if mimeType == t { 853 - return true 854 - } 855 - } 856 - return false 719 + return slices.Contains(textualTypes, mimeType) 857 720 } 858 721 859 722 // modify the spindle configured for this repo ··· 1354 1227 f, err := rp.repoResolver.Resolve(r) 1355 1228 user := rp.oauth.GetUser(r) 1356 1229 1357 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 1230 + scheme := "http" 1231 + if !rp.config.Core.Dev { 1232 + scheme = "https" 1233 + } 1234 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 1235 + xrpcc := &indigoxrpc.Client{ 1236 + Host: host, 1237 + } 1238 + 1239 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 1240 + xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 1358 1241 if err != nil { 1359 - log.Println("failed to create unsigned client", err) 1242 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1243 + log.Println("failed to call XRPC repo.branches", xrpcerr) 1244 + rp.pages.Error503(w) 1245 + return 1246 + } 1247 + rp.pages.Error503(w) 1360 1248 return 1361 1249 } 1362 1250 1363 - result, err := us.Branches(f.OwnerDid(), f.Name) 1364 - if err != nil { 1251 + var result types.RepoBranchesResponse 1252 + if err := json.Unmarshal(xrpcBytes, &result); err != nil { 1253 + log.Println("failed to decode XRPC response", err) 1365 1254 rp.pages.Error503(w) 1366 - log.Println("failed to reach knotserver", err) 1367 1255 return 1368 1256 } 1369 1257 ··· 1749 1607 return 1750 1608 } 1751 1609 1752 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 1610 + scheme := "http" 1611 + if !rp.config.Core.Dev { 1612 + scheme = "https" 1613 + } 1614 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 1615 + xrpcc := &indigoxrpc.Client{ 1616 + Host: host, 1617 + } 1618 + 1619 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 1620 + branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 1753 1621 if err != nil { 1754 - log.Printf("failed to create unsigned client for %s", f.Knot) 1755 - rp.pages.Error503(w) 1622 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1623 + log.Println("failed to call XRPC repo.branches", xrpcerr) 1624 + rp.pages.Error503(w) 1625 + return 1626 + } 1627 + rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1756 1628 return 1757 1629 } 1758 1630 1759 - result, err := us.Branches(f.OwnerDid(), f.Name) 1760 - if err != nil { 1631 + var branchResult types.RepoBranchesResponse 1632 + if err := json.Unmarshal(branchBytes, &branchResult); err != nil { 1633 + log.Println("failed to decode XRPC branches response", err) 1761 1634 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1762 - log.Println("failed to reach knotserver", err) 1763 1635 return 1764 1636 } 1765 - branches := result.Branches 1637 + branches := branchResult.Branches 1766 1638 1767 1639 sortBranches(branches) 1768 1640 ··· 1800 1644 head = queryHead 1801 1645 } 1802 1646 1803 - tags, err := us.Tags(f.OwnerDid(), f.Name) 1647 + tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 1804 1648 if err != nil { 1649 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1650 + log.Println("failed to call XRPC repo.tags", xrpcerr) 1651 + rp.pages.Error503(w) 1652 + return 1653 + } 1805 1654 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1806 - log.Println("failed to reach knotserver", err) 1655 + return 1656 + } 1657 + 1658 + var tags types.RepoTagsResponse 1659 + if err := json.Unmarshal(tagBytes, &tags); err != nil { 1660 + log.Println("failed to decode XRPC tags response", err) 1661 + rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1807 1662 return 1808 1663 } 1809 1664 ··· 1866 1699 return 1867 1700 } 1868 1701 1869 - us, err := knotclient.NewUnsignedClient(f.Knot, rp.config.Core.Dev) 1702 + scheme := "http" 1703 + if !rp.config.Core.Dev { 1704 + scheme = "https" 1705 + } 1706 + host := fmt.Sprintf("%s://%s", scheme, f.Knot) 1707 + xrpcc := &indigoxrpc.Client{ 1708 + Host: host, 1709 + } 1710 + 1711 + repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name) 1712 + 1713 + branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo) 1870 1714 if err != nil { 1871 - log.Printf("failed to create unsigned client for %s", f.Knot) 1872 - rp.pages.Error503(w) 1715 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1716 + log.Println("failed to call XRPC repo.branches", xrpcerr) 1717 + rp.pages.Error503(w) 1718 + return 1719 + } 1720 + rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1873 1721 return 1874 1722 } 1875 1723 1876 - branches, err := us.Branches(f.OwnerDid(), f.Name) 1877 - if err != nil { 1724 + var branches types.RepoBranchesResponse 1725 + if err := json.Unmarshal(branchBytes, &branches); err != nil { 1726 + log.Println("failed to decode XRPC branches response", err) 1878 1727 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1879 - log.Println("failed to reach knotserver", err) 1880 1728 return 1881 1729 } 1882 1730 1883 - tags, err := us.Tags(f.OwnerDid(), f.Name) 1731 + tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo) 1884 1732 if err != nil { 1733 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1734 + log.Println("failed to call XRPC repo.tags", xrpcerr) 1735 + rp.pages.Error503(w) 1736 + return 1737 + } 1885 1738 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1886 - log.Println("failed to reach knotserver", err) 1887 1739 return 1888 1740 } 1889 1741 1890 - formatPatch, err := us.Compare(f.OwnerDid(), f.Name, base, head) 1891 - if err != nil { 1742 + var tags types.RepoTagsResponse 1743 + if err := json.Unmarshal(tagBytes, &tags); err != nil { 1744 + log.Println("failed to decode XRPC tags response", err) 1892 1745 rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1893 - log.Println("failed to compare", err) 1894 1746 return 1895 1747 } 1748 + 1749 + compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head) 1750 + if err != nil { 1751 + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { 1752 + log.Println("failed to call XRPC repo.compare", xrpcerr) 1753 + rp.pages.Error503(w) 1754 + return 1755 + } 1756 + rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1757 + return 1758 + } 1759 + 1760 + var formatPatch types.RepoFormatPatchResponse 1761 + if err := json.Unmarshal(compareBytes, &formatPatch); err != nil { 1762 + log.Println("failed to decode XRPC compare response", err) 1763 + rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.") 1764 + return 1765 + } 1766 + 1896 1767 diff := patchutil.AsNiceDiff(formatPatch.Patch, base) 1897 1768 1898 1769 repoinfo := f.RepoInfo(user)
+1 -1
appview/xrpcclient/xrpc.go
··· 115 115 116 116 var xrpcerr *indigoxrpc.Error 117 117 if ok := errors.As(err, &xrpcerr); !ok { 118 - return fmt.Errorf("Recieved invalid XRPC error response.") 118 + return fmt.Errorf("Recieved invalid XRPC error response: %v", err) 119 119 } 120 120 121 121 switch xrpcerr.StatusCode {