Skip to content

feat: add sparse checkout support#3903

Open
mcncl wants to merge 3 commits into
mainfrom
SUP-6409/sparse_checkout
Open

feat: add sparse checkout support#3903
mcncl wants to merge 3 commits into
mainfrom
SUP-6409/sparse_checkout

Conversation

@mcncl
Copy link
Copy Markdown
Contributor

@mcncl mcncl commented May 7, 2026

Description

This adds sparse checkout support to the agent via BUILDKITE_GIT_SPARSE_CHECKOUT_PATHS, allowing checkout to materialize only selected paths in the working tree.

It also handles reused checkout directories safely by reconfiguring sparse checkout when paths change, and disabling sparse checkout when a later job does not request it.

Changes

  • adds git-sparse-checkout-paths support to agent start and bootstrap
  • propagates BUILDKITE_GIT_SPARSE_CHECKOUT_PATHS through the agent/bootstrap environment
  • runs Executor.setupSparseCheckout after fetch and before checkout
  • disables sparse checkout and restores a full checkout when later jobs on the same checkout dir do not request sparse paths
  • extracts sparse checkout logic and tests into focused files:
    • internal/job/checkout_sparse.go
    • internal/job/checkout_sparse_test.go

Testing

  • Tests have run locally (with go test ./...). Buildkite employees may check this if the pipeline has run automatically.
  • Code is formatted (with go tool gofumpt -extra -w .)

Disclosures / Credits

I used Codex to assist with writing this change. The core function (non _test.go) changes were implemented by me, I then used Codex to assist with writing the Test functions.

I used Amp to check the changes, which it highlighted a couple of issues with (fixed) and a missing test case (added).

Comment thread internal/job/checkout.go Outdated
// current working tree. It returns true if sparse checkout was successfully
// applied for this build, so callers can adjust later behaviour (e.g. skip
// submodule init, which requires the full tree).
func (e *Executor) setupSparseCheckout(ctx context.Context) (bool, error) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sparse-checkout set --cone requires git2.26. On older git we warn and fall through to a normal full checkout rather than failing the build.

Comment thread agent/job_runner.go Outdated
setEnv("BUILDKITE_GIT_CHECKOUT_FLAGS", r.conf.AgentConfiguration.GitCheckoutFlags)
setEnv("BUILDKITE_GIT_CLONE_FLAGS", r.conf.AgentConfiguration.GitCloneFlags)
setEnv("BUILDKITE_GIT_FETCH_FLAGS", r.conf.AgentConfiguration.GitFetchFlags)
setEnv("BUILDKITE_SPARSE_CHECKOUT_PATHS", r.conf.AgentConfiguration.SparseCheckoutPaths)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always exported (matching all other git flag env vars); empty value = full checkout

@mcncl mcncl force-pushed the SUP-6409/sparse_checkout branch from 318d9ef to 163cbea Compare May 7, 2026 05:00
@mcncl mcncl marked this pull request as ready for review May 7, 2026 05:08
@mcncl mcncl requested review from a team as code owners May 7, 2026 05:08
Copy link
Copy Markdown
Contributor

@DrJosh9000 DrJosh9000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first reaction is "why not _Git_SparseCheckoutPaths", but I'm not fussed

Comment thread clicommand/global.go Outdated
@mcncl
Copy link
Copy Markdown
Contributor Author

mcncl commented May 7, 2026

@DrJosh9000 thats a fair call and removes any chance of ambiguity

Copy link
Copy Markdown
Contributor

@mcncl - do not merge these changes into main. These changes should be merged into the branch feat/git-checkout-features

@mcncl mcncl force-pushed the SUP-6409/sparse_checkout branch from 163cbea to 9e93039 Compare May 7, 2026 23:11
@mcncl mcncl changed the base branch from main to feat/git-checkout-features May 7, 2026 23:12
Copy link
Copy Markdown
Contributor

@zhming0 zhming0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some suggestions re code organization, increase signal-to-noise ratio on integration test and adding an e2e test. 🙏🏿

so far no blocker, but I plan to take another look later 👀

Comment thread agent/job_runner.go
requireCheckoutPath(t, tester.CheckoutDir(), "docs/readme.md", false)
}

func TestCheckingOutLocalGitProjectWithSparseCheckoutExistingGitDir(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's getting a bit difficult to tell the difference between this test case and the case above. Code comment will be ideal, or alternatively maybe we can consolidate to reduce repetition and increase signal-to-noise ratio?

Comment thread internal/job/checkout.go
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the size of checkout.go and the fact that this code change is quite isolated, I recommend opening a internal/job/checkout_sparse.go file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same recommendation for this code change, we can move these new tests to their own focused test file, so checkout_test.go focus on testing the core, happy path.

Comment thread internal/job/checkout.go Outdated
Comment on lines +432 to +453
if len(paths) == 0 {
e.disableSparseCheckoutIfConfigured(ctx)
return false, nil
}

ok, err := gitVersionAtLeast(ctx, e.shell, 2, 26)
if err != nil {
e.shell.Warningf("Sparse checkout requires git >= 2.26; falling back to full checkout (%v)", err)
e.disableSparseCheckoutIfConfigured(ctx)
return false, nil
}
if !ok {
e.shell.Warningf("Sparse checkout requires git >= 2.26; falling back to full checkout")
e.disableSparseCheckoutIfConfigured(ctx)
return false, nil
}

e.shell.Commentf("Setting up sparse checkout for paths: %s", strings.Join(paths, ","))
args := append([]string{"sparse-checkout", "set", "--cone"}, paths...)
if err := e.shell.Command("git", args...).Run(ctx); err != nil {
return false, fmt.Errorf("setting sparse checkout paths: %w", err)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest writing an end-to-end test to verify that the behavior of setupSparseCheckout is safe when an agent executes jobs that require sparse checkout interleaved with other jobs that don't require it.

@mcncl mcncl changed the base branch from feat/git-checkout-features to main June 1, 2026 00:34
@mcncl mcncl force-pushed the SUP-6409/sparse_checkout branch from f844882 to a93473e Compare June 1, 2026 02:18
@mcncl mcncl added the feature New user-facing feature! label Jun 1, 2026
Comment thread agent/job_runner.go
setEnv("BUILDKITE_GIT_CHECKOUT_FLAGS", r.conf.AgentConfiguration.GitCheckoutFlags)
setEnv("BUILDKITE_GIT_CLONE_FLAGS", r.conf.AgentConfiguration.GitCloneFlags)
setEnv("BUILDKITE_GIT_FETCH_FLAGS", r.conf.AgentConfiguration.GitFetchFlags)
setEnv("BUILDKITE_GIT_SPARSE_CHECKOUT_PATHS", strings.Join(r.conf.AgentConfiguration.GitSparseCheckoutPaths, ","))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this setEnv be conditional, (ie. if len(r.conf.AgentConfiguration.GitSparseCheckoutPaths) > 0 { setEnv("BUILDKITE_GIT_SPARSE_CHECKOUT_PATHS", strings.Join(r.conf.AgentConfiguration.GitSparseCheckoutPaths, ",")) })?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New user-facing feature!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants