git: fix possible conflict on mutated branches

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2025-10-01 22:52:08 -07:00
parent d9183cb92e
commit 9f5cf396f6
2 changed files with 98 additions and 2 deletions

View File

@@ -500,8 +500,8 @@ func (gs *gitSourceHandler) Snapshot(ctx context.Context, g session.Group) (cach
ref, err := gs.trySnapshot(ctx, g, false)
if err != nil {
var wce *wouldClobberExistingTagError
if errors.As(err, &wce) {
// try once more with a fresh repo
var ulre *unableToUpdateLocalRefError
if errors.As(err, &wce) || errors.As(err, &ulre) {
ref, err = gs.trySnapshot(ctx, g, true)
if err != nil {
return nil, err
@@ -581,6 +581,11 @@ func (gs *gitSourceHandler) trySnapshot(ctx context.Context, g session.Group, re
// only hope is to abandon the existing shared repo and start a fresh one
return nil, &wouldClobberExistingTagError{err}
}
if strings.Contains(err.Error(), "(unable to update local ref)") && strings.Contains(err.Error(), "some local refs could not be updated;") {
// this can happen if a branch updated in remote so that old branch
// is now a parent dir of a new branch
return nil, &unableToUpdateLocalRefError{err}
}
return nil, err
}
@@ -794,6 +799,14 @@ func (e *wouldClobberExistingTagError) Unwrap() error {
return e.error
}
type unableToUpdateLocalRefError struct {
error
}
func (e *unableToUpdateLocalRefError) Unwrap() error {
return e.error
}
func (gs *gitSourceHandler) emptyGitCli(ctx context.Context, g session.Group, opts ...gitutil.Option) (*gitutil.GitCLI, func() error, error) {
var cleanups []func() error
cleanup := func() error {

View File

@@ -915,6 +915,89 @@ func testFetchMutatedTag(t *testing.T, format string, keepGitDir bool) {
ref.Release(context.TODO())
}
func TestFetchMutatedBranchSHA1(t *testing.T) {
testFetchMutatedBranch(t, "sha1", false)
}
func TestFetchMutatedBranchKeepGitDirSHA1(t *testing.T) {
testFetchMutatedBranch(t, "sha1", true)
}
func TestFetchMutatedBranchSHA256(t *testing.T) {
testFetchMutatedBranch(t, "sha256", false)
}
func TestFetchMutatedBranchKeepGitDirSHA256(t *testing.T) {
testFetchMutatedBranch(t, "sha256", true)
}
// testFetchMutatedBranch tests that if a branch is mutated in a way that previous
// ref becomes parent directory of new ref, causing collision to existing checkouts
func testFetchMutatedBranch(t *testing.T, format string, keepGitDir bool) {
ctx := t.Context()
if runtime.GOOS == "windows" {
t.Skip("Depends on unimplemented containerd bind-mount support on Windows")
}
repo := setupGitRepo(t, format)
cmd := exec.Command("git", "rev-parse", "feature")
cmd.Dir = repo.mainPath
out, err := cmd.Output()
require.NoError(t, err)
expLen := 40
if format == "sha256" {
expLen = 64
}
shaBranch := strings.TrimSpace(string(out))
require.Equal(t, expLen, len(shaBranch))
id := &GitIdentifier{Remote: repo.mainURL, Ref: "feature", KeepGitDir: keepGitDir}
gs := setupGitSource(t, t.TempDir())
g, err := gs.Resolve(ctx, id, nil, nil)
require.NoError(t, err)
key1, pin1, _, done, err := g.CacheKey(ctx, nil, 0)
require.NoError(t, err)
require.True(t, done)
ref, err := g.Snapshot(ctx, nil)
require.NoError(t, err)
ref.Release(context.TODO())
// mutate the branch to point to become parent dir
cmd = exec.Command("git", "branch", "-D", "feature")
cmd.Dir = repo.mainPath
out, err = cmd.CombinedOutput()
require.NoError(t, err, string(out))
cmd = exec.Command("git", "branch", "feature/new", shaBranch)
cmd.Dir = repo.mainPath
out, err = cmd.CombinedOutput()
require.NoError(t, err, string(out))
id = &GitIdentifier{Remote: repo.mainURL, Ref: "feature/new", KeepGitDir: keepGitDir}
g, err = gs.Resolve(ctx, id, nil, nil)
require.NoError(t, err)
key2, pin2, _, done, err := g.CacheKey(ctx, nil, 0)
require.NoError(t, err)
require.True(t, done)
if keepGitDir {
require.NotEqual(t, key1, key2) // key contains new ref
} else {
require.Equal(t, key1, key2)
}
require.Equal(t, pin1, pin2)
ref, err = g.Snapshot(ctx, nil)
require.NoError(t, err)
ref.Release(context.TODO())
}
func TestMultipleTagAccessKeepGitDirSHA1(t *testing.T) {
testMultipleTagAccess(t, true, "sha1")
}