This repository has no description
3.1 kB
113 lines
1package xrpc
2
3import (
4 "fmt"
5 "net/http"
6 "net/url"
7 "os/exec"
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/knotmirror/xrpc/gitea"
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.ParseDID(repoQuery)
27 if err != nil {
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 l.Debug("request")
42
43 ctx := r.Context()
44
45 repoPath, err := x.makeRepoPath(ctx, repo)
46 if err != nil {
47 l.Warn("local mirror failed, trying proxy", "err", err)
48 if x.proxyToKnot(w, r, repo) {
49 return
50 }
51 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to resolve repo"})
52 return
53 }
54
55 rev := ref
56 if rev == "" {
57 rev = "HEAD"
58 }
59 commit, err := gitea.GetCommit(ctx, repoPath, rev)
60
61 repoName, err := func() (string, error) {
62 r, err := db.GetRepoByRepoDid(ctx, x.db, repo)
63 if err != nil {
64 return "", err
65 }
66 if r == nil {
67 return "", fmt.Errorf("repo not found: %s", repo)
68 }
69 return r.Name, nil
70 }()
71 if err != nil {
72 l.Warn("local mirror failed, trying proxy", "err", err)
73 if x.proxyToKnot(w, r, repo) {
74 return
75 }
76 writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to retrieve repo name"})
77 return
78 }
79
80 safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
81 immutableLink := func() string {
82 params := url.Values{}
83 params.Set("repo", repo.String())
84 params.Set("ref", commit.Hash.String())
85 params.Set("format", format)
86 params.Set("prefix", prefix)
87 return fmt.Sprintf("%s/xrpc/%s?%s", x.cfg.BaseUrl(), tangled.GitTempGetArchiveNSID, params.Encode())
88 }()
89
90 filename := fmt.Sprintf("%s-%s.tar.gz", repoName, safeRefFilename)
91 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
92 w.Header().Set("Content-Type", "application/gzip")
93 w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink))
94
95 cmd := exec.Command(
96 "git",
97 "archive",
98 fmt.Sprintf("--prefix=%s", prefix),
99 "--format=tar.gz",
100 commit.Hash.String(),
101 )
102
103 var stderr strings.Builder
104 cmd.Dir = repoPath
105 cmd.Stdout = w
106 cmd.Stderr = &stderr
107
108 if err := cmd.Run(); err != nil {
109 err = fmt.Errorf("%w\n%s", err, stderr.String())
110 l.Error("failed to archive", "err", err)
111 w.WriteHeader(http.StatusInternalServerError)
112 }
113}