1package git
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "path/filepath"
8 "strings"
9
10 "github.com/charmbracelet/log"
11 "github.com/charmbracelet/soft-serve/git"
12 "github.com/go-git/go-git/v5/plumbing/format/pktline"
13 gitm "github.com/gogs/git-module"
14)
15
16// WritePktline encodes and writes a pktline to the given writer.
17func WritePktline(w io.Writer, v ...interface{}) error {
18 msg := fmt.Sprintln(v...)
19 pkt := pktline.NewEncoder(w)
20 if err := pkt.EncodeString(msg); err != nil {
21 return fmt.Errorf("git: error writing pkt-line message: %w", err)
22 }
23 if err := pkt.Flush(); err != nil {
24 return fmt.Errorf("git: error flushing pkt-line message: %w", err)
25 }
26
27 return nil
28}
29
30// WritePktlineErr writes an error pktline to the given writer.
31func WritePktlineErr(w io.Writer, err error) error {
32 return WritePktline(w, "ERR", err.Error())
33}
34
35// EnsureWithin ensures the given repo is within the repos directory.
36func EnsureWithin(reposDir string, repo string) error {
37 repoDir := filepath.Join(reposDir, repo)
38 absRepos, err := filepath.Abs(reposDir)
39 if err != nil {
40 log.Debugf("failed to get absolute path for repo: %s", err)
41 return ErrSystemMalfunction
42 }
43 absRepo, err := filepath.Abs(repoDir)
44 if err != nil {
45 log.Debugf("failed to get absolute path for repos: %s", err)
46 return ErrSystemMalfunction
47 }
48
49 // ensure the repo is within the repos directory
50 if !strings.HasPrefix(absRepo, absRepos) {
51 log.Debugf("repo path is outside of repos directory: %s", absRepo)
52 return ErrInvalidRepo
53 }
54
55 return nil
56}
57
58// EnsureDefaultBranch ensures the repo has a default branch.
59// It will prefer choosing "main" or "master" if available.
60func EnsureDefaultBranch(ctx context.Context, scmd ServiceCommand) error {
61 r, err := git.Open(scmd.Dir)
62 if err != nil {
63 return err
64 }
65 brs, err := r.Branches()
66 if err != nil {
67 return err
68 }
69 if len(brs) == 0 {
70 return fmt.Errorf("no branches found")
71 }
72 // Rename the default branch to the first branch available
73 _, err = r.HEAD()
74 if err == git.ErrReferenceNotExist {
75 branch := brs[0]
76 // Prefer "main" or "master" as the default branch
77 for _, b := range brs {
78 if b == "main" || b == "master" {
79 branch = b
80 break
81 }
82 }
83
84 if _, err := r.SymbolicRef(git.HEAD, git.RefsHeads+branch, gitm.SymbolicRefOptions{
85 CommandOptions: gitm.CommandOptions{
86 Context: ctx,
87 },
88 }); err != nil {
89 return err
90 }
91 }
92 if err != nil && err != git.ErrReferenceNotExist {
93 return err
94 }
95 return nil
96}