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