Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132package gitutil
import ( "context" "fmt" "os" "path/filepath" "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, cloning it first if neededfunc 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" } clone := func() error { if err := Run(ctx, "clone", "--no-checkout", "--depth=1", "--filter=tree:0", "--revision="+rev, cloneUri, path); err != nil { return fmt.Errorf("git clone: %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 } if !exist { if err := clone(); err != nil { return err } } else { fetch := func() error { return Run(ctx, "-C", path, "fetch", "--depth=1", "--filter=tree:0", "origin", rev) } 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 cloneErr := clone(); cloneErr != nil { return fmt.Errorf("git fetch: %w (re-clone failed: %v)", retryErr, cloneErr) } } } } if err := Run(ctx, "-C", path, "checkout", rev); err != nil { return fmt.Errorf("git checkout: %w", err) } return 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}