1package xrpc
2
3import (
4 "compress/gzip"
5 "fmt"
6 "net/http"
7 "strings"
8
9 "github.com/go-git/go-git/v5/plumbing"
10
11 "tangled.sh/tangled.sh/core/knotserver/git"
12 xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors"
13)
14
15func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) {
16 repo, repoPath, unescapedRef, err := x.parseStandardParams(r)
17 if err != nil {
18 writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
19 return
20 }
21
22 format := r.URL.Query().Get("format")
23 if format == "" {
24 format = "tar.gz" // default
25 }
26
27 prefix := r.URL.Query().Get("prefix")
28
29 if format != "tar.gz" {
30 writeError(w, xrpcerr.NewXrpcError(
31 xrpcerr.WithTag("InvalidRequest"),
32 xrpcerr.WithMessage("only tar.gz format is supported"),
33 ), http.StatusBadRequest)
34 return
35 }
36
37 gr, err := git.Open(repoPath, unescapedRef)
38 if err != nil {
39 writeError(w, xrpcerr.NewXrpcError(
40 xrpcerr.WithTag("RefNotFound"),
41 xrpcerr.WithMessage("repository or ref not found"),
42 ), http.StatusNotFound)
43 return
44 }
45
46 repoParts := strings.Split(repo, "/")
47 repoName := repoParts[len(repoParts)-1]
48
49 safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-")
50
51 var archivePrefix string
52 if prefix != "" {
53 archivePrefix = prefix
54 } else {
55 archivePrefix = fmt.Sprintf("%s-%s", repoName, safeRefFilename)
56 }
57
58 filename := fmt.Sprintf("%s-%s.tar.gz", repoName, safeRefFilename)
59 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
60 w.Header().Set("Content-Type", "application/gzip")
61
62 gw := gzip.NewWriter(w)
63 defer gw.Close()
64
65 err = gr.WriteTar(gw, archivePrefix)
66 if err != nil {
67 // once we start writing to the body we can't report error anymore
68 // so we are only left with logging the error
69 x.Logger.Error("writing tar file", "error", err.Error())
70 return
71 }
72
73 err = gw.Flush()
74 if err != nil {
75 // once we start writing to the body we can't report error anymore
76 // so we are only left with logging the error
77 x.Logger.Error("flushing", "error", err.Error())
78 return
79 }
80}