git.go

  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	log "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
 68	}
 69	brs, err := r.Branches()
 70	if len(brs) == 0 {
 71		return ErrNoBranches
 72	}
 73	if err != nil {
 74		return err
 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
 94		}
 95	}
 96	if err != nil && err != git.ErrReferenceNotExist {
 97		return err
 98	}
 99	return nil
100}