1package git23import (4 "context"5 "errors"6 "fmt"7 "io"8 "path/filepath"9 "strings"1011 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)1617// ErrNoBranches is returned when a repo has no branches.18var ErrNoBranches = errors.New("no branches found")1920// 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 }3031 return nil32}3334// 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}3839// 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 ErrSystemMalfunction46 }47 absRepo, err := filepath.Abs(repoDir)48 if err != nil {49 log.Debugf("failed to get absolute path for repos: %s", err)50 return ErrSystemMalfunction51 }5253 // ensure the repo is within the repos directory54 if !strings.HasPrefix(absRepo, absRepos) {55 log.Debugf("repo path is outside of repos directory: %s", absRepo)56 return ErrInvalidRepo57 }5859 return nil60}6162// 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 err68 }69 brs, err := r.Branches()70 if len(brs) == 0 {71 return ErrNoBranches72 }73 if err != nil {74 return err75 }76 // Rename the default branch to the first branch available77 _, err = r.HEAD()78 if err == git.ErrReferenceNotExist {79 branch := brs[0]80 // Prefer "main" or "master" as the default branch81 for _, b := range brs {82 if b == "main" || b == "master" {83 branch = b84 break85 }86 }8788 if _, err := r.SymbolicRef(git.HEAD, git.RefsHeads+branch, gitm.SymbolicRefOptions{89 CommandOptions: gitm.CommandOptions{90 Context: ctx,91 },92 }); err != nil {93 return err94 }95 }96 if err != nil && err != git.ErrReferenceNotExist {97 return err98 }99 return nil100}