git: fix commit via staging area with conflicting renames in git index

This commit is contained in:
Aleksey Pivovarov
2018-06-13 18:08:28 +03:00
parent 717174e1c2
commit be2bcd0578
2 changed files with 64 additions and 17 deletions
@@ -289,7 +289,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment {
try {
Set<FilePath> added = map2SetNotNull(rootChanges, it -> it.afterPath);
Set<FilePath> 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<Change> excludedStagedChanges = filter(stagedChanges, change -> !added.contains(getAfterPath(change)) &&
!removed.contains(getBeforePath(change)));
Collection<CommitChange> 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<FilePath> 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<Change> changes) throws VcsException {
@NotNull Collection<CommitChange> 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));
for (CommitChange change : changes) {
addIfNotNull(allPaths, change.afterPath);
addIfNotNull(allPaths, change.beforePath);
}
for (List<String> 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<Change> changes,
@NotNull Collection<CommitChange> changes,
@NotNull List<VcsException> exceptions) throws VcsException {
Set<FilePath> toAdd = new HashSet<>();
Set<FilePath> 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,
@@ -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)))