1package git
  2
  3import (
  4	"fmt"
  5	"io"
  6	"log"
  7	"os"
  8	"path/filepath"
  9	"strings"
 10
 11	"github.com/charmbracelet/soft-serve/git"
 12	"github.com/go-git/go-git/v5/plumbing/format/pktline"
 13)
 14
 15// GitPack runs the git pack protocol against the provided repo.
 16func GitPack(out io.Writer, in io.Reader, er io.Writer, gitCmd string, repoDir string, repo string) error {
 17	cmd := strings.TrimPrefix(gitCmd, "git-")
 18	rp := filepath.Join(repoDir, repo)
 19	switch gitCmd {
 20	case "git-upload-archive", "git-upload-pack":
 21		exists, err := fileExists(rp)
 22		if !exists {
 23			return ErrInvalidRepo
 24		}
 25		if err != nil {
 26			return err
 27		}
 28		return RunGit(out, in, er, "", cmd, rp)
 29	case "git-receive-pack":
 30		err := ensureRepo(repoDir, repo)
 31		if err != nil {
 32			return err
 33		}
 34		err = RunGit(out, in, er, "", cmd, rp)
 35		if err != nil {
 36			return err
 37		}
 38		err = ensureDefaultBranch(out, in, er, rp)
 39		if err != nil {
 40			return err
 41		}
 42		// Needed for git dumb http server
 43		return RunGit(out, in, er, rp, "update-server-info")
 44	default:
 45		return fmt.Errorf("unknown git command: %s", gitCmd)
 46	}
 47}
 48
 49// RunGit runs a git command in the given repo.
 50func RunGit(out io.Writer, in io.Reader, err io.Writer, dir string, args ...string) error {
 51	c := git.NewCommand(args...)
 52	return c.RunInDirWithOptions(dir, git.RunInDirOptions{
 53		Stdout: out,
 54		Stdin:  in,
 55		Stderr: err,
 56	})
 57}
 58
 59// WritePktline encodes and writes a pktline to the given writer.
 60func WritePktline(w io.Writer, v ...interface{}) {
 61	msg := fmt.Sprint(v...)
 62	pkt := pktline.NewEncoder(w)
 63	if err := pkt.EncodeString(msg); err != nil {
 64		log.Printf("git: error writing pkt-line message: %s", err)
 65	}
 66	if err := pkt.Flush(); err != nil {
 67		log.Printf("git: error flushing pkt-line message: %s", err)
 68	}
 69}
 70
 71func fileExists(path string) (bool, error) {
 72	_, err := os.Stat(path)
 73	if err == nil {
 74		return true, nil
 75	}
 76	if os.IsNotExist(err) {
 77		return false, nil
 78	}
 79	return true, err
 80}
 81
 82func ensureRepo(dir string, repo string) error {
 83	exists, err := fileExists(dir)
 84	if err != nil {
 85		return err
 86	}
 87	if !exists {
 88		err = os.MkdirAll(dir, os.ModeDir|os.FileMode(0700))
 89		if err != nil {
 90			return err
 91		}
 92	}
 93	rp := filepath.Join(dir, repo)
 94	exists, err = fileExists(rp)
 95	if err != nil {
 96		return err
 97	}
 98	if !exists {
 99		_, err := git.Init(rp, true)
100		if err != nil {
101			return err
102		}
103	}
104	return nil
105}
106
107func ensureDefaultBranch(out io.Writer, in io.Reader, er io.Writer, repoPath string) error {
108	r, err := git.Open(repoPath)
109	if err != nil {
110		return err
111	}
112	brs, err := r.Branches()
113	if err != nil {
114		return err
115	}
116	if len(brs) == 0 {
117		return fmt.Errorf("no branches found")
118	}
119	// Rename the default branch to the first branch available
120	_, err = r.HEAD()
121	if err == git.ErrReferenceNotExist {
122		err = RunGit(out, in, er, repoPath, "branch", "-M", brs[0])
123		if err != nil {
124			return err
125		}
126	}
127	if err != nil && err != git.ErrReferenceNotExist {
128		return err
129	}
130	return nil
131}