1package git
2
3import (
4 "fmt"
5 "io"
6 "log"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "github.com/charmbracelet/soft-serve/git"
12 "github.com/go-git/go-git/v5/plumbing/format/pktline"
13)
14
15// GitPack runs the git pack protocol against the provided repo.
16func GitPack(out io.Writer, in io.Reader, er io.Writer, gitCmd string, repoDir string, repo string) error {
17 cmd := strings.TrimPrefix(gitCmd, "git-")
18 rp := filepath.Join(repoDir, repo)
19 switch gitCmd {
20 case "git-upload-archive", "git-upload-pack":
21 exists, err := fileExists(rp)
22 if !exists {
23 return ErrInvalidRepo
24 }
25 if err != nil {
26 return err
27 }
28 return RunGit(out, in, er, "", cmd, rp)
29 case "git-receive-pack":
30 err := ensureRepo(repoDir, repo)
31 if err != nil {
32 return err
33 }
34 err = RunGit(out, in, er, "", cmd, rp)
35 if err != nil {
36 return err
37 }
38 err = ensureDefaultBranch(out, in, er, rp)
39 if err != nil {
40 return err
41 }
42 // Needed for git dumb http server
43 return RunGit(out, in, er, rp, "update-server-info")
44 default:
45 return fmt.Errorf("unknown git command: %s", gitCmd)
46 }
47}
48
49// RunGit runs a git command in the given repo.
50func RunGit(out io.Writer, in io.Reader, err io.Writer, dir string, args ...string) error {
51 c := git.NewCommand(args...)
52 return c.RunInDirWithOptions(dir, git.RunInDirOptions{
53 Stdout: out,
54 Stdin: in,
55 Stderr: err,
56 })
57}
58
59// WritePktline encodes and writes a pktline to the given writer.
60func WritePktline(w io.Writer, v ...interface{}) {
61 msg := fmt.Sprint(v...)
62 pkt := pktline.NewEncoder(w)
63 if err := pkt.EncodeString(msg); err != nil {
64 log.Printf("git: error writing pkt-line message: %s", err)
65 }
66 if err := pkt.Flush(); err != nil {
67 log.Printf("git: error flushing pkt-line message: %s", err)
68 }
69}
70
71func fileExists(path string) (bool, error) {
72 _, err := os.Stat(path)
73 if err == nil {
74 return true, nil
75 }
76 if os.IsNotExist(err) {
77 return false, nil
78 }
79 return true, err
80}
81
82func ensureRepo(dir string, repo string) error {
83 exists, err := fileExists(dir)
84 if err != nil {
85 return err
86 }
87 if !exists {
88 err = os.MkdirAll(dir, os.ModeDir|os.FileMode(0700))
89 if err != nil {
90 return err
91 }
92 }
93 rp := filepath.Join(dir, repo)
94 exists, err = fileExists(rp)
95 if err != nil {
96 return err
97 }
98 if !exists {
99 _, err := git.Init(rp, true)
100 if err != nil {
101 return err
102 }
103 }
104 return nil
105}
106
107func ensureDefaultBranch(out io.Writer, in io.Reader, er io.Writer, repoPath string) error {
108 r, err := git.Open(repoPath)
109 if err != nil {
110 return err
111 }
112 brs, err := r.Branches()
113 if err != nil {
114 return err
115 }
116 if len(brs) == 0 {
117 return fmt.Errorf("no branches found")
118 }
119 // Rename the default branch to the first branch available
120 _, err = r.HEAD()
121 if err == git.ErrReferenceNotExist {
122 err = RunGit(out, in, er, repoPath, "branch", "-M", brs[0])
123 if err != nil {
124 return err
125 }
126 }
127 if err != nil && err != git.ErrReferenceNotExist {
128 return err
129 }
130 return nil
131}