// Copyright (C) 2024 Umorpha Systems // SPDX-License-Identifier: AGPL-3.0-or-later package gitcache import ( "errors" "os" "os/exec" "path/filepath" "sync" "time" ) const tsDir = "x-gitcache-ts" type Cache struct { Dir string MinPeriod time.Duration initOnce sync.Once initErr error } // "ll" stands for "low level" ///////////////////////////////////////////////// func (cache *Cache) llInit() error { if err := runWithStderr(exec.Command("git", "init", "--bare", cache.Dir)); err != nil { return err } if err := os.Mkdir(filepath.Join(cache.Dir, tsDir), 0o777); err != nil && !errors.Is(err, os.ErrExist) { return err } return nil } func (cache *Cache) llFetch(namespace, url string) error { cmd := exec.Command("git", "fetch", "--no-write-fetch-head", "--no-recurse-submodules", "--no-tags", "--prune", "--", url, "*:refs/namespaces/"+namespace+"/*") cmd.Dir = cache.Dir if err := runWithStderr(cmd); err != nil { return err } return nil } //////////////////////////////////////////////////////////////////////////////// func (cache *Cache) init() error { cache.initOnce.Do(func() { cache.initErr = cache.llInit() }) return cache.initErr } func (cache *Cache) Fetch(url string) error { if err := cache.init(); err != nil { return err } id := url2id(url) tsFile := filepath.Join(cache.Dir, tsDir, id) if cache.MinPeriod != 0 { ts, err := mtime(tsFile) if err == nil && ts.Add(cache.MinPeriod).After(time.Now()) { return nil } } if err := cache.llFetch(id, url); err != nil { return err } return touch(tsFile) } func (cache *Cache) RevParse(url, rev string) (string, error) { if err := cache.Fetch(url); err != nil { return "", err } cmd := exec.Command("git", "--namespace="+url2id(url), "rev-parse", "--verify", rev) cmd.Dir = cache.Dir out, err := outputWithStderr(cmd) if err != nil { return "", err } return string(out[:len(out)-1]), nil } func (cache *Cache) Clone(url, dir string, flags ...string) error { if err := cache.Fetch(url); err != nil { return err } cacheDir, err := filepath.Abs(cache.Dir) if err != nil { return err } cacheURL := "ext::git --namespace " + url2id(url) + " %s " + cacheDir cmd := exec.Command("git", append(append([]string{ "-c", "protocol.ext.allow=user", "-c", "url." + cacheURL + ".insteadOf=" + url, "clone", }, flags...), "--", url, dir)...) if err := runWithStderr(cmd); err != nil { return err } return nil }