refactor: GCInterval replaces LightGC; Sync/Init drop lightGC param

gitshallow.Repo.GCInterval int:
  0 (default) = git auto gc (no explicit call)
  N = aggressive gc + prune every Nth successful pull

GC() simplified to always aggressive+prune (the only mode we use).
Sync(), Init(), Fetch() all parameter-free; GCInterval baked into Repo.
This commit is contained in:
AJ ONeal 2026-04-20 09:24:51 -06:00
parent 10c4b6dbc3
commit 7c0cd26da1
No known key found for this signature in database
3 changed files with 36 additions and 42 deletions

View File

@ -57,7 +57,7 @@ func main() {
repo := gitshallow.New(url, absPath, defaultDepth, defaultBranch)
updated, err := repo.Sync(false)
updated, err := repo.Sync()
if err != nil {
fmt.Fprintf(os.Stderr, "Sync failed: %v\n", err)
os.Exit(1)

View File

@ -15,9 +15,15 @@ type Repo struct {
Path string
Depth int // 0 defaults to 1, -1 for all
Branch string // Optional: specific branch to clone/pull
LightGC bool // true = skip aggressive GC; false (default) = aggressive+prune
// GCInterval controls explicit aggressive GC after pulls.
// 0 (default) — no explicit gc; git runs gc.auto on its own schedule
// 1 — aggressive gc after every pull
// N — aggressive gc after every Nth pull
GCInterval int
mu sync.Mutex
pullCount int
}
// New creates a new Repo instance.
@ -35,23 +41,20 @@ func New(url, path string, depth int, branch string) *Repo {
// Init clones the repo if missing, then syncs once.
// Returns whether anything new was fetched.
func (r *Repo) Init(lightGC bool) (bool, error) {
func (r *Repo) Init() (bool, error) {
gitDir := filepath.Join(r.Path, ".git")
if _, err := os.Stat(gitDir); err != nil {
if _, err := r.Clone(); err != nil {
return false, err
}
}
updated, err := r.syncGit(lightGC)
return updated, err
return r.syncGit()
}
// Clone performs a shallow clone (--depth N --single-branch --no-tags).
func (r *Repo) Clone() (bool, error) {
r.mu.Lock()
defer r.mu.Unlock()
return r.clone()
}
@ -59,7 +62,6 @@ func (r *Repo) clone() (bool, error) {
if r.exists() {
return false, nil
}
if r.URL == "" {
return false, fmt.Errorf("repository URL is required")
}
@ -93,18 +95,15 @@ func (r *Repo) exists() bool {
// runGit executes a git command in the repo directory (or parent for clone).
func (r *Repo) runGit(args ...string) (string, error) {
cmd := exec.Command("git", args...)
if _, err := os.Stat(r.Path); err == nil && r.exists() {
cmd.Dir = r.Path
} else {
cmd.Dir = filepath.Dir(r.Path)
}
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("git %s failed: %v\n%s", strings.Join(args, " "), err, output)
}
return strings.TrimSpace(string(output)), nil
}
@ -112,7 +111,6 @@ func (r *Repo) runGit(args ...string) (string, error) {
func (r *Repo) Pull() (updated bool, err error) {
r.mu.Lock()
defer r.mu.Unlock()
return r.pull()
}
@ -144,47 +142,36 @@ func (r *Repo) pull() (updated bool, err error) {
if err != nil {
return false, err
}
return oldHead != newHead, nil
}
// GC runs git gc. aggressiveGC adds --aggressive; pruneNow adds --prune=now.
func (r *Repo) GC(aggressiveGC, pruneNow bool) error {
// GC runs git gc --aggressive --prune=now.
func (r *Repo) GC() error {
r.mu.Lock()
defer r.mu.Unlock()
return r.gc(aggressiveGC, pruneNow)
return r.gc()
}
func (r *Repo) gc(aggressiveGC, pruneNow bool) error {
func (r *Repo) gc() error {
if !r.exists() {
return fmt.Errorf("repository does not exist at %s", r.Path)
}
args := []string{"gc"}
if aggressiveGC {
args = append(args, "--aggressive")
}
if pruneNow {
args = append(args, "--prune=now")
}
_, err := r.runGit(args...)
_, err := r.runGit("gc", "--aggressive", "--prune=now")
return err
}
// Sync clones if missing, pulls, and runs GC. Returns whether HEAD changed.
// lightGC=false runs aggressive GC with --prune=now to minimize disk use.
func (r *Repo) Sync(lightGC bool) (bool, error) {
return r.syncGit(lightGC)
// Sync clones if missing, pulls, and conditionally runs GC based on GCEvery.
// Returns whether HEAD changed.
func (r *Repo) Sync() (bool, error) {
return r.syncGit()
}
// Fetch satisfies httpcache.Syncer using the Repo's LightGC setting.
// Fetch satisfies httpcache.Syncer.
func (r *Repo) Fetch() (bool, error) {
return r.syncGit(r.LightGC)
return r.syncGit()
}
func (r *Repo) syncGit(lightGC bool) (updated bool, err error) {
func (r *Repo) syncGit() (updated bool, err error) {
r.mu.Lock()
defer r.mu.Unlock()
@ -199,5 +186,12 @@ func (r *Repo) syncGit(lightGC bool) (updated bool, err error) {
return updated, err
}
return true, r.gc(!lightGC, !lightGC)
if r.GCInterval > 0 {
r.pullCount++
if r.pullCount%r.GCInterval == 0 {
return true, r.gc()
}
}
return true, nil
}

View File

@ -82,7 +82,7 @@ func (s *Sources) Fetch() (bool, error) {
// For HTTP: fetches each cacher unconditionally on first run.
func (s *Sources) Init() error {
if s.gitRepo != nil {
_, err := s.gitRepo.Init(s.gitRepo.LightGC)
_, err := s.gitRepo.Init()
return err
}
for _, syn := range s.syncs {