diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java index 9096685ba472..bbc37f4dc0e7 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java @@ -289,7 +289,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment { try { Set added = map2SetNotNull(rootChanges, it -> it.afterPath); Set removed = map2SetNotNull(rootChanges, it -> it.beforePath); - removed.removeAll(added); String rootPath = root.getPath(); @@ -298,10 +297,16 @@ public class GitCheckinEnvironment implements CheckinEnvironment { LOG.debug("Found staged changes: " + GitUtil.getLogString(rootPath, stagedChanges)); // Reset staged changes which are not selected for commit - Collection excludedStagedChanges = filter(stagedChanges, change -> !added.contains(getAfterPath(change)) && - !removed.contains(getBeforePath(change))); + Collection excludedStagedChanges = mapNotNull(stagedChanges, change -> { + FilePath before = getBeforePath(change); + FilePath after = getAfterPath(change); + if (removed.contains(before)) before = null; + if (added.contains(after)) after = null; + return before != null || after != null ? new CommitChange(before, after) : null; + }); + if (!excludedStagedChanges.isEmpty()) { - LOG.info("Staged changes excluded for commit: " + GitUtil.getLogString(rootPath, excludedStagedChanges)); + LOG.info("Staged changes excluded for commit: " + getLogString(rootPath, excludedStagedChanges)); resetExcluded(project, root, excludedStagedChanges); } try { @@ -311,6 +316,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { toAdd.removeAll(alreadyHandledPaths); Set toRemove = new HashSet<>(removed); + toRemove.removeAll(toAdd); toRemove.removeAll(alreadyHandledPaths); LOG.debug(String.format("Updating index: added: %s, removed: %s", toAdd, toRemove)); @@ -656,10 +662,12 @@ public class GitCheckinEnvironment implements CheckinEnvironment { private static void resetExcluded(@NotNull Project project, @NotNull VirtualFile root, - @NotNull Collection changes) throws VcsException { + @NotNull Collection changes) throws VcsException { Set allPaths = new THashSet<>(CASE_SENSITIVE_FILE_PATH_HASHING_STRATEGY); - allPaths.addAll(mapNotNull(changes, ChangesUtil::getAfterPath)); - allPaths.addAll(mapNotNull(changes, ChangesUtil::getBeforePath)); + for (CommitChange change : changes) { + addIfNotNull(allPaths, change.afterPath); + addIfNotNull(allPaths, change.beforePath); + } for (List paths : VcsFileUtil.chunkPaths(root, allPaths)) { GitLineHandler handler = new GitLineHandler(project, root, GitCommand.RESET); @@ -671,16 +679,16 @@ public class GitCheckinEnvironment implements CheckinEnvironment { private static void restoreExcluded(@NotNull Project project, @NotNull VirtualFile root, - @NotNull Collection changes, + @NotNull Collection changes, @NotNull List exceptions) throws VcsException { Set toAdd = new HashSet<>(); Set toRemove = new HashSet<>(); - for (Change change : changes) { + for (CommitChange change : changes) { if (addAsCaseOnlyRename(project, root, change)) continue; - addIfNotNull(toAdd, getAfterPath(change)); - addIfNotNull(toRemove, getBeforePath(change)); + addIfNotNull(toAdd, change.afterPath); + addIfNotNull(toRemove, change.beforePath); } toRemove.removeAll(toAdd); @@ -688,13 +696,12 @@ public class GitCheckinEnvironment implements CheckinEnvironment { updateIndex(project, root, toAdd, toRemove, exceptions); } - private static boolean addAsCaseOnlyRename(@NotNull Project project, @NotNull VirtualFile root, @NotNull Change change) { + private static boolean addAsCaseOnlyRename(@NotNull Project project, @NotNull VirtualFile root, @NotNull CommitChange change) { try { - CommitChange commitChange = new CommitChange(change); - if (!isCaseOnlyRename(commitChange)) return false; + if (!isCaseOnlyRename(change)) return false; - FilePath beforePath = assertNotNull(commitChange.beforePath); - FilePath afterPath = assertNotNull(commitChange.afterPath); + FilePath beforePath = assertNotNull(change.beforePath); + FilePath afterPath = assertNotNull(change.afterPath); LOG.debug(String.format("Restoring staged case-only rename after commit: %s", change)); GitLineHandler h = new GitLineHandler(project, root, GitCommand.MV); @@ -1373,6 +1380,11 @@ public class GitCheckinEnvironment implements CheckinEnvironment { } } + public CommitChange(@Nullable FilePath beforePath, + @Nullable FilePath afterPath) { + this(beforePath, afterPath, null, null, null, null); + } + public CommitChange(@Nullable FilePath beforePath, @Nullable FilePath afterPath, @Nullable VcsRevisionNumber beforeRevision, diff --git a/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt b/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt index 4353d809316f..3a458d1fbfab 100644 --- a/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt +++ b/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt @@ -417,7 +417,7 @@ class GitCommitTest : GitSingleRepoTest() { modified("c.java") } - val expectedIndexContent = if (SystemInfo.isFileSystemCaseSensitive) { + val expectedIndexContent = if (SystemInfo.isFileSystemCaseSensitive && !Registry.`is`("git.force.commit.using.staging.area")) { STAGED_CONTENT } else { @@ -531,6 +531,41 @@ class GitCommitTest : GitSingleRepoTest() { } } + fun `test commit rename with conflicting staged rename`() { + `assume version where git reset returns 0 exit code on success `() + assumeTrue(Registry.`is`("git.force.commit.using.staging.area")) + + tac("a.txt", "file content") + + rm("a.txt") + touch("b.txt", "file content") + touch("c.txt", "file content") + git("add a.txt") + git("add b.txt") + + val changes = assertChanges { + rename("a.txt", "b.txt") + } + + git("add c.txt") + git("rm b.txt --cached") + assertChanges { + rename("a.txt", "c.txt") + } + + commit(changes) + + assertChanges { + added("c.txt") + } + + assertMessage("comment", repo.message("HEAD")) + + repo.assertCommitted { + rename("a.txt", "b.txt") + } + } + private fun `assume version where git reset returns 0 exit code on success `() { assumeTrue("Not testing: git reset returns 1 and fails the commit process in ${vcs.version}", vcs.version.isLaterOrEqual(GitVersion(1, 8, 2, 0)))