Monorepo for Tangled tangled.org
4

Configure Feed

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

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