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 {