From 9836a6fb052311d664c514e8a34d2992aaf848bd Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Wed, 23 May 2018 16:32:03 +0300 Subject: [PATCH] git: restore case-only renames after commit via staging area --- .../checkin/GitCheckinEnvironment.java | 56 ++++++++++++++++--- .../tests/git4idea/test/GitTestAssertions.kt | 5 +- .../tests/git4idea/tests/GitCommitTest.kt | 30 +++++++++- 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java index 0be8bf728913..9096685ba472 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java @@ -74,6 +74,7 @@ import git4idea.index.GitIndexUtil; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import git4idea.util.GitFileUtils; +import gnu.trove.THashSet; import org.jetbrains.annotations.*; import javax.swing.*; @@ -301,7 +302,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { !removed.contains(getBeforePath(change))); if (!excludedStagedChanges.isEmpty()) { LOG.info("Staged changes excluded for commit: " + GitUtil.getLogString(rootPath, excludedStagedChanges)); - reset(project, root, excludedStagedChanges); + resetExcluded(project, root, excludedStagedChanges); } try { List alreadyHandledPaths = getPaths(changedWithIndex); @@ -324,12 +325,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { finally { // Stage back the changes unstaged before commit if (!excludedStagedChanges.isEmpty()) { - Set toAdd = map2SetNotNull(excludedStagedChanges, ChangesUtil::getAfterPath); - Set toRemove = map2SetNotNull(excludedStagedChanges, ChangesUtil::getBeforePath); - toRemove.removeAll(toAdd); - - LOG.debug(String.format("Restoring staged changes after commit: added: %s, removed: %s", toAdd, toRemove)); - updateIndex(project, root, toAdd, toRemove, exceptions); + restoreExcluded(project, root, excludedStagedChanges, exceptions); } } } @@ -658,8 +654,10 @@ public class GitCheckinEnvironment implements CheckinEnvironment { } - private static void reset(@NotNull Project project, @NotNull VirtualFile root, @NotNull Collection changes) throws VcsException { - Set allPaths = new HashSet<>(); + private static void resetExcluded(@NotNull Project project, + @NotNull VirtualFile root, + @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)); @@ -671,6 +669,46 @@ public class GitCheckinEnvironment implements CheckinEnvironment { } } + private static void restoreExcluded(@NotNull Project project, + @NotNull VirtualFile root, + @NotNull Collection changes, + @NotNull List exceptions) throws VcsException { + Set toAdd = new HashSet<>(); + Set toRemove = new HashSet<>(); + + for (Change change : changes) { + if (addAsCaseOnlyRename(project, root, change)) continue; + + addIfNotNull(toAdd, getAfterPath(change)); + addIfNotNull(toRemove, getBeforePath(change)); + } + toRemove.removeAll(toAdd); + + LOG.debug(String.format("Restoring staged changes after commit: added: %s, removed: %s", toAdd, toRemove)); + updateIndex(project, root, toAdd, toRemove, exceptions); + } + + private static boolean addAsCaseOnlyRename(@NotNull Project project, @NotNull VirtualFile root, @NotNull Change change) { + try { + CommitChange commitChange = new CommitChange(change); + if (!isCaseOnlyRename(commitChange)) return false; + + FilePath beforePath = assertNotNull(commitChange.beforePath); + FilePath afterPath = assertNotNull(commitChange.afterPath); + + LOG.debug(String.format("Restoring staged case-only rename after commit: %s", change)); + GitLineHandler h = new GitLineHandler(project, root, GitCommand.MV); + h.addParameters("-f", beforePath.getPath(), afterPath.getPath()); + Git.getInstance().runCommandWithoutCollectingOutput(h).getOutputOrThrow(); + return true; + } + catch (VcsException e) { + LOG.warn(e); + return false; + } + } + + public List commit(List changes, String preparedComment) { return commit(changes, preparedComment, FunctionUtil.nullConstant(), null); } diff --git a/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt b/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt index 93187a8c8210..6f534f130b13 100644 --- a/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt +++ b/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt @@ -73,10 +73,11 @@ fun GitRepository.assertCommitted(depth: Int = 1, changes: ChangesBuilder.() -> val cb = ChangesBuilder() cb.changes() - val actualChanges = GitHistoryUtils.history(project, root, "-${depth}")[depth - 1].changes + val allCanges = GitHistoryUtils.history(project, root, "-${depth}")[depth - 1].changes + val actualChanges = allCanges.toMutableSet() for (change in cb.changes) { val found = actualChanges.find(change.matcher) - PlatformTestCase.assertNotNull("The change [$change] wasn't committed", found) + PlatformTestCase.assertNotNull("The change [$change] wasn't committed\n$allCanges", found) actualChanges.remove(found) } PlatformTestCase.assertTrue(actualChanges.isEmpty()) diff --git a/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt b/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt index b4910060331e..4353d809316f 100644 --- a/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt +++ b/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt @@ -308,6 +308,31 @@ class GitCommitTest : GitSingleRepoTest() { } } + fun `test commit case rename & don't commit one staged case rename`() { + `assume version where git reset returns 0 exit code on success `() + + tac("s.java") + generateCaseRename("a.java", "A.java") + git("mv s.java S.java") + + val changes = assertChanges { + rename("a.java", "A.java") + rename("s.java", "S.java") + } + + commit(listOf(changes[0])) + + repo.assertCommitted { + rename("a.java", "A.java") + } + assertChanges { + rename("s.java", "S.java") + } + repo.assertStagedChanges { + rename("s.java", "S.java") + } + } + fun `test commit case rename & don't commit one staged simple rename, then rename should remain staged`() { `assume version where git reset returns 0 exit code on success `() @@ -540,12 +565,13 @@ class GitCommitTest : GitSingleRepoTest() { cb.changes() updateChangeListManager() + val vcsChanges = changeListManager.allChanges val allChanges = mutableListOf() - val actualChanges = HashSet(changeListManager.allChanges) + val actualChanges = HashSet(vcsChanges) for (change in cb.changes) { val found = actualChanges.find(change.matcher) - assertNotNull("The change [$change] not found", found) + assertNotNull("The change [$change] not found\n$vcsChanges", found) actualChanges.remove(found) allChanges.add(found!!) }