eclipse/lib/gitcache/gitcache.go

153 lines
3.6 KiB
Go
Raw Normal View History

2024-01-11 20:36:03 +00:00
// Copyright (C) 2024 Umorpha Systems
// SPDX-License-Identifier: AGPL-3.0-or-later
package gitcache
import (
2024-01-12 19:50:38 +00:00
"fmt"
"io"
2024-01-11 20:36:03 +00:00
"os"
"os/exec"
"path/filepath"
"sync"
"time"
)
2024-01-12 19:50:38 +00:00
const (
tsDir = "x-gitcache-ts"
tmpDir = "x-gitcache-tmp"
)
2024-01-11 20:36:03 +00:00
type Cache struct {
Dir string
MinPeriod time.Duration
initOnce sync.Once
initErr error
}
// "ll" stands for "low level" /////////////////////////////////////////////////
2024-01-11 21:46:13 +00:00
func (cache *Cache) llInit(stderr io.Writer) error {
2024-01-12 19:06:32 +00:00
fmt.Fprintf(stderr, "[gitcache] Initializing cache...\n")
2024-01-11 21:46:13 +00:00
cmd := exec.Command("git", "init", "--bare", cache.Dir)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
2024-01-11 20:57:28 +00:00
return err
2024-01-11 20:36:03 +00:00
}
2024-01-12 19:50:38 +00:00
if err := mkdirAllowExisting(filepath.Join(cache.Dir, tsDir)); err != nil {
2024-01-11 20:36:03 +00:00
return err
}
return nil
}
2024-01-11 21:46:13 +00:00
func (cache *Cache) llFetch(stderr io.Writer, namespace, url string) error {
2024-01-12 19:06:32 +00:00
fmt.Fprintf(stderr, "[gitcache] Fetching %q...\n", url)
2024-01-11 20:36:03 +00:00
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 21:46:13 +00:00
cmd.Stderr = stderr
return cmd.Run()
2024-01-11 20:36:03 +00:00
}
////////////////////////////////////////////////////////////////////////////////
2024-01-11 21:46:13 +00:00
func (cache *Cache) init(stderr io.Writer) error {
2024-01-11 20:36:03 +00:00
cache.initOnce.Do(func() {
2024-01-11 21:46:13 +00:00
cache.initErr = cache.llInit(stderr)
2024-01-11 20:36:03 +00:00
})
2024-01-12 19:50:38 +00:00
if err := mkdirAllowExisting(filepath.Join(cache.Dir, tmpDir)); err != nil {
return err
}
2024-01-11 20:36:03 +00:00
return cache.initErr
}
2024-01-11 21:46:13 +00:00
func (cache *Cache) Fetch(stderr io.Writer, url string) error {
if err := cache.init(stderr); err != nil {
2024-01-11 20:36:03 +00:00
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
}
}
2024-01-11 21:46:13 +00:00
if err := cache.llFetch(stderr, id, url); err != nil {
2024-01-11 20:36:03 +00:00
return err
}
return touch(tsFile)
}
2024-01-11 21:46:13 +00:00
func (cache *Cache) Clone(stderr io.Writer, url, dir string, flags ...string) error {
if err := cache.Fetch(stderr, url); err != nil {
2024-01-11 20:36:03 +00:00
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
2024-01-12 19:06:32 +00:00
fmt.Fprintf(stderr, "[gitcache] Cloning %q to %q...\n", url, dir)
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 21:46:13 +00:00
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
2024-01-11 20:57:28 +00:00
return err
2024-01-11 20:36:03 +00:00
}
return nil
}
2024-01-11 21:46:13 +00:00
func (cache *Cache) RevParse(stderr io.Writer, url, rev string) (string, error) {
if err := cache.Fetch(stderr, url); err != nil {
return "", err
}
// `git rev-parse` doesn't obey `--namespace`, so we have to
// create a temporary clone to do this :(
2024-01-12 19:50:38 +00:00
//
// Don't use .Clone(), so we can use hardlinks to reduce the
// cost of this.
fmt.Fprintf(stderr, "[gitcache] Creating temporary view of %q...\n", url)
tmpdir, err := os.MkdirTemp(filepath.Join(cache.Dir, tmpDir), "*.git")
2024-01-11 21:46:13 +00:00
if err != nil {
return "", err
}
defer os.RemoveAll(tmpdir)
2024-01-12 19:50:38 +00:00
cmd := exec.Command("git", "--namespace="+url2id(url), "clone", "--mirror", cache.Dir, tmpdir)
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
2024-01-11 21:46:13 +00:00
return "", err
}
2024-01-12 19:50:38 +00:00
cmd = exec.Command("git", "rev-parse", "--verify", rev)
cmd.Dir = tmpdir
2024-01-11 21:46:13 +00:00
cmd.Stderr = stderr
out, err := cmd.Output()
if err != nil {
return "", err
}
return string(out[:len(out)-1]), nil
}
2024-01-12 19:50:52 +00:00
func (cache *Cache) GC(stderr io.Writer, flags ...string) error {
fmt.Fprintf(stderr, "[gitcache] Collecting garbage...\n")
cmd := exec.Command("git", append([]string{"gc"}, flags...)...)
cmd.Dir = cache.Dir
cmd.Stderr = stderr
return cmd.Run()
}