From 757c14388a7267f6065e08d2dd64db2c959464ad Mon Sep 17 00:00:00 2001 From: Mohamed-elg Date: Thu, 11 Jun 2026 22:21:10 +0200 Subject: [PATCH 1/3] pre hook --- main.go | 73 ++++++++++++++++++++++++++++++++++++++- pkg/hook/exechook.go | 7 ++-- pkg/hook/exechook_test.go | 3 ++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 99fdf840a..ffce3431a 100644 --- a/main.go +++ b/main.go @@ -230,6 +230,16 @@ func main() { envDuration(3*time.Second, "GITSYNC_EXECHOOK_BACKOFF", "GIT_SYNC_EXECHOOK_BACKOFF"), "the time to wait before retrying a failed exechook") + flPreExechookCommand := pflag.String("pre-exechook-command", + envString("", "GITSYNC_PRE_EXECHOOK_COMMAND", "GIT_SYNC_PRE_EXECHOOK_COMMAND"), + "an optional command to be run before syncs complete (must be idempotent)") + flPreExechookTimeout := pflag.Duration("pre-exechook-timeout", + envDuration(30*time.Second, "GITSYNC_PRE_EXECHOOK_TIMEOUT", "GIT_SYNC_PRE_EXECHOOK_TIMEOUT"), + "the timeout for the pre-exechook") + flPreExechookBackoff := pflag.Duration("pre-exechook-backoff", + envDuration(3*time.Second, "GITSYNC_PRE_EXECHOOK_BACKOFF", "GIT_SYNC_PRE_EXECHOOK_BACKOFF"), + "the time to wait before retrying a failed pre-exechook") + flWebhookURL := pflag.String("webhook-url", envString("", "GITSYNC_WEBHOOK_URL", "GIT_SYNC_WEBHOOK_URL"), "a URL for optional webhook notifications when syncs complete (must be idempotent)") @@ -541,6 +551,15 @@ func main() { } } + if *flPreExechookCommand != "" { + if *flPreExechookTimeout < time.Second { + fatalConfigErrorf(log, true, "invalid flag: --pre-exechook-timeout must be at least 1s") + } + if *flPreExechookBackoff < time.Second { + fatalConfigErrorf(log, true, "invalid flag: --pre-exechook-backoff must be at least 1s") + } + } + if *flWebhookURL != "" { if *flWebhookStatusSuccess == -1 { // Back-compat: -1 and 0 mean the same things @@ -859,8 +878,10 @@ func main() { // Startup exechooks goroutine var exechookRunner *hook.HookRunner if *flExechookCommand != "" { - log := log.WithName("exechook") + logname := "exechook" + log := log.WithName(logname) exechook := hook.NewExechook( + logname, cmd.NewRunner(log), *flExechookCommand, func(hash string) string { @@ -880,6 +901,32 @@ func main() { go exechookRunner.Run(context.Background()) } + // Startup pre-exechooks goroutine + var preExechookRunner *hook.HookRunner + if *flPreExechookCommand != "" { + logname := "pre-exechook" + log := log.WithName(logname) + exechook := hook.NewExechook( + logname, + cmd.NewRunner(log), + *flPreExechookCommand, + func(hash string) string { + return git.worktreeFor(hash).Path().String() + }, + []string{}, + *flPreExechookTimeout, + log, + ) + preExechookRunner = hook.NewHookRunner( + exechook, + *flPreExechookBackoff, + hook.NewHookData(), + log, + *flOneTime, + ) + go preExechookRunner.Run(context.Background()) + } + // Setup signal notify channel sigChan := make(chan os.Signal, 1) if syncSig != 0 { @@ -947,6 +994,11 @@ func main() { for { start := time.Now() ctx, cancel := context.WithTimeout(context.Background(), *flSyncTimeout) + prevHash := "" + + if preExechookRunner != nil { + preExechookRunner.Send(prevHash) + } if changed, hash, err := git.SyncRepo(ctx, refreshCreds); err != nil { failCount++ @@ -981,6 +1033,9 @@ func main() { if exechookRunner != nil { exechookRunner.Send(hash) } + if preExechookRunner != nil { + prevHash = hash + } updateSyncMetrics(metricKeySuccess, start) } else { updateSyncMetrics(metricKeyNoOp, start) @@ -2528,6 +2583,22 @@ OPTIONS The timeout for the --exechook-command. If not specifid, this defaults to 30 seconds ("30s"). + --pre-exechook-backoff , $GITSYNC_PRE_EXECHOOK_BACKOFF + The time to wait before retrying a failed --pre-exechook-command. If + not specified, this defaults to 3 seconds ("3s"). + + --pre-exechook-command , $GITSYNC_PRE_EXECHOOK_COMMAND + An optional command to be executed before syncing a new hash of the + remote repository. This command does not take any arguments and + executes with the synced repo as its working directory. The + $GITSYNC_HASH environment variable will be set to the previous git hash that + was synced. This hook will always be invoked as it runs before any sync attempt. + + --pre-exechook-timeout , $GITSYNC_PRE_EXECHOOK_TIMEOUT + The timeout for the --pre-exechook-command. If not specifid, this + defaults to 30 seconds ("30s"). + + --filter , $GITSYNC_FILTER Use partial clone with the specified filter. This can reduce the amount of data transferred when cloning large repositories. diff --git a/pkg/hook/exechook.go b/pkg/hook/exechook.go index e31d99642..c62f15ef0 100644 --- a/pkg/hook/exechook.go +++ b/pkg/hook/exechook.go @@ -27,6 +27,8 @@ import ( // Exechook implements Hook in terms of executing a command. type Exechook struct { + // Name + name string // Runner cmdrunner cmd.Runner // Command to run @@ -42,8 +44,9 @@ type Exechook struct { } // NewExechook returns a new Exechook. -func NewExechook(cmdrunner cmd.Runner, command string, getWorktree func(string) string, args []string, timeout time.Duration, log logintf) *Exechook { +func NewExechook(name string, cmdrunner cmd.Runner, command string, getWorktree func(string) string, args []string, timeout time.Duration, log logintf) *Exechook { return &Exechook{ + name: name, cmdrunner: cmdrunner, command: command, getWorktree: getWorktree, @@ -55,7 +58,7 @@ func NewExechook(cmdrunner cmd.Runner, command string, getWorktree func(string) // Name describes hook, implements Hook.Name. func (h *Exechook) Name() string { - return "exechook" + return h.name } // Do runs exechook.command, implements Hook.Do. diff --git a/pkg/hook/exechook_test.go b/pkg/hook/exechook_test.go index 0de065088..1f6b521df 100644 --- a/pkg/hook/exechook_test.go +++ b/pkg/hook/exechook_test.go @@ -29,6 +29,7 @@ func TestNotZeroReturnExechookDo(t *testing.T) { t.Run("test not zero return code", func(t *testing.T) { l := logging.New("", "", 0) ch := NewExechook( + "exechook", cmd.NewRunner(l), "false", func(string) string { return "/tmp" }, @@ -47,6 +48,7 @@ func TestZeroReturnExechookDo(t *testing.T) { t.Run("test zero return code", func(t *testing.T) { l := logging.New("", "", 0) ch := NewExechook( + "exechook", cmd.NewRunner(l), "true", func(string) string { return "/tmp" }, @@ -65,6 +67,7 @@ func TestTimeoutExechookDo(t *testing.T) { t.Run("test timeout", func(t *testing.T) { l := logging.New("", "", 0) ch := NewExechook( + "exechook", cmd.NewRunner(l), "/bin/sh", func(string) string { return "/tmp" }, From 2d840264566e1f507292d114568d49c5d051e24b Mon Sep 17 00:00:00 2001 From: Mohamed-elg Date: Thu, 11 Jun 2026 22:38:53 +0200 Subject: [PATCH 2/3] readme + tab fix --- README.md | 16 ++++++++++++++++ main.go | 3 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9a4b1223..2fcd6b8c2 100644 --- a/README.md +++ b/README.md @@ -297,6 +297,22 @@ OPTIONS The timeout for the --exechook-command. If not specifid, this defaults to 30 seconds ("30s"). + --pre-exechook-backoff , $GITSYNC_PRE_EXECHOOK_BACKOFF + The time to wait before retrying a failed --pre-exechook-command. If + not specified, this defaults to 3 seconds ("3s"). + + --pre-exechook-command , $GITSYNC_PRE_EXECHOOK_COMMAND + An optional command to be executed before syncing a new hash of the + remote repository. This command does not take any arguments and + executes with the synced repo as its working directory. The + $GITSYNC_HASH environment variable will be set to the previous git hash that + was synced. This hook will always be invoked as it runs before any sync attempt. + + --pre-exechook-timeout , $GITSYNC_PRE_EXECHOOK_TIMEOUT + The timeout for the --pre-exechook-command. If not specifid, this + defaults to 30 seconds ("30s"). + + --filter , $GITSYNC_FILTER Use partial clone with the specified filter. This can reduce the amount of data transferred when cloning large repositories. diff --git a/main.go b/main.go index ffce3431a..5489b5572 100644 --- a/main.go +++ b/main.go @@ -2583,7 +2583,7 @@ OPTIONS The timeout for the --exechook-command. If not specifid, this defaults to 30 seconds ("30s"). - --pre-exechook-backoff , $GITSYNC_PRE_EXECHOOK_BACKOFF + --pre-exechook-backoff , $GITSYNC_PRE_EXECHOOK_BACKOFF The time to wait before retrying a failed --pre-exechook-command. If not specified, this defaults to 3 seconds ("3s"). @@ -2598,7 +2598,6 @@ OPTIONS The timeout for the --pre-exechook-command. If not specifid, this defaults to 30 seconds ("30s"). - --filter , $GITSYNC_FILTER Use partial clone with the specified filter. This can reduce the amount of data transferred when cloning large repositories. From 47f28c6eba0d042a87806477ea2f13a249c4073c Mon Sep 17 00:00:00 2001 From: Mohamed-elg Date: Thu, 11 Jun 2026 22:55:41 +0200 Subject: [PATCH 3/3] fix def --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 5489b5572..563c02afe 100644 --- a/main.go +++ b/main.go @@ -982,6 +982,7 @@ func main() { syncCount := uint64(0) initialSyncDone := false waitTime := *flInitPeriod + prevHash := "" // getMaxFailures returns the effective max-failure limit for the current // phase. During the initial sync phase, --init-max-failures (if set) // overrides --max-failures; otherwise --max-failures applies. @@ -994,7 +995,6 @@ func main() { for { start := time.Now() ctx, cancel := context.WithTimeout(context.Background(), *flSyncTimeout) - prevHash := "" if preExechookRunner != nil { preExechookRunner.Send(prevHash)