Skip to content

Conversation

@thorsten-klein
Copy link
Contributor

@thorsten-klein thorsten-klein commented Dec 5, 2025

Issue: Auto-cache fails when cached repository’s default branch changes

We’re encountering failures when using auto-cache in scenarios where the default branch name of a cached repository has changed. @pdgendt has been able to reproduce this issue locally as well.

During analysis, we observed that git remote update --prune completes successfully. However, attempting to clone a repository from the auto-cache into the workspace subsequently results in a valid clone, but with no HEAD existing.

This behavior is resolved with git (2.48.0). Without the fix from this PR, the added test fails with older git (before 2.48.0, e.g. 2.34.1 from Ubuntu 22), but it passes when newer git (>=2.48.0) is used.
Reference to git Release Notes: https://github.com/git/git/blob/master/Documentation/RelNotes/2.48.0.adoc

When "git fetch $remote" notices that refs/remotes/$remote/HEAD is missing and discovers what branch the other side points with its HEAD, refs/remotes/$remote/HEAD is updated to point to it.

A typical west update error looks like this:

--- trusted-firmware-m: cloning from /var/tmp/west/auto-cache/trusted-firmware-m/23a33554f8237323fc95b384be1b9463
Cloning into '/home/klt1re/work/GIT/uc-platform-ws/modules/tee/tf-m/trusted-firmware-m'...
done.
warning: remote HEAD refers to nonexistent ref, unable to checkout
fatal: git checkout: --detach does not take a path argument 'HEAD'

Root Cause

When a remote default branch is renamed and the auto-cache is updated with remote, the auto-cache becomes corrupt as HEAD points to an invalid remote HEAD

Git clone from such a corrupt auto-cache succeeds, but with a warning warning: remote HEAD refers to nonexistent ref, unable to checkout.. Subsequent git commands using HEAD may fail. For example, the next git command in west update is git checkout --detach HEAD, which now fails because HEAD does not exist.

First Approach: Ensure HEAD exists

After cloning, run git checkout {project.revision} to make sure that HEAD exists. Subsequent git checkout --detach HEAD will succeed. But in this case, the auto-cache still remains corrupt. So I personally prefer a solution that fixes the auto-cache, instead of just fixing the clone in the workspace.

Second Approach: Fixing the auto-cache

After running git remote update --prune, we need to set HEAD to remote HEAD within the auto-cache.
This is addressed by:

  1. Reading the current remote HEAD using:
    git ls-remote --symref origin HEAD
  2. Updating the local HEAD to match the remote using:
    git symbolic-ref HEAD <remote-HEAD>

This ensures the cache stays in sync with the upstream repository’s default branch and prevents the checkout errors described above. This solution helps to ensure that HEAD will always exist after cloning from the cache.

Third Approach: Avoid running into the issue by not using HEAD at all

Avoid using HEAD at all during west update after cloning a repository from a cache into the workspace.

@thorsten-klein thorsten-klein force-pushed the auto-cache-renamed-remote-default-branch branch from d3048f5 to 6551422 Compare December 5, 2025 16:50
@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.95%. Comparing base (11302e1) to head (c99a3da).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #895   +/-   ##
=======================================
  Coverage   85.95%   85.95%           
=======================================
  Files          11       11           
  Lines        3453     3447    -6     
=======================================
- Hits         2968     2963    -5     
+ Misses        485      484    -1     
Files with missing lines Coverage Δ
src/west/app/project.py 80.50% <ø> (-0.02%) ⬇️

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request fixes a critical issue where cloning from auto-cache fails when a repository's default branch is renamed on the remote. The fix ensures the cached repository's HEAD is synchronized with the remote HEAD before updating, preventing checkout failures.

Key Changes

  • Adds logic to update the auto-cache's symbolic HEAD reference before syncing with remote to handle default branch changes
  • Implements an optimization to skip remote updates when the requested revision is already present in the auto-cache
  • Updates documentation to clarify auto-cache behavior regarding on-demand synchronization

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/west/app/project.py Implements HEAD synchronization logic before remote updates and adds optimization to skip unnecessary remote fetches; updates help text and documentation for auto-cache behavior; extends _rev_type function with cwd parameter
tests/test_project_caching.py Adds comprehensive test for auto-cache remote update optimization, including scenarios with offline remotes, new commits, and branch revisions; imports chdir helper

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@thorsten-klein thorsten-klein force-pushed the auto-cache-renamed-remote-default-branch branch from 6551422 to e473e19 Compare December 10, 2025 20:00
@thorsten-klein
Copy link
Contributor Author

UPDATE:
With newest git version 2.52.0 (as it is used within west CI), the issue is resolved.
Therefore I will change to the simple solution which just fixes the clone.

@thorsten-klein thorsten-klein force-pushed the auto-cache-renamed-remote-default-branch branch from e473e19 to 0e2c9ec Compare December 10, 2025 20:49
@thorsten-klein thorsten-klein force-pushed the auto-cache-renamed-remote-default-branch branch from 0e2c9ec to f86db73 Compare December 11, 2025 11:49
@pdgendt pdgendt requested review from marc-hb and mbolivar December 11, 2025 12:33
pdgendt
pdgendt previously approved these changes Dec 11, 2025
@pdgendt pdgendt dismissed their stale review December 11, 2025 13:48

open comment left

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@thorsten-klein thorsten-klein force-pushed the auto-cache-renamed-remote-default-branch branch from f86db73 to 486468a Compare December 14, 2025 17:09
Copy link
Collaborator

@marc-hb marc-hb left a comment

Choose a reason for hiding this comment

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

So I personally prefer a solution that fixes the auto-cache, instead of just fixing the clone in the workspace.

Why not fix both?

With newest git version 2.52.0 (as it is used within west CI), the issue is resolved.

What changed in git 2.52 and which part does it fix?

west must stay compatible with a large range of git versions so I'm not sure what you're trying to say here.

Therefore I will change to the simple solution which just fixes the clone.

BTW prefer "workspace" than "clone" because everything is a clone.

)
# Reset the remote's URL to the project's fetch URL.
project.git(['remote', 'set-url', project.remote_name, project.url])
# Make sure a valid revision is checked out
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment is too short, it relates to neither the issue nor to #895 that describes it.

# Reset the remote's URL to the project's fetch URL.
project.git(['remote', 'set-url', project.remote_name, project.url])
# Make sure a valid revision is checked out
project.git(['checkout', '--quiet', project.revision])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than 2 git checkout ... commands, would this single checkout achieve the same result?
And then --detach is probably redundant.

Suggested change
project.git(['checkout', '--quiet', project.revision])
project.git(['checkout', '--quiet', project.revision + "~0"])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, it really seems to work without the project.git('checkout --quiet --detach HEAD'). So only this line is enough

project.git(['checkout', '--quiet', project.revision])

With your suggested ~0 it does not work anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevertheless the issue occurs in code which seems to have no effect. At least all tests still pass even when returning after project.git(['remote', 'set-url', project.remote_name, project.url]).

Maybe it makes more sense to clean up (throw out this code) instead of applying fixes for obsolete code?

@thorsten-klein
Copy link
Contributor Author

thorsten-klein commented Dec 17, 2025

I see that the original author was @mbolivar (reference: c50d342).

Do you remember the rationale for deleting all branches after the repository is cloned into the workspace from a cache?

Also, do you see any potential risks in keeping the local branches?
From what I can tell, removing this behavior does not cause any tests to fail.

@thorsten-klein thorsten-klein force-pushed the auto-cache-renamed-remote-default-branch branch 2 times, most recently from 7dd6b8a to 229d7ae Compare December 17, 2025 08:32
@marc-hb
Copy link
Collaborator

marc-hb commented Dec 17, 2025

Do you remember the rationale for deleting all branches after the repository is cloned into the workspace from a cache?

Not sure, risk of collision and user confusion maybe?

I vaguely remember #331 and #346 but I'm not sure they are related and I didn't take the time to read them again yet.

@marc-hb
Copy link
Collaborator

marc-hb commented Dec 17, 2025

With your suggested ~0 it does not work anymore.

What do you mean "does not work"; how can this fail? I've never seen appending ~0 failing before (and it does make a big difference when appending to branch names). BTW we already have HEAD~0 in the documentation and in a test.

Appending ^{commit} is probably "more correct" than ~0 and certainly more readable. I suspect the effect is the same. Check git help revisions to make sure.

At least all tests still pass even when...

There are always areas with little or no test coverage, "all tests pass" is great but never enough.

This commit removes existing code, which uses HEAD right after cloning
the repository into the workspace from a cache.
Background: When a remote default branch is renamed and the auto-cache
is updated with remote, the auto-cache becomes corrupt as HEAD points to
an invalid remote HEAD. Therefore, avoid using HEAD within git
operations during west update, as long as no revision is checked out.
@thorsten-klein thorsten-klein force-pushed the auto-cache-renamed-remote-default-branch branch from 229d7ae to c99a3da Compare December 19, 2025 07:48
@thorsten-klein
Copy link
Contributor Author

What do you mean "does not work"

With this change the added test does not pass, so it "does not work".

@marc-hb

This comment was marked as resolved.

@thorsten-klein
Copy link
Contributor Author

If you don't have an explanation for that test failure

project.revision may contain a branch name, and so the tests fail with following error when using f"{project.revision}~0"

fatal: 'changed/main~0' is not a valid branch name

@marc-hb
Copy link
Collaborator

marc-hb commented Dec 23, 2025

fatal: 'changed/main~0' is not a valid branch name

This is making less and less sense. I suggested project.git(['checkout', '--quiet', project.revision + "~0"]). git checkout does not need a branch name. The idea of appending ~0 was precisely to STOP it being a branch name!

Compare: git checkout main with git checkout main~0.

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.

3 participants