git4idea: Update operation is refactored to the separate class

This commit is contained in:
Constantine Plotnikov
2009-11-03 15:58:34 +03:00
parent e7cabd4603
commit 86aae04157
3 changed files with 616 additions and 349 deletions
@@ -0,0 +1,479 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package git4idea.update;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.AbstractVcsHelper;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManagerEx;
import com.intellij.openapi.vcs.changes.LocalChangeList;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ui.UIUtil;
import git4idea.GitBranch;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.changes.GitChangeUtils;
import git4idea.commands.GitHandler;
import git4idea.commands.GitHandlerUtil;
import git4idea.commands.GitLineHandler;
import git4idea.commands.GitLineHandlerAdapter;
import git4idea.i18n.GitBundle;
import git4idea.rebase.GitRebaseUtils;
import git4idea.ui.GitUIUtil;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Abstract class that implement rebase operation for several roots based on rebase operation (for example update operation)
*/
public abstract class GitBaseRebaseProcess {
/**
* The logger
*/
private static final Logger LOG = Logger.getInstance("#git4idea.update.GitUpdateProcess");
/**
* The context project
*/
protected Project myProject;
/**
* The vcs service
*/
protected GitVcs myVcs;
/**
* The exception list
*/
protected List<VcsException> myExceptions;
public GitBaseRebaseProcess(final GitVcs vcs, final Project project, List<VcsException> exceptions) {
myVcs = vcs;
myProject = project;
myExceptions = exceptions;
}
/**
* Perform rebase operation
*
* @param progressIndicator the progress indicator to use
* @param roots the vcs roots
*/
public void doUpdate(ProgressIndicator progressIndicator, Set<VirtualFile> roots) {
ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
projectManager.blockReloadingProjectOnExternalChanges();
try {
HashSet<VirtualFile> rootsToStash = new HashSet<VirtualFile>();
List<LocalChangeList> listsCopy = null;
ChangeListManagerEx changeManager = (ChangeListManagerEx)ChangeListManagerEx.getInstance(myProject);
if (isAutoStash()) {
listsCopy = changeManager.getChangeListsCopy();
for (LocalChangeList l : listsCopy) {
final Collection<Change> changeCollection = l.getChanges();
LOG.debug("Stashing " + changeCollection.size() + " changes from '" + l.getName() + "'");
for (Change c : changeCollection) {
if (c.getAfterRevision() != null) {
VirtualFile r = GitUtil.getGitRootOrNull(c.getAfterRevision().getFile());
if (r != null) {
rootsToStash.add(r);
}
}
else if (c.getBeforeRevision() != null) {
VirtualFile r = GitUtil.getGitRootOrNull(c.getBeforeRevision().getFile());
if (r != null) {
rootsToStash.add(r);
}
}
}
}
}
if (areRootsUnderRebase(roots)) return;
String stashMessage = makeStashMessage();
for (final VirtualFile root : roots) {
try {
// check if there is a remote for the branch
final GitBranch branch = GitBranch.current(myProject, root);
if (branch == null) {
continue;
}
final String value = branch.getTrackedRemoteName(myProject, root);
if (value == null || value.length() == 0) {
continue;
}
final Ref<Boolean> cancelled = new Ref<Boolean>(false);
final Ref<Throwable> ex = new Ref<Throwable>();
try {
boolean stashCreated = rootsToStash.contains(root) && GitStashUtils.saveStash(myProject, root, stashMessage);
try {
markStart(root);
try {
GitLineHandler h = makeStartHandler(root);
RebaseConflictDetector rebaseConflictDetector = new RebaseConflictDetector();
h.addLineListener(rebaseConflictDetector);
try {
GitHandlerUtil.doSynchronouslyWithExceptions(h, progressIndicator);
}
finally {
if (!rebaseConflictDetector.isRebaseConflict()) {
myExceptions.addAll(h.errors());
}
cleanupHandler(root, h);
}
while (rebaseConflictDetector.isRebaseConflict() && !cancelled.get()) {
mergeFiles(root, cancelled, ex);
//noinspection ThrowableResultOfMethodCallIgnored
if (ex.get() != null) {
//noinspection ThrowableResultOfMethodCallIgnored
throw GitUtil.rethrowVcsException(ex.get());
}
checkLocallyModified(root, cancelled, ex);
//noinspection ThrowableResultOfMethodCallIgnored
if (ex.get() != null) {
//noinspection ThrowableResultOfMethodCallIgnored
throw GitUtil.rethrowVcsException(ex.get());
}
if (cancelled.get()) {
break;
}
doRebase(progressIndicator, root, rebaseConflictDetector, "--continue");
final Ref<Integer> result = new Ref<Integer>();
noChangeLoop:
while (rebaseConflictDetector.isNoChange()) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
int rc = Messages.showDialog(myProject, GitBundle.message("update.rebase.no.change", root.getPresentableUrl()),
GitBundle.getString("update.rebase.no.change.title"),
new String[]{GitBundle.getString("update.rebase.no.change.skip"),
GitBundle.getString("update.rebase.no.change.retry"),
GitBundle.getString("update.rebase.no.change.cancel")}, 0, Messages.getErrorIcon());
result.set(rc);
}
});
switch (result.get()) {
case 0:
doRebase(progressIndicator, root, rebaseConflictDetector, "--skip");
continue noChangeLoop;
case 1:
continue noChangeLoop;
case 2:
cancelled.set(true);
break noChangeLoop;
}
}
}
if (cancelled.get()) {
//noinspection ThrowableInstanceNeverThrown
myExceptions.add(new VcsException("The update process was cancelled for " + root.getPresentableUrl()));
doRebase(progressIndicator, root, rebaseConflictDetector, "--abort");
}
}
finally {
markEnd(root, cancelled.get());
}
}
finally {
if (stashCreated) {
unstash(listsCopy, changeManager, root);
}
}
}
finally {
mergeFiles(root, cancelled, ex);
//noinspection ThrowableResultOfMethodCallIgnored
if (ex.get() != null) {
//noinspection ThrowableResultOfMethodCallIgnored
myExceptions.add(GitUtil.rethrowVcsException(ex.get()));
}
}
}
catch (VcsException ex) {
myExceptions.add(ex);
}
}
}
finally {
projectManager.unblockReloadingProjectOnExternalChanges();
}
}
/**
* Clean up the start handler
*
* @param root the root
* @param h the handler
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void cleanupHandler(VirtualFile root, GitLineHandler h) {
// do nothing by default
}
/**
* Make handler that starts operation
*
* @param root the vcs root
* @return the handler that starts rebase operation
* @throws VcsException in if there is problem with running git
*/
protected abstract GitLineHandler makeStartHandler(VirtualFile root) throws VcsException;
/**
* Unstash changes and restore them in change list
*
* @param listsCopy copy of change list
* @param changeManager the change list manager
* @param root the vcs root
*/
private void unstash(List<LocalChangeList> listsCopy, ChangeListManagerEx changeManager, VirtualFile root) {
try {
GitStashUtils.popLastStash(myProject, root);
for (LocalChangeList changeList : listsCopy) {
final Collection<Change> changes = changeList.getChanges();
if (!changes.isEmpty()) {
LOG.debug("After unstash: moving " + changes.size() + " changes to '" + changeList.getName() + "'");
changeManager.moveChangesTo(changeList, changes.toArray(new Change[changes.size()]));
}
}
}
catch (final VcsException ue) {
myExceptions.add(ue);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
GitUIUtil.showOperationError(myProject, ue, "Auto-unstash");
}
});
}
}
/**
* Mark the start of the operation
*
* @param root the vcs root
* @throws VcsException the exception
*/
protected void markStart(VirtualFile root) throws VcsException {
}
/**
* Mark the end of the operation
*
* @param root the vcs operation
* @param cancelled true if the operation was cancelled due to update operation
*/
protected void markEnd(VirtualFile root, boolean cancelled) {
}
/**
* @return a stash message for the operation
*/
protected abstract String makeStashMessage();
/**
* @return true if auto-stash is requested
*/
protected abstract boolean isAutoStash();
/**
* Check if some roots are under the rebase operation and show a message in this case
*
* @param roots the roots to check
* @return true if some roots are being rebased
*/
private boolean areRootsUnderRebase(Set<VirtualFile> roots) {
Set<VirtualFile> rebasingRoots = new TreeSet<VirtualFile>(GitUtil.VIRTUAL_FILE_COMPARATOR);
for (final VirtualFile root : roots) {
if (GitRebaseUtils.isRebaseInTheProgress(root)) {
rebasingRoots.add(root);
}
}
if (!rebasingRoots.isEmpty()) {
final StringBuilder files = new StringBuilder();
for (VirtualFile r : rebasingRoots) {
files.append(GitBundle.message("update.root.rebasing.item", r.getPresentableUrl()));
//noinspection ThrowableInstanceNeverThrown
myExceptions.add(new VcsException(GitBundle.message("update.root.rebasing", r.getPresentableUrl())));
}
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
Messages.showErrorDialog(myProject, GitBundle.message("update.root.rebasing.message", files.toString()),
GitBundle.message("update.root.rebasing.title"));
}
});
return true;
}
return false;
}
/**
* Merge files
*
* @param root the project root
* @param cancelled the cancelled indicator
* @param ex the exception holder
*/
private void mergeFiles(final VirtualFile root, final Ref<Boolean> cancelled, final Ref<Throwable> ex) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
try {
List<VirtualFile> affectedFiles = GitChangeUtils.unmergedFiles(myProject, root);
while (affectedFiles.size() != 0) {
AbstractVcsHelper.getInstance(myProject).showMergeDialog(affectedFiles, myVcs.getMergeProvider());
affectedFiles = GitChangeUtils.unmergedFiles(myProject, root);
if (affectedFiles.size() != 0) {
int result = Messages
.showYesNoDialog(myProject, GitBundle.message("update.rebase.unmerged", affectedFiles.size(), root.getPresentableUrl()),
GitBundle.getString("update.rebase.unmerged.title"), Messages.getErrorIcon());
if (result != 0) {
cancelled.set(true);
return;
}
}
}
}
catch (Throwable t) {
ex.set(t);
}
}
});
}
/**
* Check and process locally modified files
*
* @param root the project root
* @param cancelled the cancelled indicator
* @param ex the exception holder
*/
private void checkLocallyModified(final VirtualFile root, final Ref<Boolean> cancelled, final Ref<Throwable> ex) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
try {
if (!GitUpdateLocallyModifiedDialog.showIfNeeded(myProject, root)) {
cancelled.set(true);
}
}
catch (Throwable t) {
ex.set(t);
}
}
});
}
/**
* Do rebase operation as part of update operator
*
* @param progressIndicator the progress indicator for the update
* @param root the vcs root
* @param rebaseConflictDetector the detector of conflicts in rebase operation
* @param action the rebase action to execute
*/
private void doRebase(ProgressIndicator progressIndicator,
VirtualFile root,
RebaseConflictDetector rebaseConflictDetector,
final String action) {
GitLineHandler rh = new GitLineHandler(myProject, root, GitHandler.REBASE);
// ignore failure for abort
rh.ignoreErrorCode(1);
rh.addParameters(action);
rebaseConflictDetector.reset();
rh.addLineListener(rebaseConflictDetector);
if(!"--abort".equals(action)) {
configureRebaseEditor(root, rh);
}
try {
GitHandlerUtil.doSynchronouslyWithExceptions(rh, progressIndicator);
} finally {
cleanupHandler(root, rh);
}
}
/**
* Configure rebase editor
* @param root the vcs root
* @param h the handler to configure
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void configureRebaseEditor(VirtualFile root, GitLineHandler h) {
// do nothing by default
}
/**
* The detector of conflict conditions for rebase operation
*/
static class RebaseConflictDetector extends GitLineHandlerAdapter {
/**
* The line that indicates that there is a rebase conflict.
*/
private final static String[] REBASE_CONFLICT_INDICATORS = {"When you have resolved this problem run \"git rebase --continue\".",
"Automatic cherry-pick failed. After resolving the conflicts,"};
/**
* The line that indicates "no change" condition.
*/
private static final String REBASE_NO_CHANGE_INDICATOR = "No changes - did you forget to use 'git add'?";
/**
* if true, the rebase conflict happened
*/
AtomicBoolean rebaseConflict = new AtomicBoolean(false);
/**
* if true, the no changes were detected in the rebase operations
*/
AtomicBoolean noChange = new AtomicBoolean(false);
/**
* Reset detector before new operation
*/
public void reset() {
rebaseConflict.set(false);
noChange.set(false);
}
/**
* @return true if "no change" condition was detected during the operation
*/
public boolean isNoChange() {
return noChange.get();
}
/**
* @return true if conflict during rebase was detected
*/
public boolean isRebaseConflict() {
return rebaseConflict.get();
}
/**
* {@inheritDoc}
*/
@Override
public void onLineAvailable(String line, Key outputType) {
for (String i : REBASE_CONFLICT_INDICATORS) {
if (line.startsWith(i)) {
rebaseConflict.set(true);
break;
}
}
if (line.startsWith(REBASE_NO_CHANGE_INDICATOR)) {
noChange.set(true);
}
}
}
}
@@ -15,47 +15,25 @@
*/
package git4idea.update;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.AbstractVcsHelper;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManagerEx;
import com.intellij.openapi.vcs.changes.LocalChangeList;
import com.intellij.openapi.vcs.update.SequentialUpdatesContext;
import com.intellij.openapi.vcs.update.UpdateEnvironment;
import com.intellij.openapi.vcs.update.UpdateSession;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ui.UIUtil;
import git4idea.GitBranch;
import git4idea.GitRevisionNumber;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.changes.GitChangeUtils;
import git4idea.commands.GitHandler;
import git4idea.commands.GitHandlerUtil;
import git4idea.commands.GitLineHandler;
import git4idea.commands.GitLineHandlerAdapter;
import git4idea.config.GitVcsSettings;
import git4idea.i18n.GitBundle;
import git4idea.merge.MergeChangeCollector;
import git4idea.rebase.GitRebaseUtils;
import git4idea.ui.GitUIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Git update environment implementation. The environment does
@@ -63,7 +41,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
* and processed as well.
*/
public class GitUpdateEnvironment implements UpdateEnvironment {
private static final Logger LOG = Logger.getInstance("#git4idea.update.GitUpdateEnvironment");
/**
* The vcs instance
*/
@@ -104,280 +81,12 @@ public class GitUpdateEnvironment implements UpdateEnvironment {
ProgressIndicator progressIndicator,
@NotNull Ref<SequentialUpdatesContext> sequentialUpdatesContextRef)
throws ProcessCanceledException {
Set<VirtualFile> roots = GitUtil.gitRoots(Arrays.asList(filePaths));
List<VcsException> exceptions = new ArrayList<VcsException>();
ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
projectManager.blockReloadingProjectOnExternalChanges();
try {
HashSet<VirtualFile> rootsToStash = new HashSet<VirtualFile>();
List<LocalChangeList> listsCopy = null;
ChangeListManagerEx changeManager = (ChangeListManagerEx)ChangeListManagerEx.getInstance(myProject);
if (mySettings.UPDATE_STASH) {
listsCopy = changeManager.getChangeListsCopy();
for (LocalChangeList l : listsCopy) {
final Collection<Change> changeCollection = l.getChanges();
LOG.debug("Stashing " + changeCollection.size() + " changes from '" + l.getName() + "'");
for (Change c : changeCollection) {
if (c.getAfterRevision() != null) {
VirtualFile r = GitUtil.getGitRootOrNull(c.getAfterRevision().getFile());
if (r != null) {
rootsToStash.add(r);
}
}
else if (c.getBeforeRevision() != null) {
VirtualFile r = GitUtil.getGitRootOrNull(c.getBeforeRevision().getFile());
if (r != null) {
rootsToStash.add(r);
}
}
}
}
}
Set<VirtualFile> roots = GitUtil.gitRoots(Arrays.asList(filePaths));
Set<VirtualFile> rebasingRoots = new TreeSet<VirtualFile>(GitUtil.VIRTUAL_FILE_COMPARATOR);
for (final VirtualFile root : roots) {
if (GitRebaseUtils.isRebaseInTheProgress(root)) {
rebasingRoots.add(root);
}
}
if (!rebasingRoots.isEmpty()) {
final StringBuilder files = new StringBuilder();
for (VirtualFile r : rebasingRoots) {
files.append(GitBundle.message("update.root.rebasing.item", r.getPresentableUrl()));
//noinspection ThrowableInstanceNeverThrown
exceptions.add(new VcsException(GitBundle.message("update.root.rebasing", r.getPresentableUrl())));
}
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
Messages.showErrorDialog(myProject, GitBundle.message("update.root.rebasing.message", files.toString()),
GitBundle.message("update.root.rebasing.title"));
}
});
return new GitUpdateSession(exceptions);
}
String stashMessage = "Uncommitted changes before update operation at " +
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US).format(new Date());
for (final VirtualFile root : roots) {
try {
// check if there is a remote for the branch
final GitBranch branch = GitBranch.current(myProject, root);
if (branch == null) {
continue;
}
final String value = branch.getTrackedRemoteName(myProject, root);
if (value == null || value.length() == 0) {
continue;
}
final Ref<Boolean> cancelled = new Ref<Boolean>(false);
final Ref<Throwable> ex = new Ref<Throwable>();
try {
boolean stashCreated = rootsToStash.contains(root) && GitStashUtils.saveStash(myProject, root, stashMessage);
try {
// remember the current position
GitRevisionNumber before = GitRevisionNumber.resolve(myProject, root, "HEAD");
// do pull
GitLineHandler h = new GitLineHandler(myProject, root, GitHandler.PULL);
// ignore merge failure for the pull
h.ignoreErrorCode(1);
switch (mySettings.UPDATE_TYPE) {
case REBASE:
h.addParameters("--rebase");
break;
case MERGE:
h.addParameters("--no-rebase");
break;
case BRANCH_DEFAULT:
// use default for the branch
break;
default:
assert false : "Unknown update type: " + mySettings.UPDATE_TYPE;
}
h.addParameters("--no-stat");
h.addParameters("-v");
try {
RebaseConflictDetector rebaseConflictDetector = new RebaseConflictDetector();
h.addLineListener(rebaseConflictDetector);
try {
GitHandlerUtil.doSynchronouslyWithExceptions(h, progressIndicator);
}
finally {
if (!rebaseConflictDetector.isRebaseConflict()) {
exceptions.addAll(h.errors());
}
}
while (rebaseConflictDetector.isRebaseConflict() && !cancelled.get()) {
mergeFiles(root, cancelled, ex);
//noinspection ThrowableResultOfMethodCallIgnored
if (ex.get() != null) {
//noinspection ThrowableResultOfMethodCallIgnored
throw GitUtil.rethrowVcsException(ex.get());
}
checkLocallyModified(root, cancelled, ex);
//noinspection ThrowableResultOfMethodCallIgnored
if (ex.get() != null) {
//noinspection ThrowableResultOfMethodCallIgnored
throw GitUtil.rethrowVcsException(ex.get());
}
if (cancelled.get()) {
break;
}
doRebase(progressIndicator, root, rebaseConflictDetector, "--continue");
final Ref<Integer> result = new Ref<Integer>();
noChangeLoop:
while (rebaseConflictDetector.isNoChange()) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
int rc = Messages.showDialog(myProject, GitBundle.message("update.rebase.no.change", root.getPresentableUrl()),
GitBundle.getString("update.rebase.no.change.title"),
new String[]{GitBundle.getString("update.rebase.no.change.skip"),
GitBundle.getString("update.rebase.no.change.retry"),
GitBundle.getString("update.rebase.no.change.cancel")}, 0, Messages.getErrorIcon());
result.set(rc);
}
});
switch (result.get()) {
case 0:
doRebase(progressIndicator, root, rebaseConflictDetector, "--skip");
continue noChangeLoop;
case 1:
continue noChangeLoop;
case 2:
cancelled.set(true);
break noChangeLoop;
}
}
}
if (cancelled.get()) {
//noinspection ThrowableInstanceNeverThrown
exceptions.add(new VcsException("The update process was cancelled for " + root.getPresentableUrl()));
doRebase(progressIndicator, root, rebaseConflictDetector, "--abort");
}
}
finally {
if (!cancelled.get()) {
// find out what have changed
MergeChangeCollector collector = new MergeChangeCollector(myProject, root, before, updatedFiles);
collector.collect(exceptions);
}
}
}
finally {
if (stashCreated) {
try {
GitStashUtils.popLastStash(myProject, root);
for (LocalChangeList changeList : listsCopy) {
final Collection<Change> changes = changeList.getChanges();
if (! changes.isEmpty()) {
LOG.debug("After unstash: moving " + changes.size() + " changes to '" + changeList.getName() + "'");
changeManager.moveChangesTo(changeList, changes.toArray(new Change[changes.size()]));
}
}
}
catch (final VcsException ue) {
exceptions.add(ue);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
GitUIUtil.showOperationError(myProject, ue, "Auto-unstash");
}
});
}
}
}
}
finally {
mergeFiles(root, cancelled, ex);
//noinspection ThrowableResultOfMethodCallIgnored
if (ex.get() != null) {
//noinspection ThrowableResultOfMethodCallIgnored
exceptions.add(GitUtil.rethrowVcsException(ex.get()));
}
}
}
catch (VcsException ex) {
exceptions.add(ex);
}
}
}
finally {
projectManager.unblockReloadingProjectOnExternalChanges();
}
new GitUpdateProcess(myProject, mySettings, myVcs, updatedFiles, exceptions).doUpdate( progressIndicator, roots);
return new GitUpdateSession(exceptions);
}
/**
* Merge files
*
* @param root the project root
* @param cancelled the cancelled indicator
* @param ex the exception holder
*/
private void mergeFiles(final VirtualFile root, final Ref<Boolean> cancelled, final Ref<Throwable> ex) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
try {
List<VirtualFile> affectedFiles = GitChangeUtils.unmergedFiles(myProject, root);
while (affectedFiles.size() != 0) {
AbstractVcsHelper.getInstance(myProject).showMergeDialog(affectedFiles, myVcs.getMergeProvider());
affectedFiles = GitChangeUtils.unmergedFiles(myProject, root);
if (affectedFiles.size() != 0) {
int result = Messages
.showYesNoDialog(myProject, GitBundle.message("update.rebase.unmerged", affectedFiles.size(), root.getPresentableUrl()),
GitBundle.getString("update.rebase.unmerged.title"), Messages.getErrorIcon());
if (result != 0) {
cancelled.set(true);
return;
}
}
}
}
catch (Throwable t) {
ex.set(t);
}
}
});
}
/**
* Check and process locally modified files
*
* @param root the project root
* @param cancelled the cancelled indicator
* @param ex the exception holder
*/
private void checkLocallyModified(final VirtualFile root, final Ref<Boolean> cancelled, final Ref<Throwable> ex) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
try {
if (!GitUpdateLocallyModifiedDialog.showIfNeeded(myProject, root)) {
cancelled.set(true);
}
}
catch (Throwable t) {
ex.set(t);
}
}
});
}
/**
* Do rebase operation as part of update operator
*
* @param progressIndicator the progress indicator for the update
* @param root the vcs root
* @param rebaseConflictDetector the detector of conflicts in rebase operation
* @param action the rebase action to execute
*/
private void doRebase(ProgressIndicator progressIndicator,
VirtualFile root,
RebaseConflictDetector rebaseConflictDetector,
final String action) {
GitLineHandler rh = new GitLineHandler(myProject, root, GitHandler.REBASE);
// ignore failure for abort
rh.ignoreErrorCode(1);
rh.addParameters(action);
rebaseConflictDetector.reset();
rh.addLineListener(rebaseConflictDetector);
GitHandlerUtil.doSynchronouslyWithExceptions(rh, progressIndicator);
}
/**
* {@inheritDoc}
@@ -399,60 +108,4 @@ public class GitUpdateEnvironment implements UpdateEnvironment {
return new GitUpdateConfigurable(mySettings);
}
/**
* The detector of conflict conditions for rebase operation
*/
static class RebaseConflictDetector extends GitLineHandlerAdapter {
/**
* The line that indicates that there is a rebase conflict.
*/
private final static String REBASE_CONFLICT_INDICATOR = "When you have resolved this problem run \"git rebase --continue\".";
/**
* The line that indicates "no change" condition.
*/
private static final String REBASE_NO_CHANGE_INDICATOR = "No changes - did you forget to use 'git add'?";
/**
* if true, the rebase conflict happened
*/
AtomicBoolean rebaseConflict = new AtomicBoolean(false);
/**
* if true, the no changes were detected in the rebase operations
*/
AtomicBoolean noChange = new AtomicBoolean(false);
/**
* Reset detector before new operation
*/
public void reset() {
rebaseConflict.set(false);
noChange.set(false);
}
/**
* @return true if "no change" condition was detected during the operation
*/
public boolean isNoChange() {
return noChange.get();
}
/**
* @return true if conflict during rebase was detected
*/
public boolean isRebaseConflict() {
return rebaseConflict.get();
}
/**
* {@inheritDoc}
*/
@Override
public void onLineAvailable(String line, Key outputType) {
if (line.startsWith(REBASE_CONFLICT_INDICATOR)) {
rebaseConflict.set(true);
}
if (line.startsWith(REBASE_NO_CHANGE_INDICATOR)) {
noChange.set(true);
}
}
}
}
@@ -0,0 +1,135 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package git4idea.update;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitRevisionNumber;
import git4idea.GitVcs;
import git4idea.commands.GitHandler;
import git4idea.commands.GitLineHandler;
import git4idea.config.GitVcsSettings;
import git4idea.merge.MergeChangeCollector;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* This class encapsulates update operation from update environment. This allows to customize this operation when needed.
*/
public class GitUpdateProcess extends GitBaseRebaseProcess {
/**
* The settings to use
*/
private GitVcsSettings mySettings;
/**
* The updated files
*/
private UpdatedFiles myUpdatedFiles;
/**
* The revision that was before update
*/
private GitRevisionNumber myBefore;
/**
* The constructor
*
* @param project the project instance
* @param settings the git vcs settings
* @param vcs the git vcs instance
* @param updatedFiles the collection where set of updated files is shown
* @param exceptions the collection with exceptions
*/
public GitUpdateProcess(final Project project,
GitVcsSettings settings,
final GitVcs vcs,
UpdatedFiles updatedFiles,
List<VcsException> exceptions) {
super(vcs, project, exceptions);
mySettings = settings;
myUpdatedFiles = updatedFiles;
}
/**
* {@inheritDoc}
*/
@Override
protected GitLineHandler makeStartHandler(VirtualFile root) {
// do pull
GitLineHandler h = new GitLineHandler(myProject, root, GitHandler.PULL);
// ignore merge failure for the pull
h.ignoreErrorCode(1);
switch (mySettings.UPDATE_TYPE) {
case REBASE:
h.addParameters("--rebase");
break;
case MERGE:
h.addParameters("--no-rebase");
break;
case BRANCH_DEFAULT:
// use default for the branch
break;
default:
assert false : "Unknown update type: " + mySettings.UPDATE_TYPE;
}
h.addParameters("--no-stat");
h.addParameters("-v");
return h;
}
/**
* {@inheritDoc}
*/
@Override
protected void markStart(VirtualFile root) throws VcsException {
// remember the current position
myBefore = GitRevisionNumber.resolve(myProject, root, "HEAD");
}
/**
* {@inheritDoc}
*/
@Override
protected void markEnd(VirtualFile root, final boolean cancelled) {
if (!cancelled) {
// find out what have changed
MergeChangeCollector collector = new MergeChangeCollector(myProject, root, myBefore, myUpdatedFiles);
collector.collect(myExceptions);
}
}
/**
* {@inheritDoc}
*/
@Override
protected boolean isAutoStash() {
return mySettings.UPDATE_STASH;
}
/**
* {@inheritDoc}
*/
@Override
protected String makeStashMessage() {
return "Uncommitted changes before update operation at " +
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US).format(new Date());
}
}