From a406ca2d5a3b081bbfef2600823c18fef680ab57 Mon Sep 17 00:00:00 2001 From: ThanhNguyxn Date: Sun, 1 Feb 2026 11:19:43 +0700 Subject: [PATCH] fix(store): add proper GC with Handler and interval gating Address maintainer feedback on PR #1239: - Add Handler: repo.DeleteObject to prevent nil panic in Prune - Handle ErrLooseObjectsNotSupported gracefully - Add 5-minute interval gating to avoid repack overhead on every write - Remove sirupsen/logrus dependency (best-effort silent GC) Fixes #1104 --- internal/store/gitstore.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/internal/store/gitstore.go b/internal/store/gitstore.go index 3b68e4b0..c8db660c 100644 --- a/internal/store/gitstore.go +++ b/internal/store/gitstore.go @@ -21,6 +21,9 @@ import ( cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" ) +// gcInterval defines minimum time between garbage collection runs. +const gcInterval = 5 * time.Minute + // GitTokenStore persists token records and auth metadata using git as the backing storage. type GitTokenStore struct { mu sync.Mutex @@ -31,6 +34,7 @@ type GitTokenStore struct { remote string username string password string + lastGC time.Time } // NewGitTokenStore creates a token store that saves credentials to disk through the @@ -613,6 +617,7 @@ func (s *GitTokenStore) commitAndPushLocked(message string, relPaths ...string) } else if errRewrite := s.rewriteHeadAsSingleCommit(repo, headRef.Name(), commitHash, message, signature); errRewrite != nil { return errRewrite } + s.maybeRunGC(repo) if err = repo.Push(&git.PushOptions{Auth: s.gitAuth(), Force: true}); err != nil { if errors.Is(err, git.NoErrAlreadyUpToDate) { return nil @@ -652,6 +657,23 @@ func (s *GitTokenStore) rewriteHeadAsSingleCommit(repo *git.Repository, branch p return nil } +func (s *GitTokenStore) maybeRunGC(repo *git.Repository) { + now := time.Now() + if now.Sub(s.lastGC) < gcInterval { + return + } + s.lastGC = now + + pruneOpts := git.PruneOptions{ + OnlyObjectsOlderThan: now, + Handler: repo.DeleteObject, + } + if err := repo.Prune(pruneOpts); err != nil && !errors.Is(err, git.ErrLooseObjectsNotSupported) { + return + } + _ = repo.RepackObjects(&git.RepackConfig{}) +} + // PersistConfig commits and pushes configuration changes to git. func (s *GitTokenStore) PersistConfig(_ context.Context) error { if err := s.EnsureRepository(); err != nil {