From a43a722e799db6c1da22ab7e3b1e99455229b8e3 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 19 Sep 2026 01:50:36 -0600 Subject: [PATCH] feat: expose refresh outcomes --- net/gitshallow/cmd/git-shallow-sync/main.go | 5 ++++- net/gitshallow/forcepush_test.go | 8 ++++---- net/gitshallow/gitshallow.go | 20 +++++++++++++++---- net/gitshallow/gitshallow_integration_test.go | 12 +++++------ net/ippolicy/go.mod | 5 +++++ net/ippolicy/go.sum | 4 ---- net/ippolicy/ipprefix.go | 9 ++++----- net/ippolicy/ipprefix_test.go | 9 +++++---- 8 files changed, 44 insertions(+), 28 deletions(-) diff --git a/net/gitshallow/cmd/git-shallow-sync/main.go b/net/gitshallow/cmd/git-shallow-sync/main.go index 3e0c6d2..5f66fcf 100644 --- a/net/gitshallow/cmd/git-shallow-sync/main.go +++ b/net/gitshallow/cmd/git-shallow-sync/main.go @@ -108,7 +108,10 @@ func main() { os.Exit(1) } state := "already up to date" - if updated { + switch updated { + case gitshallow.UpdateSkipped: + state = "skipped" + case gitshallow.UpdateChanged: state = "updated" } fmt.Fprintf(os.Stderr, "%s (%s)\n", time.Since(t).Round(time.Millisecond), state) diff --git a/net/gitshallow/forcepush_test.go b/net/gitshallow/forcepush_test.go index cf2efec..6a350c4 100644 --- a/net/gitshallow/forcepush_test.go +++ b/net/gitshallow/forcepush_test.go @@ -59,8 +59,8 @@ func TestRepo_ForcePushRecovery(t *testing.T) { repo := gitshallow.New(upstream, clone, 1, "main") if updated, err := repo.Update(t.Context()); err != nil { t.Fatalf("first Update: %v", err) - } else if !updated { - t.Fatal("first Update: expected updated=true") + } else if updated != gitshallow.UpdateChanged { + t.Fatal("first Update: expected changed") } got, err := os.ReadFile(filepath.Join(clone, "data.txt")) if err != nil { @@ -89,8 +89,8 @@ func TestRepo_ForcePushRecovery(t *testing.T) { if err != nil { t.Fatalf("post-force-push Fetch: %v", err) } - if !updated { - t.Error("post-force-push Fetch: expected updated=true") + if updated != gitshallow.UpdateChanged { + t.Error("post-force-push Fetch: expected changed") } got, err = os.ReadFile(filepath.Join(clone, "data.txt")) if err != nil { diff --git a/net/gitshallow/gitshallow.go b/net/gitshallow/gitshallow.go index 2ffbae3..49468e1 100644 --- a/net/gitshallow/gitshallow.go +++ b/net/gitshallow/gitshallow.go @@ -221,18 +221,27 @@ func (r *Repo) gc(ctx context.Context) error { return err } +// UpdateStatus describes what happened during an update. +type UpdateStatus uint8 + +const ( + UpdateSkipped UpdateStatus = iota + UpdateCurrent + UpdateChanged +) + // Fetch clones the repo if missing, pulls otherwise, and conditionally runs // GC based on GCInterval. Returns whether HEAD changed. Implements Upstream. // Safe to call concurrently — concurrent callers share a single in-flight // fetch (via singleflight) and all receive the same result. To force a // pull regardless of MaxAge, set MaxAge=-1 before calling. -func (r *Repo) Update(ctx context.Context) (updated bool, err error) { +func (r *Repo) Update(ctx context.Context) (UpdateStatus, error) { // MaxAge: file-mtime gate (FETCH_HEAD is rewritten on every successful // fetch, so its mtime is "last time we talked to the remote"). if maxAge := r.effectiveMaxAge(); maxAge > 0 { if info, err := os.Stat(filepath.Join(r.Path, ".git", "FETCH_HEAD")); err == nil { if time.Since(info.ModTime()) < maxAge { - return false, nil + return UpdateSkipped, nil } } } @@ -241,9 +250,12 @@ func (r *Repo) Update(ctx context.Context) (updated bool, err error) { return r.fetch(ctx) }) if err != nil { - return false, err + return UpdateCurrent, err } - return v.(bool), nil + if v.(bool) { + return UpdateChanged, nil + } + return UpdateCurrent, nil } func (r *Repo) fetch(ctx context.Context) (bool, error) { diff --git a/net/gitshallow/gitshallow_integration_test.go b/net/gitshallow/gitshallow_integration_test.go index 7084337..a829b91 100644 --- a/net/gitshallow/gitshallow_integration_test.go +++ b/net/gitshallow/gitshallow_integration_test.go @@ -40,8 +40,8 @@ func TestRepo_Clone(t *testing.T) { if err != nil { t.Fatalf("Fetch (clone): %v", err) } - if !updated { - t.Error("fresh clone: expected updated=true") + if updated != gitshallow.UpdateChanged { + t.Error("fresh clone: expected UpdateChanged") } for _, rel := range []string{ @@ -73,8 +73,8 @@ func TestRepo_Pull_SameInstance(t *testing.T) { if err != nil { t.Fatalf("second Fetch: %v", err) } - if updated { - t.Error("same-instance second pull: expected updated=false (already at HEAD)") + if updated == gitshallow.UpdateChanged { + t.Error("same-instance second pull: expected not UpdateChanged (already at HEAD)") } t.Log("same-instance pull correctly reported no update") } @@ -94,8 +94,8 @@ func TestRepo_Pull_FreshInstance(t *testing.T) { if err != nil { t.Fatalf("fresh-instance Fetch: %v", err) } - if updated { - t.Error("fresh-instance pull: expected updated=false (HEAD unchanged on disk)") + if updated == gitshallow.UpdateChanged { + t.Error("fresh-instance pull: expected not UpdateChanged (HEAD unchanged on disk)") } t.Log("fresh-instance pull correctly reported no update") } diff --git a/net/ippolicy/go.mod b/net/ippolicy/go.mod index 090b876..a08f05c 100644 --- a/net/ippolicy/go.mod +++ b/net/ippolicy/go.mod @@ -21,3 +21,8 @@ require ( golang.org/x/sys v0.38.0 // indirect golang.org/x/tools v0.39.0 // indirect ) + +replace ( + github.com/therootcompany/golib/net/gitshallow => ../gitshallow + github.com/therootcompany/golib/sync/cacheable => ../../sync/cacheable +) diff --git a/net/ippolicy/go.sum b/net/ippolicy/go.sum index 77a2322..f852de2 100644 --- a/net/ippolicy/go.sum +++ b/net/ippolicy/go.sum @@ -8,16 +8,12 @@ github.com/therootcompany/golib/io/transform/gsheet2csv v1.1.0 h1:R5zAPun5p3JPlN github.com/therootcompany/golib/io/transform/gsheet2csv v1.1.0/go.mod h1:CsntOAhhIMdzSAYuwuRZtfkdoiER9+TfSgYOIDWN71o= github.com/therootcompany/golib/net/dnsresolver v0.6.0 h1:sy08rzX/2QowofAKnCw1QtTDoatPL6/FXZcb+Wz4GlM= github.com/therootcompany/golib/net/dnsresolver v0.6.0/go.mod h1:tOV99lX6ghpWPksxSFAt4CDe5XLefFzIGf2qXsZJap4= -github.com/therootcompany/golib/net/gitshallow v0.10.0 h1:cLN9ElTmNWk5HSbaRD3sSFuGdpgy4YXc+LVzjzT0nM4= -github.com/therootcompany/golib/net/gitshallow v0.10.0/go.mod h1:S/3OK7wMOqJJjP79U9Qw/l3jjEyrb/zsjJ8HAPAX/Fc= github.com/therootcompany/golib/net/httpcache v0.6.0 h1:qFBnSs6rZrtUQ+fweP4bHF2FQd/Syvs7mRN5nO5YtkY= github.com/therootcompany/golib/net/httpcache v0.6.0/go.mod h1:mKSQJWpGSCDBzWkTCwT7dO1SCVCsFmdkRgKr39fNS1U= github.com/therootcompany/golib/net/ipcohort v0.9.0 h1:j3AaoW2u4XQJz1ya6fixv3pb3KgxIpZV8t0082/kS3E= github.com/therootcompany/golib/net/ipcohort v0.9.0/go.mod h1:Ljcnt2xOryVDGIVV5Kzt5hj2LhW4lBAWo5OeeVYU9ys= github.com/therootcompany/golib/net/iplist v0.9.2 h1:2WGFYzLhDyeGRuagLPe6Hp6gWMNqfqWpei9KG41C/9E= github.com/therootcompany/golib/net/iplist v0.9.2/go.mod h1:TJ+0RP4M8/zkjdm+D9YXvDUJL2VZiC2VpnZJiK9Bfjk= -github.com/therootcompany/golib/sync/cacheable v0.1.0 h1:s6mz4dZv9Vsd0nuDz8IpS+n35GLjOwpjZlomimkqw1M= -github.com/therootcompany/golib/sync/cacheable v0.1.0/go.mod h1:0ZQ9sOtqyr1j8ZfSaAyPM1HlGi1B2qFgaGC4ULUbhD4= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= diff --git a/net/ippolicy/ipprefix.go b/net/ippolicy/ipprefix.go index d8fa93c..4438c01 100644 --- a/net/ippolicy/ipprefix.go +++ b/net/ippolicy/ipprefix.go @@ -216,7 +216,7 @@ func (ps *IPPrefixSet) Contains(addr netip.Addr) bool { } func (ps *IPPrefixSet) reload(ctx context.Context) (*ipcohort.Cohort, error) { - updated, err := ps.repo.Update(ctx) + status, err := ps.repo.Update(ctx) if err != nil { return nil, err } @@ -226,7 +226,7 @@ func (ps *IPPrefixSet) reload(ctx context.Context) (*ipcohort.Cohort, error) { paths[i] = ps.repo.FilePath(f) } - if cachedCohortValid(updated, ps.cohort.Load(), paths) { + if cachedCohortValid(status, ps.cohort.Load(), paths) { return nil, nil } @@ -235,15 +235,14 @@ func (ps *IPPrefixSet) reload(ctx context.Context) (*ipcohort.Cohort, error) { return nil, fmt.Errorf("load files: %w", err) } - log().Info("prefix set loaded", "entries", commaify(cohort.Size())) return cohort, nil } // cachedCohortValid reports whether the current cohort can be reused // without reloading from disk: the repo was not updated, the cohort is // non-nil and non-empty, and all data files are present. -func cachedCohortValid(updated bool, cohort *ipcohort.Cohort, paths []string) bool { - return !updated && cohort != nil && cohort.Size() > 0 && filesPresent(paths) +func cachedCohortValid(status gitshallow.UpdateStatus, cohort *ipcohort.Cohort, paths []string) bool { + return status != gitshallow.UpdateChanged && cohort != nil && cohort.Size() > 0 && filesPresent(paths) } func filesPresent(paths []string) bool { diff --git a/net/ippolicy/ipprefix_test.go b/net/ippolicy/ipprefix_test.go index 00ea155..53f2915 100644 --- a/net/ippolicy/ipprefix_test.go +++ b/net/ippolicy/ipprefix_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + "github.com/therootcompany/golib/net/gitshallow" "github.com/therootcompany/golib/net/ipcohort" ) @@ -32,16 +33,16 @@ func TestCachedCohortValidRequiresFiles(t *testing.T) { t.Fatal(err) } - if !cachedCohortValid(false, cohort, []string{present}) { + if !cachedCohortValid(gitshallow.UpdateCurrent, cohort, []string{present}) { t.Fatal("fresh metadata and present files should use the cache") } - if cachedCohortValid(false, cohort, []string{present, filepath.Join(dir, "gone")}) { + if cachedCohortValid(gitshallow.UpdateCurrent, cohort, []string{present, filepath.Join(dir, "gone")}) { t.Fatal("missing data file must invalidate the cache") } - if cachedCohortValid(false, nil, []string{present}) { + if cachedCohortValid(gitshallow.UpdateCurrent, nil, []string{present}) { t.Fatal("nil cohort must not be used") } - if cachedCohortValid(true, cohort, []string{present}) { + if cachedCohortValid(gitshallow.UpdateChanged, cohort, []string{present}) { t.Fatal("updated repo must reload") } }