From ec7106fdeade6531866e620f48069ff1845f404c Mon Sep 17 00:00:00 2001 From: German Lashevich Date: Wed, 27 Mar 2024 03:41:25 +0100 Subject: [PATCH] feat(git): retry git clone on retriable error (#4725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds retry logic to the process of cloning a git repository. Currently, it retries only if the output of the git clone command contains the string `Connection reset`. Probably, there are more cases where retry is reasonable, but I'm not sure what they are. The number of retries is hardcoded to 10 with increasing delay between retries — in the same way as it is done in #4265, which served me as an example. The initial use case is described in #4724. --- internal/client/git.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/internal/client/git.go b/internal/client/git.go index 2a500d015..802626447 100644 --- a/internal/client/git.go +++ b/internal/client/git.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" "sync" + "time" "github.com/caarlos0/log" "github.com/charmbracelet/x/exp/ordered" @@ -85,10 +86,8 @@ func (g *gitClient) CreateFiles( return fmt.Errorf("git: failed to create parent: %w", err) } - if err := runGitCmds(ctx, parent, env, [][]string{ - {"clone", url, name}, - }); err != nil { - return fmt.Errorf("git: failed to clone local repository: %w", err) + if err := cloneRepoWithRetries(ctx, parent, url, name, env); err != nil { + return err } if err := runGitCmds(ctx, cwd, env, [][]string{ @@ -200,6 +199,31 @@ func isPasswordError(err error) bool { return errors.As(err, &kerr) } +func cloneRepoWithRetries(ctx *context.Context, parent, url, name string, env []string) error { + var try int + for try < 10 { + try++ + err := runGitCmds(ctx, parent, env, [][]string{{"clone", url, name}}) + if err == nil { + return nil + } + if isRetriableCloneError(err) { + log.WithField("try", try). + WithField("image", name). + WithError(err). + Warnf("failed to push image, will retry") + time.Sleep(time.Duration(try*10) * time.Second) + continue + } + return fmt.Errorf("failed to clone local repository: %w", err) + } + return fmt.Errorf("failed to push %s after %d tries", name, try) +} + +func isRetriableCloneError(err error) bool { + return strings.Contains(err.Error(), "Connection reset") +} + func runGitCmds(ctx *context.Context, cwd string, env []string, cmds [][]string) error { for _, cmd := range cmds { args := append([]string{"-C", cwd}, cmd...)