internal/gitutil/gitutil.go

a0dd5878fbda93a49fe7d4503e2ac80fc6f6c8f3
gitbay/internal/gitutil/gitutil.go history · blame · raw

102 lines · 3109 bytes

  1// Package gitutil wraps the system git binary. All repository access goes
  2// through git subprocesses; there is no in-process git implementation.
  3package gitutil
  4
  5import (
  6	"fmt"
  7	"io"
  8	"os"
  9	"os/exec"
 10	"path/filepath"
 11	"strings"
 12)
 13
 14// InitBare creates a bare repository with the shared hooks directory wired
 15// via core.hooksPath.
 16func InitBare(path, defaultBranch, hooksPath string) error {
 17	if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
 18		return err
 19	}
 20	cmd := exec.Command("git", "init", "--bare", "--initial-branch="+defaultBranch, path)
 21	if out, err := cmd.CombinedOutput(); err != nil {
 22		return fmt.Errorf("git init: %v\n%s", err, out)
 23	}
 24	cmd = exec.Command("git", "-C", path, "config", "core.hooksPath", hooksPath)
 25	if out, err := cmd.CombinedOutput(); err != nil {
 26		return fmt.Errorf("git config core.hooksPath: %v\n%s", err, out)
 27	}
 28	return nil
 29}
 30
 31// Transport streams one git transport service (upload-pack, receive-pack,
 32// upload-archive) over rw. extraEnv entries are appended to the daemon's
 33// environment; hooks read the FORGE_* variables from it.
 34func Transport(service, repoPath string, rw io.ReadWriter, errW io.Writer, extraEnv []string) error {
 35	var args []string
 36	switch service {
 37	case "git-upload-pack", "git-receive-pack", "git-upload-archive":
 38		args = []string{strings.TrimPrefix(service, "git-"), repoPath}
 39	default:
 40		return fmt.Errorf("unknown service %q", service)
 41	}
 42	cmd := exec.Command("git", args...)
 43	cmd.Env = append(os.Environ(), extraEnv...)
 44	cmd.Stdin = rw
 45	cmd.Stdout = rw
 46	cmd.Stderr = errW
 47	return cmd.Run()
 48}
 49
 50// IsAncestor reports whether old is an ancestor of new in the repository at
 51// dir. It must run with the caller's environment intact so that quarantined
 52// objects during pre-receive remain visible.
 53func IsAncestor(dir, old, new string) (bool, error) {
 54	cmd := exec.Command("git", "-C", dir, "merge-base", "--is-ancestor", old, new)
 55	err := cmd.Run()
 56	if err == nil {
 57		return true, nil
 58	}
 59	if ee, ok := err.(*exec.ExitError); ok && ee.ExitCode() == 1 {
 60		return false, nil
 61	}
 62	return false, err
 63}
 64
 65// ZeroSHA reports whether s is an all-zero object id (SHA-1 or SHA-256).
 66func ZeroSHA(s string) bool {
 67	if len(s) != 40 && len(s) != 64 {
 68		return false
 69	}
 70	for i := 0; i < len(s); i++ {
 71		if s[i] != '0' {
 72			return false
 73		}
 74	}
 75	return true
 76}
 77
 78// RevList returns up to limit commit SHAs reachable from ref, newest first.
 79func RevList(dir, ref string, limit int) ([]string, error) {
 80	cmd := exec.Command("git", "-C", dir, "rev-list", fmt.Sprintf("--max-count=%d", limit), ref)
 81	out, err := cmd.Output()
 82	if err != nil {
 83		return nil, fmt.Errorf("rev-list %s: %w", ref, err)
 84	}
 85	var shas []string
 86	for _, l := range strings.Split(strings.TrimSpace(string(out)), "\n") {
 87		if l != "" {
 88			shas = append(shas, l)
 89		}
 90	}
 91	return shas, nil
 92}
 93
 94// ReadCommit returns the raw commit object bytes.
 95func ReadCommit(dir, sha string) ([]byte, error) {
 96	cmd := exec.Command("git", "-C", dir, "cat-file", "commit", sha)
 97	out, err := cmd.Output()
 98	if err != nil {
 99		return nil, fmt.Errorf("cat-file commit %s: %w", sha, err)
100	}
101	return out, nil
102}