Monorepo for Tangled tangled.org
5

Configure Feed

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

1package xrpc 2 3import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "net/http" 9 "net/url" 10 "os/exec" 11 "strings" 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" 16 "tangled.org/core/api/tangled" 17 "tangled.org/core/knotmirror/db" 18 "tangled.org/core/knotmirror/xrpc/gitea" 19) 20 21func (x *Xrpc) GetArchive(w http.ResponseWriter, r *http.Request) { 22 var ( 23 repoQuery = r.URL.Query().Get("repo") 24 ref = r.URL.Query().Get("ref") 25 format = r.URL.Query().Get("format") 26 prefix = r.URL.Query().Get("prefix") 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 if format == "" { 36 format = "tar.gz" 37 } 38 if format != "tar.gz" && format != "zip" { 39 writeJson(w, http.StatusBadRequest, atclient.ErrorBody{Name: "BadRequest", Message: "only tar.gz and zip formats are supported"}) 40 return 41 } 42 43 l := x.logger.With("repo", repo, "ref", ref, "format", format, "prefix", prefix) 44 l.Debug("request") 45 46 ctx := r.Context() 47 48 repoPath, err := x.makeRepoPath(ctx, repo) 49 if err != nil { 50 l.Warn("local mirror failed, trying proxy", "err", err) 51 if x.proxyToKnot(w, r, repo) { 52 return 53 } 54 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to resolve repo"}) 55 return 56 } 57 58 rev := ref 59 if rev == "" { 60 rev = "HEAD" 61 } 62 commit, err := gitea.GetCommit(ctx, repoPath, rev) 63 if err != nil { 64 l.Warn("local mirror failed, trying proxy", "err", err) 65 if x.proxyToKnot(w, r, repo) { 66 return 67 } 68 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to resolve ref"}) 69 return 70 } 71 72 repoName, err := func() (string, error) { 73 r, err := db.GetRepoByRepoDid(ctx, x.db, repo) 74 if err != nil { 75 return "", err 76 } 77 if r == nil { 78 return "", fmt.Errorf("repo not found: %s", repo) 79 } 80 return r.Name, nil 81 }() 82 if err != nil { 83 l.Warn("local mirror failed, trying proxy", "err", err) 84 if x.proxyToKnot(w, r, repo) { 85 return 86 } 87 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to retrieve repo name"}) 88 return 89 } 90 91 safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 92 if safeRefFilename == "" { 93 safeRefFilename = commit.Hash.String() 94 } 95 immutableLink := func() string { 96 params := url.Values{} 97 params.Set("repo", repo.String()) 98 params.Set("ref", commit.Hash.String()) 99 params.Set("format", format) 100 params.Set("prefix", prefix) 101 return fmt.Sprintf("%s/xrpc/%s?%s", x.cfg.BaseUrl(), tangled.GitTempGetArchiveNSID, params.Encode()) 102 }() 103 104 var archivePrefix string 105 if prefix != "" { 106 archivePrefix = prefix 107 } else { 108 archivePrefix = fmt.Sprintf("%s-%s", repoName, safeRefFilename) 109 } 110 111 filename := fmt.Sprintf("%s-%s.%s", repoName, safeRefFilename, format) 112 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 113 w.Header().Set("Content-Type", archiveContentType(format)) 114 w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink)) 115 116 if err := writeLocalArchive(ctx, w, repoPath, commit.Hash.String(), format, archivePrefix); err != nil { 117 l.Error("writing archive", "err", err.Error(), "format", format) 118 w.WriteHeader(http.StatusInternalServerError) 119 } 120} 121 122func archiveContentType(format string) string { 123 if format == "zip" { 124 return "application/zip" 125 } 126 return "application/gzip" 127} 128 129func writeLocalArchive(ctx context.Context, w io.Writer, repoPath, rev, format, prefix string) error { 130 args := []string{"-C", repoPath, "archive", "--format=" + format} 131 if prefix != "" { 132 args = append(args, "--prefix="+strings.TrimRight(prefix, "/")+"/") 133 } 134 args = append(args, rev) 135 136 cmd := exec.CommandContext(ctx, "git", args...) 137 cmd.Stdout = w 138 stderr := new(bytes.Buffer) 139 cmd.Stderr = stderr 140 141 if err := cmd.Run(); err != nil { 142 return fmt.Errorf("%w, stderr: %s", err, stderr.String()) 143 } 144 return nil 145}