Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Go
at sl/gitmirror
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179package gitutil
import ( "context" "fmt" "os" "path/filepath" "strconv" "strings" "sync"
"github.com/hashicorp/go-version")
// don't let two requests mutate the same checkout at oncevar repoLocks keyedMutex
type keyedMutex struct { mu sync.Mutex m map[string]*sync.Mutex}
func (k *keyedMutex) lock(key string) func() { k.mu.Lock() if k.m == nil { k.m = make(map[string]*sync.Mutex) } mu, ok := k.m[key] if !ok { mu = &sync.Mutex{} k.m[key] = mu } k.mu.Unlock()
mu.Lock() return mu.Unlock}
func Version() (*version.Version, error) { out, err := Output(context.Background(), "version") if err != nil { return nil, err } fields := strings.Fields(string(out)) if len(fields) < 3 { return nil, fmt.Errorf("invalid git version: %s", out) }
// git version 2.29.3.windows.1 versionString := fields[2] if pos := strings.Index(versionString, "windows"); pos >= 1 { versionString = versionString[:pos-1] } return version.NewVersion(versionString)}
// SparseSync updates a sparse checkout, fetching it first if needed// this should only fail if the repo is completely empty!func SparseSync(ctx context.Context, cloneUri, path, rev string, sparse ...string) error { defer repoLocks.lock(path)()
exist, err := isDir(path) if err != nil { return err } if exist { gitDirExist, err := isDir(path + "/.git") if err != nil { return err } if !gitDirExist { if err := os.RemoveAll(path); err != nil { return fmt.Errorf("cleanup invalid git dir: %w", err) } exist = false } } if rev == "" { rev = "HEAD" } setup := func() error { if err := os.MkdirAll(path, 0o755); err != nil { return fmt.Errorf("mkdir repo dir: %w", err) } if err := Run(ctx, "init", "-q", path); err != nil { return fmt.Errorf("git init: %w", err) } if err := Run(ctx, "-C", path, "remote", "add", "origin", cloneUri); err != nil { return fmt.Errorf("git remote add: %w", err) } if err := Run(ctx, append([]string{"-C", path, "sparse-checkout", "set", "--no-cone"}, sparse...)...); err != nil { return fmt.Errorf("git sparse-checkout set: %w", err) } return nil } fetch := func() error { return Run(ctx, "-C", path, "fetch", "--depth=1", "--filter=tree:0", "origin", rev) }
if !exist { if err := setup(); err != nil { return err } if err := fetch(); err != nil { os.RemoveAll(path) return fmt.Errorf("git fetch: %w", err) } } else { if err := fetch(); err != nil { // a cancelled fetch can leave lock files behind removeStaleLocks(path) if retryErr := fetch(); retryErr != nil { if rmErr := os.RemoveAll(path); rmErr != nil { return fmt.Errorf("git fetch: %w (cleanup failed: %v)", retryErr, rmErr) } if setupErr := setup(); setupErr != nil { return fmt.Errorf("git fetch: %w (re-init failed: %v)", retryErr, setupErr) } if cloneErr := fetch(); cloneErr != nil { return fmt.Errorf("git fetch: %w (re-fetch failed: %v)", retryErr, cloneErr) } } } } removeStaleLocks(path) if err := Run(ctx, "-C", path, "checkout", "--detach", "FETCH_HEAD"); err != nil { return fmt.Errorf("git checkout: %w", err) } return nil}
const maxAncestryDepth = 100
// reports whether ref's recent history includes sha// this doesnt use SparseSync bcz that is shallow and we need the partially-fully-graphfunc RefContainsCommit(ctx context.Context, cloneUri, ref, sha string) (bool, error) { path, err := os.MkdirTemp("", "ref-ancestry-") if err != nil { return false, fmt.Errorf("making a scratch dir: %w", err) } defer os.RemoveAll(path)
if err := Run(ctx, "init", path); err != nil { return false, fmt.Errorf("git init: %w", err) } if err := Run(ctx, "-C", path, "remote", "add", "origin", cloneUri); err != nil { return false, fmt.Errorf("git remote add: %w", err) } if err := Run(ctx, "-C", path, "fetch", "--filter=tree:0", "--depth="+strconv.Itoa(maxAncestryDepth), "origin", ref); err != nil { return false, fmt.Errorf("git fetch %s: %w", ref, err) }
// this exits 0 when the ref's history contains it // a sha outside the fetched window fails the command out, err := Output(ctx, "-C", path, "merge-base", sha, "FETCH_HEAD") if err != nil { return false, nil } return strings.TrimSpace(string(out)) == sha, nil}
func removeStaleLocks(path string) { locks, _ := filepath.Glob(filepath.Join(path, ".git", "*.lock")) for _, lock := range locks { os.Remove(lock) }}
func isDir(path string) (bool, error) { info, err := os.Stat(path) if err == nil && info.IsDir() { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err}