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