From c044ea124afd167c59cf04a193843a7efcc4b02b Mon Sep 17 00:00:00 2001 From: mail2sudheerobbu-oss Date: Mon, 30 Mar 2026 12:17:36 -0400 Subject: [PATCH 1/6] fix(git): re-enable blobless clone filter with promisor-remote fallback Restore --filter=blob:none for bare clones with a graceful fallback for Git servers that do not support partial clones. When the clone fails with a "promisor remote" error, retry without the filter so the operation still succeeds on non-GitHub servers. Signed-off-by: sunithaveeroju <149201802+sunithaveeroju@users.noreply.github.com> --- pkg/controller/git/bare_repo.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/controller/git/bare_repo.go b/pkg/controller/git/bare_repo.go index bc8b1248ee..e5ac1f05ff 100644 --- a/pkg/controller/git/bare_repo.go +++ b/pkg/controller/git/bare_repo.go @@ -117,23 +117,28 @@ func CloneBare( return b, nil } -func (b *bareRepo) clone(_ *BareCloneOptions) error { +func (b *bareRepo) clone(opts *BareCloneOptions) error { + if opts == nil { + opts = &BareCloneOptions{} + } args := []string{"clone", "--bare"} - // NOTE(hidde): Temporarily disabled until we figure out why this can result - // in "could not fetch from promisor remote" errors. - // - // if opts.Filter != "" { - // args = append(args, "--filter", opts.Filter) - // } + if opts.Filter != "" { + args = append(args, "--filter", opts.Filter) + } args = append(args, b.accessURL, b.dir) cmd := b.buildGitCommand(args...) cmd.Dir = b.homeDir // Override the cmd.Dir that's set by r.buildGitCommand() if _, err := libExec.Exec(cmd); err != nil { + if opts.Filter != "" && strings.Contains(err.Error(), "promisor remote") { + // Some Git servers do not support partial clones. Fall back to a + // full clone without the filter. + opts.Filter = "" + return b.clone(opts) + } return fmt.Errorf("error cloning repo %q into %q: %w", b.originalURL, b.dir, err) } return nil } - type LoadBareRepoOptions struct { Credentials *RepoCredentials } From 1021abc674c84ec42f098279d30690af16db069a Mon Sep 17 00:00:00 2001 From: mail2sudheerobbu-oss Date: Mon, 30 Mar 2026 12:19:37 -0400 Subject: [PATCH 2/6] fix(git): re-enable clone filter with promisor-remote fallback in repo.go Re-enable filtering option for cloning repositories and handle errors related to promisor remotes. Signed-off-by: sunithaveeroju <149201802+sunithaveeroju@users.noreply.github.com> --- pkg/controller/git/repo.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pkg/controller/git/repo.go b/pkg/controller/git/repo.go index 5e20422899..0556d61afd 100644 --- a/pkg/controller/git/repo.go +++ b/pkg/controller/git/repo.go @@ -3,6 +3,7 @@ package git import ( "fmt" "os" + "strings" libExec "github.com/akuity/kargo/pkg/exec" ) @@ -119,21 +120,23 @@ func (r *repo) clone(opts *CloneOptions) error { if opts.Depth > 0 { args = append(args, "--depth", fmt.Sprint(opts.Depth)) } - // NOTE(hidde): Temporarily disabled until we figure out why this can result - // in "could not fetch from promisor remote" errors. - // - // if opts.Filter != "" { - // args = append(args, "--filter", opts.Filter) - // } + if opts.Filter != "" { + args = append(args, "--filter", opts.Filter) + } args = append(args, r.accessURL, r.dir) cmd := r.buildGitCommand(args...) cmd.Dir = r.homeDir // Override the cmd.Dir that's set by r.buildGitCommand() if _, err := libExec.Exec(cmd); err != nil { + if opts.Filter != "" && strings.Contains(err.Error(), "promisor remote") { + // Some Git servers do not support partial clones. Fall back to a + // full clone without the filter. + opts.Filter = "" + return r.clone(opts) + } return fmt.Errorf("error cloning repo %q into %q: %w", r.originalURL, r.dir, err) } return nil } - type LoadRepoOptions struct { Credentials *RepoCredentials } From dec2123aaa3ac69f0aab0b55633fb471cf1205af Mon Sep 17 00:00:00 2001 From: mail2sudheerobbu-oss Date: Mon, 6 Apr 2026 20:27:33 -0400 Subject: [PATCH 3/6] refactor(git): replace Filter string with Blobless bool opt-in in BareCloneOptions Per maintainer feedback, replace the reactive error-string-matching Filter field with an explicit Blobless bool field. When true, passes --filter=blob:none unconditionally without retry logic. Signed-off-by: sunithaveeroju <149201802+sunithaveeroju@users.noreply.github.com> --- pkg/controller/git/bare_repo.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/pkg/controller/git/bare_repo.go b/pkg/controller/git/bare_repo.go index e5ac1f05ff..bb4d99e070 100644 --- a/pkg/controller/git/bare_repo.go +++ b/pkg/controller/git/bare_repo.go @@ -7,7 +7,6 @@ import ( "os" "path/filepath" "slices" - "strings" libExec "github.com/akuity/kargo/pkg/exec" ) @@ -69,11 +68,12 @@ type BareCloneOptions struct { // should be ignored when cloning the repository. The setting will be // remembered for subsequent interactions with the remote repository. InsecureSkipTLSVerify bool - // Filter specifies a partial clone filter (e.g., "blob:none"). When combined - // with sparse checkout, this avoids downloading blobs for directories that - // won't be checked out, significantly reducing clone time and disk usage for - // large repositories. - Filter string + // Blobless enables blobless cloning (--filter=blob:none). When set, the + // initial clone downloads all commits and trees but defers blob downloads + // until checkout. Combine with sparse checkout to minimise disk usage on + // large repositories. The server must support partial clones; if it does + // not, the clone will fail. + Blobless bool } // CloneBare produces a local, bare clone of the remote Git repository at the @@ -122,19 +122,13 @@ func (b *bareRepo) clone(opts *BareCloneOptions) error { opts = &BareCloneOptions{} } args := []string{"clone", "--bare"} - if opts.Filter != "" { - args = append(args, "--filter", opts.Filter) + if opts.Blobless { + args = append(args, "--filter", "blob:none") } args = append(args, b.accessURL, b.dir) cmd := b.buildGitCommand(args...) cmd.Dir = b.homeDir // Override the cmd.Dir that's set by r.buildGitCommand() if _, err := libExec.Exec(cmd); err != nil { - if opts.Filter != "" && strings.Contains(err.Error(), "promisor remote") { - // Some Git servers do not support partial clones. Fall back to a - // full clone without the filter. - opts.Filter = "" - return b.clone(opts) - } return fmt.Errorf("error cloning repo %q into %q: %w", b.originalURL, b.dir, err) } return nil From 6a094401adc55b8edbf6fc6840ff2c685a19ceb5 Mon Sep 17 00:00:00 2001 From: mail2sudheerobbu-oss Date: Mon, 6 Apr 2026 20:28:07 -0400 Subject: [PATCH 4/6] refactor(git): replace Filter string with Blobless bool opt-in in CloneOptions Replaced Filter option with Blobless for blobless cloning. Updated clone logic to use Blobless when specified. Signed-off-by: sunithaveeroju <149201802+sunithaveeroju@users.noreply.github.com> --- pkg/controller/git/repo.go | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/pkg/controller/git/repo.go b/pkg/controller/git/repo.go index 0556d61afd..db0e5cd498 100644 --- a/pkg/controller/git/repo.go +++ b/pkg/controller/git/repo.go @@ -3,7 +3,6 @@ package git import ( "fmt" "os" - "strings" libExec "github.com/akuity/kargo/pkg/exec" ) @@ -46,16 +45,12 @@ type CloneOptions struct { // Depth is the number of commits to fetch from the remote repository. If // zero, all commits will be fetched. This option is ignored if Bare is true. Depth uint - // Filter allows for partially cloning the repository by specifying a - // filter. When a filter is specified, the server will only send a - // subset of reachable objects according to a given object filter. - // - // For more information, see: - // - https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--filtercodeemltfilter-specgtem - // - https://git-scm.com/docs/git-rev-list#Documentation/git-rev-list.txt---filterltfilter-specgt - // - https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/ - // - https://docs.gitlab.com/ee/topics/git/partial_clone.html - Filter string + // Blobless enables blobless cloning (--filter=blob:none). When set, the + // initial clone downloads all commits and trees but defers blob downloads + // until checkout. Combine with sparse checkout to minimise disk usage on + // large repositories. The server must support partial clones; if it does + // not, the clone will fail. + Blobless bool // SingleBranch indicates whether the clone should be a single-branch clone. // This option is ignored if Bare is true. SingleBranch bool @@ -120,19 +115,13 @@ func (r *repo) clone(opts *CloneOptions) error { if opts.Depth > 0 { args = append(args, "--depth", fmt.Sprint(opts.Depth)) } - if opts.Filter != "" { - args = append(args, "--filter", opts.Filter) + if opts.Blobless { + args = append(args, "--filter", "blob:none") } args = append(args, r.accessURL, r.dir) cmd := r.buildGitCommand(args...) cmd.Dir = r.homeDir // Override the cmd.Dir that's set by r.buildGitCommand() if _, err := libExec.Exec(cmd); err != nil { - if opts.Filter != "" && strings.Contains(err.Error(), "promisor remote") { - // Some Git servers do not support partial clones. Fall back to a - // full clone without the filter. - opts.Filter = "" - return r.clone(opts) - } return fmt.Errorf("error cloning repo %q into %q: %w", r.originalURL, r.dir, err) } return nil From 6494d17f4ab483975f47128cdb2fed71a22134fb Mon Sep 17 00:00:00 2001 From: mail2sudheerobbu-oss Date: Mon, 6 Apr 2026 20:28:33 -0400 Subject: [PATCH 5/6] refactor(git): update git_cloner to use Blobless bool instead of Filter string Signed-off-by: sunithaveeroju <149201802+sunithaveeroju@users.noreply.github.com> --- pkg/promotion/runner/builtin/git_cloner.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/promotion/runner/builtin/git_cloner.go b/pkg/promotion/runner/builtin/git_cloner.go index 5203bd5c44..dcc6b8e666 100644 --- a/pkg/promotion/runner/builtin/git_cloner.go +++ b/pkg/promotion/runner/builtin/git_cloner.go @@ -42,16 +42,15 @@ type gitCloner struct { } // filterForCheckouts returns the clone filter to use based on checkout -// configurations. Returns git.FilterBlobless if all checkouts specify sparse -// patterns, returns empty string otherwise to avoid on-demand blob fetches for -// full checkouts. -func filterForCheckouts(checkouts []builtin.Checkout) string { +// configurations. Returns true if all checkouts specify sparse patterns (enabling +// blobless cloning), false otherwise. +func filterForCheckouts(checkouts []builtin.Checkout) bool { for _, checkout := range checkouts { if len(checkout.Sparse) == 0 { - return "" + return false } } - return git.FilterBlobless + return true } // gitUserFromEnv populates a git.User struct from environment variables. @@ -161,7 +160,7 @@ func (g *gitCloner) run( }, &git.BareCloneOptions{ BaseDir: stepCtx.WorkDir, - Filter: filterForCheckouts(cfg.Checkout), + Blobless: filterForCheckouts(cfg.Checkout), }, ) if err != nil { From 2b047400f7af5d69c8d302e3418ae942a53764dc Mon Sep 17 00:00:00 2001 From: sunithaveeroju <149201802+sunithaveeroju@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:19:40 -0400 Subject: [PATCH 6/6] feat(git-clone): add explicit blobless opt-in to GitCloneConfig Signed-off-by: sunithaveeroju <149201802+sunithaveeroju@users.noreply.github.com> --- pkg/promotion/runner/builtin/git_cloner.go | 14 +------------- .../runner/builtin/schemas/git-clone-config.json | 4 ++++ pkg/x/promotion/runner/builtin/zz_config_types.go | 2 ++ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/pkg/promotion/runner/builtin/git_cloner.go b/pkg/promotion/runner/builtin/git_cloner.go index dcc6b8e666..76b9750da7 100644 --- a/pkg/promotion/runner/builtin/git_cloner.go +++ b/pkg/promotion/runner/builtin/git_cloner.go @@ -41,18 +41,6 @@ type gitCloner struct { schemaLoader gojsonschema.JSONLoader } -// filterForCheckouts returns the clone filter to use based on checkout -// configurations. Returns true if all checkouts specify sparse patterns (enabling -// blobless cloning), false otherwise. -func filterForCheckouts(checkouts []builtin.Checkout) bool { - for _, checkout := range checkouts { - if len(checkout.Sparse) == 0 { - return false - } - } - return true -} - // gitUserFromEnv populates a git.User struct from environment variables. func gitUserFromEnv() git.User { cfg := struct { @@ -160,7 +148,7 @@ func (g *gitCloner) run( }, &git.BareCloneOptions{ BaseDir: stepCtx.WorkDir, - Blobless: filterForCheckouts(cfg.Checkout), + Blobless: cfg.Blobless, }, ) if err != nil { diff --git a/pkg/promotion/runner/builtin/schemas/git-clone-config.json b/pkg/promotion/runner/builtin/schemas/git-clone-config.json index f92a0e672c..b27de897a1 100644 --- a/pkg/promotion/runner/builtin/schemas/git-clone-config.json +++ b/pkg/promotion/runner/builtin/schemas/git-clone-config.json @@ -13,6 +13,10 @@ "type": "boolean", "description": "Indicates whether to recursively clone submodules. Default is false. Note that any provided credentials must also be valid for the submodules." }, + "blobless": { + "type": "boolean", + "description": "Indicates whether to perform a blobless (--filter=blob:none) clone. Default is false." + }, "repoURL": { "type": "string", "description": "The URL of a remote Git repository to clone. Required. Deprecated: Support for SSH URLs (ssh:// and SCP-style git@host:path) is deprecated as of v1.10.0 and will be removed in v1.13.0. Use HTTPS URLs instead.", diff --git a/pkg/x/promotion/runner/builtin/zz_config_types.go b/pkg/x/promotion/runner/builtin/zz_config_types.go index 842242e17c..f37cdc1923 100644 --- a/pkg/x/promotion/runner/builtin/zz_config_types.go +++ b/pkg/x/promotion/runner/builtin/zz_config_types.go @@ -170,6 +170,8 @@ type GitCloneConfig struct { // provided, this overrides any system-level defaults. Note: Configuration of the // `git-commit` and `git-tag` steps can override this information. Author *GitCloneConfigAuthor `json:"author,omitempty"` + // Indicates whether to perform a blobless (--filter=blob:none) clone. Default is false. + Blobless bool `json:"blobless,omitempty"` // The commits, branches, or tags to check out from the repository and the paths where they // should be checked out. At least one must be specified. Checkout []Checkout `json:"checkout"`