From a89cb1db1fdc03540d0cbc8e72dd3e05b9f11712 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 10 Sep 2026 16:22:23 +0200 Subject: [PATCH] bugfix: Fetch the LFS objects of a checkout of a tag or commit The refs given to a git checkout command were resolved with 'git show-ref' and an LFS fetch of the mirror was performed only if the ref was found as a remote branch. Therefore, a checkout of a tag or a commit did not fetch the LFS objects into the mirror and the git-lfs filters of the checkout had to download them on their own. The refs are now resolved to commits and are checked against the mirror, so that a checkout of a tag or a commit fetches the LFS objects as well. Co-authored-by: Cursor --- CHANGELOG.md | 4 ++++ src/git_cache/commands/checkout.py | 27 ++++++++++++----------- src/git_cache/git_mirror.py | 13 +++++++++++ test/functional_tests/test_16_checkout.py | 6 +++++ 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be77f67..56d5126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Upcoming version +- Bugfix: Fetch the LFS objects of a `git checkout` of a tag or a commit as well. Previously, only a checkout of a + branch triggered an LFS fetch of the mirror and the LFS objects of a tag or commit were downloaded by the git-lfs + filters of the checkout itself. + ## v1.0.34 - Feature: Add optional detail and summary log files controlled by `GITCACHE_DETAIL_LOG` and `GITCACHE_SUMMARY_LOG` for CI-friendly invocation tracing. (thanks to ditschi) diff --git a/src/git_cache/commands/checkout.py b/src/git_cache/commands/checkout.py index 0814b48..84669e2 100644 --- a/src/git_cache/commands/checkout.py +++ b/src/git_cache/commands/checkout.py @@ -43,23 +43,24 @@ def git_checkout(git_options: GitOptions) -> int: Returns 0 on success, otherwise the return code of the last failed command. """ - # Collect all refs for an lfs fetch + mirror_url = get_mirror_url(git_options) + if mirror_url is None: + return simple_call_command(git_options.get_real_git_all_args()) + + mirror = GitMirror(url=mirror_url, database=Database()) + + # Collect all commits for an lfs fetch ref_candidates = [x for x in git_options.command_args if not x.startswith("-") and not x.startswith(":")] lfs_fetch_refs = [] for ref in ref_candidates: command = git_options.get_real_git_with_options() - command += ["show-ref", ref] - retval, output = getstatusoutput(command) - if retval == 0 and "remotes" in output: - lfs_fetch_refs.append(ref) - - if lfs_fetch_refs: - mirror_url = get_mirror_url(git_options) - if mirror_url: - database = Database() - mirror = GitMirror(url=mirror_url, database=database) - for ref in lfs_fetch_refs: - mirror.fetch_lfs(ref) + command += ["rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}"] + retval, commit = getstatusoutput(command) + if retval == 0 and mirror.has_commit(commit): + lfs_fetch_refs.append(commit) + + for ref in lfs_fetch_refs: + mirror.fetch_lfs(ref) return simple_call_command(git_options.get_real_git_all_args()) diff --git a/src/git_cache/git_mirror.py b/src/git_cache/git_mirror.py index 88d297d..93d25e9 100644 --- a/src/git_cache/git_mirror.py +++ b/src/git_cache/git_mirror.py @@ -265,6 +265,19 @@ def fetch_lfs(self, ref=None, options=None): LOG.warning("LFS fetch skipped as git-lfs is not available on this system!") return True + def has_commit(self, commit: str) -> bool: + """Check whether the given commit is part of the mirror. + + Args: + commit (str): The commit to search for. + + Return: + Returns True if the mirror contains the given commit. + """ + command = [self.config.get("System", "RealGit"), "cat-file", "-e", f"{commit}^{{commit}}"] + return_code, _ = getstatusoutput(command, cwd=self.git_dir) + return return_code == 0 + def cleanup(self): """Delete the mirror if it is too old. diff --git a/test/functional_tests/test_16_checkout.py b/test/functional_tests/test_16_checkout.py index a920c9f..d8de681 100644 --- a/test/functional_tests/test_16_checkout.py +++ b/test/functional_tests/test_16_checkout.py @@ -49,6 +49,12 @@ def test_checkout(gitcache_ifc: GitcacheIfc): ) assert os.path.exists(lfs_obj_file) + # Checkout of a commit should fetch the lfs files as well + commit = gitcache_ifc.run_ok(["git", "-C", checkout, "rev-parse", "extra_branch"]).stdout.strip() + lfs_updates = gitcache_ifc.db_field("lfs-updates", repo) + gitcache_ifc.run_ok(["git", "-C", checkout, "checkout", commit]) + assert lfs_updates + 1 == gitcache_ifc.db_field("lfs-updates", repo) + # ---------------------------------------------------------------------------- # EOF