A vibe coded tangled fork which supports pijul.
1package xrpc
2
3import (
4 "compress/gzip"
5 "fmt"
6 "net/http"
7 "net/url"
8 "strings"
9
10 "github.com/bluesky-social/indigo/atproto/atclient"
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "github.com/go-git/go-git/v5/plumbing"
13 "tangled.org/core/api/tangled"
14 "tangled.org/core/knotmirror/db"
15 "tangled.org/core/knotserver/git"
16)
17
18func (x *Xrpc) GetArchive(w http.ResponseWriter, r *http.Request) {
19 var (
20 repoQuery = r.URL.Query().Get("repo")
21 ref = r.URL.Query().Get("ref")
22 format = r.URL.Query().Get("format")
23 prefix = r.URL.Query().Get("prefix")
24 )
25
26 repo, err := syntax.ParseATURI(repoQuery)
27 if err != nil || repo.RecordKey() == "" {
28 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: fmt.Sprintf("repo parameter invalid: %s", repoQuery)})
29 return
30 }
31
32 if format != "tar.gz" {
33 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "only tar.gz format is supported"})
34 return
35 }
36 if format == "" {
37 format = "tar.gz"
38 }
39
40 l := x.logger.With("repo", repo, "ref", ref, "format", format, "prefix", prefix)
41 ctx := r.Context()
42
43 repoPath, err := x.makeRepoPath(ctx, repo)
44 if err != nil {
45 l.Warn("local mirror failed, trying proxy", "err", err)
46 if x.proxyToKnot(w, r, repo) {
47 return
48 }
49 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to resolve repo"})
50 return
51 }
52
53 gr, err := git.Open(repoPath, ref)
54 if err != nil {
55 l.Warn("local mirror failed, trying proxy", "err", err)
56 if x.proxyToKnot(w, r, repo) {
57 return
58 }
59 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to open git repo"})
60 return
61 }
62
63 repoName, err := func() (string, error) {
64 r, err := db.GetRepoByAtUri(ctx, x.db, repo)
65 if err != nil {
66 return "", err
67 }
68 if r == nil {
69 return "", fmt.Errorf("repo not found: %s", repo)
70 }
71 return r.Name, nil
72 }()
73 if err != nil {
74 l.Warn("local mirror failed, trying proxy", "err", err)
75 if x.proxyToKnot(w, r, repo) {
76 return
77 }
78 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to retrieve repo name"})
79 return
80 }
81
82 safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
83 immutableLink := func() string {
84 params := url.Values{}
85 params.Set("repo", repo.String())
86 params.Set("ref", gr.Hash().String())
87 params.Set("format", format)
88 params.Set("prefix", prefix)
89 return fmt.Sprintf("%s/xrpc/%s?%s", x.cfg.BaseUrl(), tangled.GitTempGetArchiveNSID, params.Encode())
90 }()
91
92 filename := fmt.Sprintf("%s-%s.tar.gz", repoName, safeRefFilename)
93 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
94 w.Header().Set("Content-Type", "application/gzip")
95 w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink))
96
97 gw := gzip.NewWriter(w)
98 defer gw.Close()
99
100 if err := gr.WriteTar(gw, prefix); err != nil {
101 // once we start writing to the body we can't report error anymore
102 // so we are only left with logging the error
103 l.Error("writing tar file", "err", err.Error())
104 w.WriteHeader(http.StatusInternalServerError)
105 return
106 }
107
108 if err := gw.Flush(); err != nil {
109 // once we start writing to the body we can't report error anymore
110 // so we are only left with logging the error
111 l.Error("flushing", "err", err.Error())
112 w.WriteHeader(http.StatusInternalServerError)
113 return
114 }
115}