From 64d4f4af537ae5255eefe72daee062ca78206ecc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 10:27:20 +0000 Subject: [PATCH 1/2] Consume a rename's source path when git reports it in the worktree column [patch] git status --porcelain reports a rename detected in the worktree (an intent-to-add file matched against a deleted tracked file) as " R", with the source path following as a bare entry. ListPendingChanges only checked the index column, so that source was parsed as its own record and the commit prompt listed a file that does not exist. Fixes #437 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CaZMWc5xm4vDRcyMefDXyV --- ProjectDirector.Test/CommitTests.cs | 37 +++++++++++++++++++++++++++++ ProjectDirector/GitCli.cs | 6 +++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/ProjectDirector.Test/CommitTests.cs b/ProjectDirector.Test/CommitTests.cs index 1c17242..d2830db 100644 --- a/ProjectDirector.Test/CommitTests.cs +++ b/ProjectDirector.Test/CommitTests.cs @@ -223,6 +223,43 @@ public void ARenameWhoseSourceLooksLikeARecordIsStillNotListed() } } + /// + /// A rename can also be reported in the worktree column: a file added with intent-to-add that + /// git matches against a deleted tracked file comes out as R. Its source path still + /// follows as a bare entry and must be consumed, or "ab cd.txt" is listed as "cd.txt". + /// + [TestMethod] + public void AWorktreeRenameReportsOnlyItsDestination() + { + // Arrange + string root = CreateCommittedRepository(); + + try + { + File.WriteAllText(Path.Join(root, "ab cd.txt"), "moved content\n"); + Assert.IsTrue(GitCli.RunIn(root, "add", "--all").Succeeded); + Assert.IsTrue(GitCli.RunIn(root, "commit", "-m", "second").Succeeded); + + File.Delete(Path.Join(root, "ab cd.txt")); + File.WriteAllText(Path.Join(root, "new.txt"), "moved content\n"); + Assert.IsTrue(GitCli.RunIn(root, "add", "-N", "new.txt").Succeeded, "git add -N failed."); + + GitResult status = GitCli.RunIn(root, "status", "--porcelain", "-z"); + Assert.StartsWith(" R new.txt\0", status.Output, "git should report the rename in the worktree column."); + + // Act + Collection changes = GitCli.ListPendingChanges(root); + + // Assert + Assert.AreEqual(1, changes.Count); + Assert.AreEqual("new.txt", changes[0]); + } + finally + { + Cleanup(root); + } + } + /// /// A path that is not a repository at all reports nothing rather than throwing, so the button /// degrades to "nothing to commit" instead of taking the application down. diff --git a/ProjectDirector/GitCli.cs b/ProjectDirector/GitCli.cs index 7ea1846..6d2f076 100644 --- a/ProjectDirector/GitCli.cs +++ b/ProjectDirector/GitCli.cs @@ -213,8 +213,10 @@ internal static Collection ListPendingChanges(string repositoryPath) changes.Add(entry[3..]); - // A rename or copy is recorded against the index, in the first status character. - if (entry[0] is 'R' or 'C') + // A rename or copy is usually recorded against the index, in the first status character, + // but one detected in the worktree (an intent-to-add file matched against a deleted + // tracked one) is recorded in the second. Either way the source path follows. + if (entry[0] is 'R' or 'C' || entry[1] is 'R' or 'C') { ++i; } From aac0e06b4811b0175abc61b71fdee1852eb61fd5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 10:34:44 +0000 Subject: [PATCH 2/2] Use Assert.HasCount in the worktree-rename test (MSTEST0037) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CaZMWc5xm4vDRcyMefDXyV --- ProjectDirector.Test/CommitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectDirector.Test/CommitTests.cs b/ProjectDirector.Test/CommitTests.cs index d2830db..3689dc0 100644 --- a/ProjectDirector.Test/CommitTests.cs +++ b/ProjectDirector.Test/CommitTests.cs @@ -251,7 +251,7 @@ public void AWorktreeRenameReportsOnlyItsDestination() Collection changes = GitCli.ListPendingChanges(root); // Assert - Assert.AreEqual(1, changes.Count); + Assert.HasCount(1, changes); Assert.AreEqual("new.txt", changes[0]); } finally