git: restore case-only renames after commit via staging area

This commit is contained in:
Aleksey Pivovarov
2018-05-23 17:40:12 +03:00
parent 3b3bd266e9
commit 9836a6fb05
3 changed files with 78 additions and 13 deletions
@@ -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<FilePath> alreadyHandledPaths = getPaths(changedWithIndex);
@@ -324,12 +325,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment {
finally {
// Stage back the changes unstaged before commit
if (!excludedStagedChanges.isEmpty()) {
Set<FilePath> toAdd = map2SetNotNull(excludedStagedChanges, ChangesUtil::getAfterPath);
Set<FilePath> 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<Change> changes) throws VcsException {
Set<FilePath> allPaths = new HashSet<>();
private static void resetExcluded(@NotNull Project project,
@NotNull VirtualFile root,
@NotNull Collection<Change> changes) throws VcsException {
Set<FilePath> 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<Change> changes,
@NotNull List<VcsException> exceptions) throws VcsException {
Set<FilePath> toAdd = new HashSet<>();
Set<FilePath> 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<VcsException> commit(List<Change> changes, String preparedComment) {
return commit(changes, preparedComment, FunctionUtil.nullConstant(), null);
}
@@ -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())
@@ -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<Change>()
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!!)
}