From d58db42a69a090a617dd220ef8780bc2bfa8fe9e Mon Sep 17 00:00:00 2001 From: qingliu Date: Thu, 25 Jun 2026 19:22:58 +0900 Subject: [PATCH] feat(git-init): add -lfs flag to pull Git LFS objects after checkout Add an opt-in -lfs flag (default false). When enabled, after the checkout and submodule update, run `git lfs install --local` and `git lfs pull` so LFS-tracked files are materialised into real content instead of pointer files, reusing the already-configured origin, credentials and proxy. Refs: DEVOPS-44388 --- image/git-init/git/git.go | 37 +++++++++++++++++++++++++++++++++++++ image/git-init/main.go | 1 + 2 files changed, 38 insertions(+) diff --git a/image/git-init/git/git.go b/image/git-init/git/git.go index be146e3e..a0db8f0c 100644 --- a/image/git-init/git/git.go +++ b/image/git-init/git/git.go @@ -69,6 +69,7 @@ type FetchSpec struct { HTTPSProxy string NOProxy string SparseCheckoutDirectories string + LFS bool } // Fetch fetches the specified git repository at the revision into path, using the refspec to fetch if provided. @@ -203,6 +204,14 @@ func Fetch(logger *zap.SugaredLogger, spec FetchSpec) error { return err } } + // Pull Git LFS objects last, after the working tree (and any submodules) + // are checked out, so the LFS-tracked files are materialised into real + // content instead of being left as LFS pointer files. + if spec.LFS { + if err := lfsFetch(logger, spec); err != nil { + return err + } + } return nil } @@ -240,6 +249,34 @@ func submoduleFetch(logger *zap.SugaredLogger, spec FetchSpec) error { return nil } +// lfsFetch downloads Git LFS objects for the checked-out revision and replaces +// the LFS pointer files in the working tree with their real content. It runs +// after the normal checkout so the repository, remote (origin) and credentials +// configured by Fetch are reused verbatim — LFS traffic goes to the same remote +// with the same auth and proxy settings. Requires the git-lfs binary to be +// present in the image; when absent the underlying git command fails loudly +// instead of silently leaving pointer files behind. +func lfsFetch(logger *zap.SugaredLogger, spec FetchSpec) error { + if spec.Path != "" { + if err := os.Chdir(spec.Path); err != nil { + return fmt.Errorf("failed to change directory with path %s; err: %w", spec.Path, err) + } + } + // Install the LFS smudge/process filters into this repository's local git + // config only (never --global / --system): the checkout is ephemeral and + // runs as a non-root user, so per-repo scope is both sufficient and safe. + if _, err := run(logger, "", "lfs", "install", "--local"); err != nil { + return fmt.Errorf("failed to install git-lfs (is the git-lfs binary available in the image?): %w", err) + } + // Fetch all LFS objects referenced by the current checkout and smudge them + // into the working tree. + if _, err := run(logger, "", "lfs", "pull"); err != nil { + return fmt.Errorf("failed to pull git-lfs objects: %w", err) + } + logger.Infof("Successfully pulled git-lfs objects in path %s", spec.Path) + return nil +} + // ensureHomeEnv works around an issue where ssh doesn't respect the HOME env variable. If HOME is set and // different from the user's detected home directory then symlink .ssh from the home directory to the HOME env // var. This way ssh will see the .ssh directory in the user's home directory even though it ignores diff --git a/image/git-init/main.go b/image/git-init/main.go index 0af2d9c9..1e99c651 100644 --- a/image/git-init/main.go +++ b/image/git-init/main.go @@ -37,6 +37,7 @@ func init() { flag.BoolVar(&fetchSpec.SSLVerify, "sslVerify", true, "Enable/Disable SSL verification in the git config") flag.BoolVar(&fetchSpec.Submodules, "submodules", true, "Initialize and fetch Git submodules") flag.UintVar(&fetchSpec.Depth, "depth", 1, "Perform a shallow clone to this depth") + flag.BoolVar(&fetchSpec.LFS, "lfs", false, "Pull Git LFS objects after checkout (requires git-lfs in the image)") flag.StringVar(&terminationMessagePath, "terminationMessagePath", "/tekton/termination", "Location of file containing termination message") flag.StringVar(&fetchSpec.SparseCheckoutDirectories, "sparseCheckoutDirectories", "", "String of directory patterns separated by a comma") }