From fbec1a84f66da865b3aef3b94e53c15b666cc431 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Fri, 20 Feb 2026 14:30:17 +1100 Subject: [PATCH] feat: integrate git maintenance for mirror repos (#125) Register each mirror for git maintenance with the incremental strategy after clone and on startup discovery. Adds a Maintenance config flag (default false) to gate the behavior. Co-Authored-By: Claude Opus 4.6 --- internal/gitclone/manager.go | 41 ++++++++++++++++++++++++++++++++++++ state/go.mod | 0 2 files changed, 41 insertions(+) create mode 100644 state/go.mod diff --git a/internal/gitclone/manager.go b/internal/gitclone/manager.go index 4f4c41d..543648b 100644 --- a/internal/gitclone/manager.go +++ b/internal/gitclone/manager.go @@ -56,6 +56,7 @@ type Config struct { MirrorRoot string `hcl:"mirror-root" help:"Directory to store git clones."` FetchInterval time.Duration `hcl:"fetch-interval,optional" help:"How often to fetch from upstream in minutes." default:"15m"` RefCheckInterval time.Duration `hcl:"ref-check-interval,optional" help:"How long to cache ref checks." default:"10s"` + Maintenance bool `hcl:"maintenance,optional" help:"Enable git maintenance scheduling for mirror repos." default:"false"` } // CredentialProvider provides credentials for git operations. @@ -120,6 +121,12 @@ func NewManager(ctx context.Context, config Config, credentialProvider Credentia return nil, errors.Wrap(err, "create root directory") } + if config.Maintenance { + if err := startMaintenance(ctx); err != nil { + logging.FromContext(ctx).WarnContext(ctx, "Failed to start git maintenance scheduler", "error", err) + } + } + logging.FromContext(ctx).InfoContext(ctx, "Git clone manager initialised", "mirror_root", config.MirrorRoot, "fetch_interval", config.FetchInterval, @@ -230,6 +237,12 @@ func (m *Manager) DiscoverExisting(ctx context.Context) ([]*Repository, error) { return errors.Wrapf(err, "configure mirror for %s", upstreamURL) } + if m.config.Maintenance { + if err := registerMaintenance(ctx, path); err != nil { + return errors.Wrapf(err, "register maintenance for %s", upstreamURL) + } + } + m.clonesMu.Lock() m.clones[upstreamURL] = repo m.clonesMu.Unlock() @@ -348,6 +361,28 @@ func mirrorConfigSettings() [][2]string { } } +func registerMaintenance(ctx context.Context, repoPath string) error { + // #nosec G204 - repoPath is controlled by us + cmd := exec.CommandContext(ctx, "git", "-C", repoPath, "config", "maintenance.strategy", "incremental") + if output, err := cmd.CombinedOutput(); err != nil { + return errors.Wrapf(err, "set maintenance.strategy: %s", string(output)) + } + // #nosec G204 - repoPath is controlled by us + cmd = exec.CommandContext(ctx, "git", "-C", repoPath, "maintenance", "register") + if output, err := cmd.CombinedOutput(); err != nil { + return errors.Wrapf(err, "maintenance register: %s", string(output)) + } + return nil +} + +func startMaintenance(ctx context.Context) error { + cmd := exec.CommandContext(ctx, "git", "maintenance", "start") + if output, err := cmd.CombinedOutput(); err != nil { + return errors.Wrapf(err, "maintenance start: %s", string(output)) + } + return nil +} + func configureMirror(ctx context.Context, repoPath string) error { for _, kv := range mirrorConfigSettings() { // #nosec G204 - repoPath and config values are controlled by us @@ -388,6 +423,12 @@ func (r *Repository) executeClone(ctx context.Context) error { return errors.Wrap(err, "configure mirror") } + if r.config.Maintenance { + if err := registerMaintenance(ctx, r.path); err != nil { + return errors.Wrap(err, "register maintenance") + } + } + return nil } diff --git a/state/go.mod b/state/go.mod new file mode 100644 index 0000000..e69de29