git.go

  1package git
  2
  3import (
  4	"context"
  5	"errors"
  6	"fmt"
  7	"io"
  8	"path/filepath"
  9	"strings"
 10
 11	"github.com/charmbracelet/log"
 12	"github.com/charmbracelet/soft-serve/git"
 13	"github.com/go-git/go-git/v5/plumbing/format/pktline"
 14)
 15
 16var (
 17
 18	// ErrNotAuthed represents unauthorized access.
 19	ErrNotAuthed = errors.New("you are not authorized to do this")
 20
 21	// ErrSystemMalfunction represents a general system error returned to clients.
 22	ErrSystemMalfunction = errors.New("something went wrong")
 23
 24	// ErrInvalidRepo represents an attempt to access a non-existent repo.
 25	ErrInvalidRepo = errors.New("invalid repo")
 26
 27	// ErrInvalidRequest represents an invalid request.
 28	ErrInvalidRequest = errors.New("invalid request")
 29
 30	// ErrMaxConnections represents a maximum connection limit being reached.
 31	ErrMaxConnections = errors.New("too many connections, try again later")
 32
 33	// ErrTimeout is returned when the maximum read timeout is exceeded.
 34	ErrTimeout = errors.New("I/O timeout reached")
 35)
 36
 37// WritePktline encodes and writes a pktline to the given writer.
 38func WritePktline(w io.Writer, v ...interface{}) {
 39	msg := fmt.Sprintln(v...)
 40	pkt := pktline.NewEncoder(w)
 41	if err := pkt.EncodeString(msg); err != nil {
 42		log.Debugf("git: error writing pkt-line message: %s", err)
 43	}
 44	if err := pkt.Flush(); err != nil {
 45		log.Debugf("git: error flushing pkt-line message: %s", err)
 46	}
 47}
 48
 49// EnsureWithin ensures the given repo is within the repos directory.
 50func EnsureWithin(reposDir string, repo string) error {
 51	repoDir := filepath.Join(reposDir, repo)
 52	absRepos, err := filepath.Abs(reposDir)
 53	if err != nil {
 54		log.Debugf("failed to get absolute path for repo: %s", err)
 55		return ErrSystemMalfunction
 56	}
 57	absRepo, err := filepath.Abs(repoDir)
 58	if err != nil {
 59		log.Debugf("failed to get absolute path for repos: %s", err)
 60		return ErrSystemMalfunction
 61	}
 62
 63	// ensure the repo is within the repos directory
 64	if !strings.HasPrefix(absRepo, absRepos) {
 65		log.Debugf("repo path is outside of repos directory: %s", absRepo)
 66		return ErrInvalidRepo
 67	}
 68
 69	return nil
 70}
 71
 72func EnsureDefaultBranch(ctx context.Context, scmd ServiceCommand) error {
 73	r, err := git.Open(scmd.Dir)
 74	if err != nil {
 75		return err
 76	}
 77	brs, err := r.Branches()
 78	if err != nil {
 79		return err
 80	}
 81	if len(brs) == 0 {
 82		return fmt.Errorf("no branches found")
 83	}
 84	// Rename the default branch to the first branch available
 85	_, err = r.HEAD()
 86	if err == git.ErrReferenceNotExist {
 87		branch := brs[0]
 88		// Prefer "main" or "master" as the default branch
 89		for _, b := range brs {
 90			if b == "main" || b == "master" {
 91				branch = b
 92				break
 93			}
 94		}
 95
 96		cmd := git.NewCommand("branch", "-M", branch).WithContext(ctx)
 97		if err := cmd.RunInDirWithOptions(scmd.Dir, git.RunInDirOptions{
 98			Stdin:  scmd.Stdin,
 99			Stdout: scmd.Stdout,
100			Stderr: scmd.Stderr,
101		}); err != nil {
102			return err
103		}
104	}
105	if err != nil && err != git.ErrReferenceNotExist {
106		return err
107	}
108	return nil
109}