Skip to content

feat: add sparse checkout support#3903

Open
mcncl wants to merge 4 commits into
feat/git-checkout-featuresfrom
SUP-6409/sparse_checkout
Open

feat: add sparse checkout support#3903
mcncl wants to merge 4 commits into
feat/git-checkout-featuresfrom
SUP-6409/sparse_checkout

Conversation

@mcncl
Copy link
Copy Markdown
Contributor

@mcncl mcncl commented May 7, 2026

Description

This adds support for sparse checkout logic within the agent, allowing it to read BUILDKITE_SPARSE_CHECKOUT_PATHS from the job environment, running the necessary git checkout process.

Changes

  • implements a global flag of sparse-checkout-paths in the agent CLI on agent start and bootstrap
  • Executor.setupSparseCheckout runs after fetch and before checkout
  • tests covering; standalone sparse checkout, sparse with mirrors, sparse with submodules (init suppressed), and re-running with different sparse paths in an existing checkout dir

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
// 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
EnvVar: "BUILDKITE_GIT_FETCH_FLAGS",
}

SparseCheckoutPathsFlag = cli.StringFlag{
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.

Consider using StringSliceFlag so that the cli package does the splitting for you (the field type can then be []string), but then of course you will have to re-join the strings for passing between agent and bootstrap

@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
if len(r.conf.AgentConfiguration.GitSparseCheckoutPaths) > 0 {
setCheckoutEnv("BUILDKITE_GIT_SPARSE_CHECKOUT_PATHS", strings.Join(r.conf.AgentConfiguration.GitSparseCheckoutPaths, ","))
}
setEnv("BUILDKITE_GIT_CLONE_MIRROR_FLAGS", r.conf.AgentConfiguration.GitCloneMirrorFlags)
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.

This change does not seem to get mentioned on the PR description?

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
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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants