IDEA-87116 Cancel push if merge conflicts occur during auto-update while pushing

* Introduce several new GitUpdateResults for different situations, including SUCCESS_WITH_RESOLVED_CONFLICTS for the one from the bugreport.
* Return GitUpdateResult from GitUpdateProcess.
* In GitPusher if update happened with conflict resolving, don't proceed with push but show a notification.
This commit is contained in:
Kirill Likhodedov
2012-06-06 18:49:27 +04:00
parent 35f63d6d5a
commit 99558ec20c
6 changed files with 81 additions and 30 deletions
@@ -15,6 +15,7 @@
*/
package git4idea.push;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -45,6 +46,7 @@ import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.settings.GitPushSettings;
import git4idea.update.GitUpdateProcess;
import git4idea.update.GitUpdateResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -546,12 +548,31 @@ public final class GitPusher {
private boolean update(@NotNull Collection<GitRepository> rootsToUpdate, @NotNull UpdateMethod updateMethod) {
GitUpdateProcess.UpdateMethod um = updateMethod == UpdateMethod.MERGE ? GitUpdateProcess.UpdateMethod.MERGE : GitUpdateProcess.UpdateMethod.REBASE;
boolean updateResult = new GitUpdateProcess(myProject, myProgressIndicator, new HashSet<GitRepository>(rootsToUpdate),
GitUpdateResult updateResult = new GitUpdateProcess(myProject, myProgressIndicator, new HashSet<GitRepository>(rootsToUpdate),
UpdatedFiles.create()).update(um);
for (GitRepository repository : rootsToUpdate) {
repository.getRoot().refresh(true, true);
}
return updateResult;
if (updateResult == GitUpdateResult.SUCCESS) {
return true;
}
else if (updateResult == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS || updateResult == GitUpdateResult.INCOMPLETE) {
String title = "Push cancelled";
String description;
if (updateResult == GitUpdateResult.INCOMPLETE) {
description = "Push has been cancelled, because not all conflicts were resolved during update.<br/>" +
"Resolve the conflicts and invoke push again.";
}
else {
description = "Push has been cancelled, because there were conflicts during update.<br/>" +
"Check that conflicts were resolved correctly, and invoke push again.";
}
new Notification(GitVcs.MINOR_NOTIFICATION.getDisplayId(), title, description, NotificationType.WARNING).notify(myProject);
return false;
}
else {
return false;
}
}
}
@@ -113,7 +113,7 @@ public class GitMergeUpdater extends GitUpdater {
LOG.info("Conflict detected");
final boolean allMerged =
new MyConflictResolver(myProject, myGit, merger, myRoot).merge();
return allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE;
return allMerged ? GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS : GitUpdateResult.INCOMPLETE;
}
else if (error == MergeError.LOCAL_CHANGES) {
LOG.info("Local changes would be overwritten by merge");
@@ -105,7 +105,7 @@ public class GitRebaseUpdater extends GitUpdater {
if (rebaseConflictDetector.isMergeConflict()) {
LOG.info("handleRebaseFailure merge conflict");
final boolean allMerged = new MyConflictResolver(myProject, myGit, myRoot, myRebaser).merge();
return allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE;
return allMerged ? GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS : GitUpdateResult.INCOMPLETE;
} else if (untrackedWouldBeOverwrittenDetector.wasMessageDetected()) {
LOG.info("handleRebaseFailure: untracked files would be overwritten by checkout");
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, ServiceManager.getService(myProject, PlatformFacade.class),
@@ -66,7 +66,7 @@ public class GitUpdateEnvironment implements UpdateEnvironment {
GitRepositoryManager repositoryManager = getRepositoryManager(myProject);
final GitUpdateProcess gitUpdateProcess = new GitUpdateProcess(myProject, progressIndicator,
getRepositoriesFromRoots(repositoryManager, roots), updatedFiles);
boolean result = gitUpdateProcess.update(GitUpdateProcess.UpdateMethod.READ_FROM_SETTINGS);
boolean result = gitUpdateProcess.update(GitUpdateProcess.UpdateMethod.READ_FROM_SETTINGS).isSuccess();
return new GitUpdateSession(result);
}
@@ -42,6 +42,7 @@ import git4idea.repo.GitBranchTrackInfo;
import git4idea.repo.GitRepository;
import git4idea.stash.GitChangesSaver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
@@ -67,7 +68,7 @@ public class GitUpdateProcess {
private final GitChangesSaver mySaver;
private final Map<VirtualFile, GitBranchPair> myTrackedBranches = new HashMap<VirtualFile, GitBranchPair>();
private boolean myResult;
private GitUpdateResult myResult;
private final Map<VirtualFile, GitUpdater> myUpdaters;
private final Collection<VirtualFile> myRootsToSave;
@@ -94,12 +95,7 @@ public class GitUpdateProcess {
/**
* Checks if update is possible, saves local changes and updates all roots.
* In case of error shows notification and returns false. If update completes without errors, returns true.
*/
public boolean update() {
return update(UpdateMethod.READ_FROM_SETTINGS);
}
/**
*
* Perform update on all roots.
* 0. Blocks reloading project on external change, saving/syncing on frame deactivation.
* 1. Checks if update is possible (rebase/merge in progress, no tracked branches...) and provides merge dialog to solve problems.
@@ -110,18 +106,19 @@ public class GitUpdateProcess {
* local changes are not restored.
*
*/
public boolean update(final UpdateMethod updateMethod) {
@NotNull
public GitUpdateResult update(final UpdateMethod updateMethod) {
LOG.info("update started|" + updateMethod);
String oldText = myProgressIndicator.getText();
myProgressIndicator.setText("Updating...");
// check if update is possible
if (checkRebaseInProgress() || isMergeInProgress() || areUnmergedFiles() || !checkTrackedBranchesConfigured()) {
return false;
return GitUpdateResult.NOT_READY;
}
if (!fetchAndNotify()) {
return false;
return GitUpdateResult.NOT_READY;
}
GitComplexProcess.Operation updateOperation = new GitComplexProcess.Operation() {
@@ -135,7 +132,8 @@ public class GitUpdateProcess {
return myResult;
}
private boolean updateImpl(UpdateMethod updateMethod, ContinuationContext context) {
@NotNull
private GitUpdateResult updateImpl(UpdateMethod updateMethod, ContinuationContext context) {
// define updaters for roots
LOG.info("updateImpl: defining updaters...");
try {
@@ -158,10 +156,10 @@ public class GitUpdateProcess {
} catch (VcsException e) {
LOG.info(e);
notifyError(myProject, "Git update failed", e.getMessage(), true, e);
return false;
return GitUpdateResult.ERROR;
}
if (myUpdaters.isEmpty()) return true;
if (myUpdaters.isEmpty()) return GitUpdateResult.NOTHING_TO_UPDATE;
// save local changes if needed (update via merge may perform without saving).
LOG.info("updateImpl: identifying if save is needed...");
@@ -182,13 +180,13 @@ public class GitUpdateProcess {
notifyError(myProject, "Git update failed",
"Tried to save uncommitted changes in " + mySaver.getSaverName() + " before update, but failed with an error.<br/>" +
"Update was cancelled.", true, e);
return false;
return GitUpdateResult.ERROR;
}
// update each root
LOG.info("updateImpl: updating...");
boolean incomplete = false;
boolean success = true;
GitUpdateResult compoundResult = null;
VirtualFile currentlyUpdatedRoot = null;
try {
for (Map.Entry<VirtualFile, GitUpdater> entry : myUpdaters.entrySet()) {
@@ -199,7 +197,7 @@ public class GitUpdateProcess {
if (res == GitUpdateResult.INCOMPLETE) {
incomplete = true;
}
success &= res.isSuccess();
compoundResult = joinResults(compoundResult, res);
}
} catch (VcsException e) {
String rootName = (currentlyUpdatedRoot == null) ? "" : currentlyUpdatedRoot.getName();
@@ -207,7 +205,7 @@ public class GitUpdateProcess {
notifyImportantError(myProject, "Error updating " + rootName,
"Updating " + rootName + " failed with an error: " + e.getLocalizedMessage());
} finally {
if (incomplete || !success) {
if (incomplete || !!compoundResult.isSuccess()) {
mySaver.notifyLocalChangesAreNotRestored();
}
else {
@@ -215,7 +213,15 @@ public class GitUpdateProcess {
restoreLocalChanges(context);
}
}
return success;
return compoundResult;
}
@NotNull
private static GitUpdateResult joinResults(@Nullable GitUpdateResult compoundResult, GitUpdateResult result) {
if (compoundResult == null) {
return result;
}
return compoundResult.join(result);
}
private void restoreLocalChanges(ContinuationContext context) {
@@ -15,19 +15,43 @@
*/
package git4idea.update;
import org.jetbrains.annotations.NotNull;
/**
* @author Kirill Likhodedov
*/
public enum GitUpdateResult {
SUCCESS,
/** User cancelled update, everything that has changed was rolled back (git rebase/merge --abort) */
CANCEL,
/** exception happened during update */
ERROR,
/** Nothing to update. */
NOTHING_TO_UPDATE(1),
/** Successful update, without merge conflict resolution during update. */
SUCCESS(2),
/** Update introduced a merge conflict, that was immediately resolved by user. */
SUCCESS_WITH_RESOLVED_CONFLICTS(3),
/** Update introduced a merge conflict that wasn't immediately resolved. */
INCOMPLETE;
INCOMPLETE(4),
/** User cancelled update, everything that has changed was rolled back (git rebase/merge --abort) */
CANCEL(5),
/** An error happened during update */
ERROR(6),
/** Update is not possible due to a configuration error or because of a failed fetch. */
NOT_READY(7);
private final int myPriority;
GitUpdateResult(int priority) {
myPriority = priority;
}
public boolean isSuccess() {
return this == SUCCESS || this == INCOMPLETE;
return this == SUCCESS || this == SUCCESS_WITH_RESOLVED_CONFLICTS || this == INCOMPLETE || this == NOTHING_TO_UPDATE;
}
@NotNull
public GitUpdateResult join(@NotNull GitUpdateResult next) {
if (myPriority >= next.myPriority) {
return this;
}
return next;
}
}