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