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
 72// EnsureDefaultBranch ensures the repo has a default branch.
 73// It will prefer choosing "main" or "master" if available.
 74func EnsureDefaultBranch(ctx context.Context, scmd ServiceCommand) error {
 75	r, err := git.Open(scmd.Dir)
 76	if err != nil {
 77		return err
 78	}
 79	brs, err := r.Branches()
 80	if err != nil {
 81		return err
 82	}
 83	if len(brs) == 0 {
 84		return fmt.Errorf("no branches found")
 85	}
 86	// Rename the default branch to the first branch available
 87	_, err = r.HEAD()
 88	if err == git.ErrReferenceNotExist {
 89		branch := brs[0]
 90		// Prefer "main" or "master" as the default branch
 91		for _, b := range brs {
 92			if b == "main" || b == "master" {
 93				branch = b
 94				break
 95			}
 96		}
 97
 98		cmd := git.NewCommand("branch", "-M", branch).WithContext(ctx)
 99		if err := cmd.RunInDirWithOptions(scmd.Dir, git.RunInDirOptions{
100			Stdin:  scmd.Stdin,
101			Stdout: scmd.Stdout,
102			Stderr: scmd.Stderr,
103		}); err != nil {
104			return err
105		}
106	}
107	if err != nil && err != git.ErrReferenceNotExist {
108		return err
109	}
110	return nil
111}