2024-01-11 20:36:03 +00:00
|
|
|
// 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 {
|
2024-01-11 20:57:28 +00:00
|
|
|
if err := runWithStderr(exec.Command("git", "init", "--bare", cache.Dir)); err != nil {
|
|
|
|
return err
|
2024-01-11 20:36:03 +00:00
|
|
|
}
|
|
|
|
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",
|
|
|
|
"--",
|
2024-01-11 21:27:17 +00:00
|
|
|
url, "*:refs/namespaces/"+namespace+"/*")
|
2024-01-11 20:36:03 +00:00
|
|
|
cmd.Dir = cache.Dir
|
2024-01-11 20:57:28 +00:00
|
|
|
if err := runWithStderr(cmd); err != nil {
|
2024-01-11 20:36:03 +00:00
|
|
|
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
|
2024-01-11 20:57:28 +00:00
|
|
|
out, err := outputWithStderr(cmd)
|
2024-01-11 20:36:03 +00:00
|
|
|
if err != nil {
|
2024-01-11 20:57:28 +00:00
|
|
|
return "", err
|
2024-01-11 20:36:03 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2024-01-11 21:27:17 +00:00
|
|
|
cacheURL := "ext::git --namespace " + url2id(url) + " %s " + cacheDir
|
2024-01-11 20:36:03 +00:00
|
|
|
|
|
|
|
cmd := exec.Command("git", append(append([]string{
|
2024-01-11 21:27:17 +00:00
|
|
|
"-c", "protocol.ext.allow=user",
|
2024-01-11 20:36:03 +00:00
|
|
|
"-c", "url." + cacheURL + ".insteadOf=" + url,
|
|
|
|
"clone",
|
|
|
|
}, flags...), "--", url, dir)...)
|
2024-01-11 20:57:28 +00:00
|
|
|
if err := runWithStderr(cmd); err != nil {
|
|
|
|
return err
|
2024-01-11 20:36:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|