diff --git a/core/internal/plugins/registry.go b/core/internal/plugins/registry.go index d8989318d..7c23bde40 100644 --- a/core/internal/plugins/registry.go +++ b/core/internal/plugins/registry.go @@ -62,7 +62,7 @@ func (g *realGitClient) Pull(path string) error { } err = worktree.Pull(&git.PullOptions{}) - if err != nil && err.Error() != "already up-to-date" { + if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { return err } @@ -116,12 +116,30 @@ func (g *realGitClient) HasUpdates(path string) (bool, string, string, error) { } } - localHash := head.Hash().String() + localHash := head.Hash() if remoteHead == "" { - return false, localHash, "", nil + return false, localHash.String(), "", nil + } + if localHash.String() == remoteHead { + return false, localHash.String(), remoteHead, nil } - return localHash != remoteHead, localHash, remoteHead, nil + remoteCommit, err := repo.CommitObject(plumbing.NewHash(remoteHead)) + if errors.Is(err, plumbing.ErrObjectNotFound) { + return true, localHash.String(), remoteHead, nil + } + if err != nil { + return false, "", "", err + } + headCommit, err := repo.CommitObject(localHash) + if err != nil { + return false, "", "", err + } + contained, err := remoteCommit.IsAncestor(headCommit) + if err != nil { + return false, "", "", err + } + return !contained, localHash.String(), remoteHead, nil } func (g *realGitClient) CurrentRevision(path string) (string, error) { diff --git a/core/internal/plugins/registry_test.go b/core/internal/plugins/registry_test.go index f0f2ba41f..c3a502a31 100644 --- a/core/internal/plugins/registry_test.go +++ b/core/internal/plugins/registry_test.go @@ -652,3 +652,37 @@ func TestRealGitClientChecksUpdatesWithoutFetching(t *testing.T) { _, _, _, err = client.HasUpdates(localPath) assert.Error(t, err) } + +func TestRealGitClientHasUpdatesIgnoresLocalAheadAfterRemoteRewind(t *testing.T) { + t.Setenv("GIT_CONFIG_GLOBAL", "") + t.Setenv("GIT_CONFIG_SYSTEM", "") + remotePath := t.TempDir() + remote, err := git.PlainInit(remotePath, false) + require.NoError(t, err) + worktree, err := remote.Worktree() + require.NoError(t, err) + signature := &object.Signature{Name: "DMS Test", Email: "test@example.com", When: time.Unix(1, 0)} + commit := func(content string) plumbing.Hash { + t.Helper() + require.NoError(t, os.WriteFile(filepath.Join(remotePath, "value.txt"), []byte(content), 0o644)) + _, err := worktree.Add("value.txt") + require.NoError(t, err) + hash, err := worktree.Commit(content, &git.CommitOptions{Author: signature}) + require.NoError(t, err) + return hash + } + first := commit("one") + second := commit("two") + localPath := t.TempDir() + _, err = git.PlainClone(localPath, &git.CloneOptions{URL: remotePath}) + require.NoError(t, err) + + require.NoError(t, remote.Storer.SetReference(plumbing.NewHashReference(plumbing.NewBranchReferenceName("master"), first))) + + client := &realGitClient{} + updated, localHash, remoteHash, err := client.HasUpdates(localPath) + require.NoError(t, err) + assert.False(t, updated) + assert.Equal(t, second.String(), localHash) + assert.Equal(t, first.String(), remoteHash) +}