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}