Monorepo for Tangled
at 8817c3ca119d07abef6871d2bd3d967e92e9bf20 116 lines 2.9 kB view raw
1package xrpc 2 3import ( 4 "compress/gzip" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strings" 9 10 "github.com/go-git/go-git/v5/plumbing" 11 12 "tangled.org/core/api/tangled" 13 "tangled.org/core/knotserver/git" 14 xrpcerr "tangled.org/core/xrpc/errors" 15) 16 17func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) { 18 repo := r.URL.Query().Get("repo") 19 repoPath, err := x.parseRepoParam(repo) 20 if err != nil { 21 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest) 22 return 23 } 24 25 ref := r.URL.Query().Get("ref") 26 // ref can be empty (git.Open handles this) 27 28 format := r.URL.Query().Get("format") 29 if format == "" { 30 format = "tar.gz" // default 31 } 32 33 prefix := r.URL.Query().Get("prefix") 34 35 if format != "tar.gz" { 36 writeError(w, xrpcerr.NewXrpcError( 37 xrpcerr.WithTag("InvalidRequest"), 38 xrpcerr.WithMessage("only tar.gz format is supported"), 39 ), http.StatusBadRequest) 40 return 41 } 42 43 gr, err := git.Open(repoPath, ref) 44 if err != nil { 45 writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound) 46 return 47 } 48 49 repoParts := strings.Split(repo, "/") 50 repoName := repoParts[len(repoParts)-1] 51 52 immutableLink, err := x.buildImmutableLink(repo, format, gr.Hash().String(), prefix) 53 if err != nil { 54 x.Logger.Error( 55 "failed to build immutable link", 56 "err", err.Error(), 57 "repo", repo, 58 "format", format, 59 "ref", gr.Hash().String(), 60 "prefix", prefix, 61 ) 62 } 63 64 safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-") 65 66 var archivePrefix string 67 if prefix != "" { 68 archivePrefix = prefix 69 } else { 70 archivePrefix = fmt.Sprintf("%s-%s", repoName, safeRefFilename) 71 } 72 73 filename := fmt.Sprintf("%s-%s.tar.gz", repoName, safeRefFilename) 74 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) 75 w.Header().Set("Content-Type", "application/gzip") 76 w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink)) 77 78 gw := gzip.NewWriter(w) 79 defer gw.Close() 80 81 err = gr.WriteTar(gw, archivePrefix) 82 if err != nil { 83 // once we start writing to the body we can't report error anymore 84 // so we are only left with logging the error 85 x.Logger.Error("writing tar file", "error", err.Error()) 86 return 87 } 88 89 err = gw.Flush() 90 if err != nil { 91 // once we start writing to the body we can't report error anymore 92 // so we are only left with logging the error 93 x.Logger.Error("flushing", "error", err.Error()) 94 return 95 } 96} 97 98func (x *Xrpc) buildImmutableLink(repo string, format string, ref string, prefix string) (string, error) { 99 scheme := "https" 100 if x.Config.Server.Dev { 101 scheme = "http" 102 } 103 104 u, err := url.Parse(scheme + "://" + x.Config.Server.Hostname + "/xrpc/" + tangled.RepoArchiveNSID) 105 if err != nil { 106 return "", err 107 } 108 109 params := url.Values{} 110 params.Set("repo", repo) 111 params.Set("format", format) 112 params.Set("ref", ref) 113 params.Set("prefix", prefix) 114 115 return fmt.Sprintf("%s?%s", u.String(), params.Encode()), nil 116}