From 5e6688c2a9adfdfd1348634cc535eafc0a9dc36f Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Apr 2026 17:28:53 -0600 Subject: [PATCH] feat(gitshallow): add MaxAge gate via FETCH_HEAD mtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Short-lived CLI invocations were doing a full git fetch+reset on every run because the only debounce was an in-memory lastSynced field. MaxAge skips the fetch when .git/FETCH_HEAD is younger than the configured duration — git rewrites FETCH_HEAD on every successful fetch, so its mtime is effectively "last time we talked to the remote", and it survives process restart. Wire check-ip's blocklist repo to the same 47m refresh interval it uses for the background Tick. --- cmd/check-ip/main.go | 1 + net/gitshallow/gitshallow.go | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cmd/check-ip/main.go b/cmd/check-ip/main.go index 7adf74a..0e3fa32 100644 --- a/cmd/check-ip/main.go +++ b/cmd/check-ip/main.go @@ -120,6 +120,7 @@ func main() { // Blocklists: git repo with inbound + outbound IP cohort files. repo := gitshallow.New(cfg.RepoURL, filepath.Join(cfg.CacheDir, "bitwire-it"), 1, "") + repo.MaxAge = refreshInterval blocklists := dataset.NewSet(repo) cfg.inbound = dataset.Add(blocklists, func() (*ipcohort.Cohort, error) { return ipcohort.LoadFiles( diff --git a/net/gitshallow/gitshallow.go b/net/gitshallow/gitshallow.go index 0dc3385..54b33ad 100644 --- a/net/gitshallow/gitshallow.go +++ b/net/gitshallow/gitshallow.go @@ -17,15 +17,21 @@ type Repo struct { Depth int // 0 defaults to 1, -1 for all Branch string // Optional: specific branch to clone/pull + // MaxAge skips the git fetch when .git/FETCH_HEAD is younger than this + // duration. Persists across process restarts (unlike the in-memory + // lastSynced debounce) — so repeated short-lived CLI invocations don't + // hammer the remote. 0 disables. + MaxAge time.Duration + // 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 - lastSynced time.Time + mu sync.Mutex + pullCount int + lastSynced time.Time } // New creates a new Repo instance. @@ -238,6 +244,17 @@ func (f *File) Fetch() (bool, error) { } func (r *Repo) syncGit() (updated bool, err error) { + // MaxAge: file-mtime gate (FETCH_HEAD is rewritten on every successful + // fetch, so its mtime is "last time we talked to the remote"). Checked + // outside the lock — just a stat. + if r.MaxAge > 0 { + if info, err := os.Stat(filepath.Join(r.Path, ".git", "FETCH_HEAD")); err == nil { + if time.Since(info.ModTime()) < r.MaxAge { + return false, nil + } + } + } + r.mu.Lock() defer r.mu.Unlock()