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/aymanbagabas/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}