From 86aae04157d0b49fcfdd80629f21f646acc2dd30 Mon Sep 17 00:00:00 2001 From: Constantine Plotnikov Date: Mon, 2 Nov 2009 23:09:31 +0300 Subject: [PATCH] git4idea: Update operation is refactored to the separate class --- .../git4idea/update/GitBaseRebaseProcess.java | 479 ++++++++++++++++++ .../git4idea/update/GitUpdateEnvironment.java | 351 +------------ .../src/git4idea/update/GitUpdateProcess.java | 135 +++++ 3 files changed, 616 insertions(+), 349 deletions(-) create mode 100644 plugins/git4idea/src/git4idea/update/GitBaseRebaseProcess.java create mode 100644 plugins/git4idea/src/git4idea/update/GitUpdateProcess.java diff --git a/plugins/git4idea/src/git4idea/update/GitBaseRebaseProcess.java b/plugins/git4idea/src/git4idea/update/GitBaseRebaseProcess.java new file mode 100644 index 000000000000..1fcdb278dcd1 --- /dev/null +++ b/plugins/git4idea/src/git4idea/update/GitBaseRebaseProcess.java @@ -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 myExceptions; + + public GitBaseRebaseProcess(final GitVcs vcs, final Project project, List 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 roots) { + ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); + projectManager.blockReloadingProjectOnExternalChanges(); + try { + HashSet rootsToStash = new HashSet(); + List listsCopy = null; + ChangeListManagerEx changeManager = (ChangeListManagerEx)ChangeListManagerEx.getInstance(myProject); + if (isAutoStash()) { + listsCopy = changeManager.getChangeListsCopy(); + for (LocalChangeList l : listsCopy) { + final Collection 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 cancelled = new Ref(false); + final Ref ex = new Ref(); + 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 result = new Ref(); + 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 listsCopy, ChangeListManagerEx changeManager, VirtualFile root) { + try { + GitStashUtils.popLastStash(myProject, root); + for (LocalChangeList changeList : listsCopy) { + final Collection 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 roots) { + Set rebasingRoots = new TreeSet(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 cancelled, final Ref ex) { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + public void run() { + try { + List 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 cancelled, final Ref 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); + } + } + } +} diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateEnvironment.java b/plugins/git4idea/src/git4idea/update/GitUpdateEnvironment.java index 221e57f07130..5e7a4eeb11a4 100644 --- a/plugins/git4idea/src/git4idea/update/GitUpdateEnvironment.java +++ b/plugins/git4idea/src/git4idea/update/GitUpdateEnvironment.java @@ -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 sequentialUpdatesContextRef) throws ProcessCanceledException { + Set roots = GitUtil.gitRoots(Arrays.asList(filePaths)); List exceptions = new ArrayList(); - ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); - projectManager.blockReloadingProjectOnExternalChanges(); - try { - HashSet rootsToStash = new HashSet(); - List listsCopy = null; - ChangeListManagerEx changeManager = (ChangeListManagerEx)ChangeListManagerEx.getInstance(myProject); - if (mySettings.UPDATE_STASH) { - listsCopy = changeManager.getChangeListsCopy(); - for (LocalChangeList l : listsCopy) { - final Collection 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 roots = GitUtil.gitRoots(Arrays.asList(filePaths)); - Set rebasingRoots = new TreeSet(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 cancelled = new Ref(false); - final Ref ex = new Ref(); - 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 result = new Ref(); - 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 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 cancelled, final Ref ex) { - UIUtil.invokeAndWaitIfNeeded(new Runnable() { - public void run() { - try { - List 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 cancelled, final Ref 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); - } - } - } } diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java new file mode 100644 index 000000000000..b1007603e9ea --- /dev/null +++ b/plugins/git4idea/src/git4idea/update/GitUpdateProcess.java @@ -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 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()); + } +}