git: don't stash/shelve before update if possible.
authorKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Mon, 28 Feb 2011 14:11:21 +0000 (17:11 +0300)
committerKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Thu, 10 Mar 2011 16:09:03 +0000 (19:09 +0300)
Update via rebase always needs clean working tree.
Update via merge can be performed on a dirty working tree, if local changes don't intersect with remote changes.

GitUpdateProcess: before update ask each updater if save is needed.
GitMergeUpdater performs 'git fetch' and analyzes which files would change.
GitMergeUpdater.update is changed to make 'git merge' instead of 'git pull'.

plugins/git4idea/src/git4idea/branch/GitBranchPair.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/update/GitChangesSaver.java
plugins/git4idea/src/git4idea/update/GitMergeUpdater.java
plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java
plugins/git4idea/src/git4idea/update/GitShelveChangesSaver.java
plugins/git4idea/src/git4idea/update/GitStashChangesSaver.java
plugins/git4idea/src/git4idea/update/GitUpdateProcess.java
plugins/git4idea/src/git4idea/update/GitUpdater.java

diff --git a/plugins/git4idea/src/git4idea/branch/GitBranchPair.java b/plugins/git4idea/src/git4idea/branch/GitBranchPair.java
new file mode 100644 (file)
index 0000000..be898be
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2011 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.branch;
+
+import git4idea.GitBranch;
+
+/**
+ * Holder for branch and its tracked branch (if any).
+ *
+ * @author Kirill Likhodedov
+ */
+public class GitBranchPair {
+  private GitBranch myBranch;
+  private GitBranch myTrackedName;
+
+  public GitBranchPair(GitBranch branch, GitBranch tracked) {
+    myBranch = branch;
+    myTrackedName = tracked;
+  }
+
+  public GitBranch getBranch() {
+    return myBranch;
+  }
+
+  public GitBranch getTracked() {
+    return myTrackedName;
+  }
+
+}
index 5fbbeacba8561435345dad1806d6a690080fca0c..68a1b9a2d6f0b6f8daff0ea8b85a28f390bacc54 100644 (file)
@@ -26,11 +26,13 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.*;
+import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.ui.UIUtil;
 import git4idea.GitVcs;
 import git4idea.config.GitVcsSettings;
 import git4idea.i18n.GitBundle;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import javax.swing.event.HyperlinkEvent;
 import java.util.Collection;
@@ -86,10 +88,14 @@ public abstract class GitChangesSaver {
 
   /**
    * Saves local changes in stash or in shelf.
+   * @param rootsToSave Save changes only from these roots.
    */
-  public void saveLocalChanges() throws VcsException {
+  public void saveLocalChanges(@Nullable Collection<VirtualFile> rootsToSave) throws VcsException {
+    if (rootsToSave == null || rootsToSave.isEmpty()) {
+      return;
+    }
     myChangeLists = myChangeManager.getChangeListsCopy();
-    save();
+    save(rootsToSave);
   }
 
   /**
@@ -111,10 +117,33 @@ public abstract class GitChangesSaver {
     }
   }
 
+  public List<LocalChangeList> getChangeLists() {
+    return myChangeLists == null ? myChangeManager.getChangeLists() : myChangeLists;
+  }
+
+  /**
+   * Utility method - gets {@link FilePath}s of changed files in a single collection.
+   */
+  public Collection<FilePath> getChangedFiles() {
+    final HashSet<FilePath> files = new HashSet<FilePath>();
+    for (LocalChangeList changeList : getChangeLists()) {
+      for (Change c : changeList.getChanges()) {
+        if (c.getAfterRevision() != null) {
+          files.add(c.getAfterRevision().getFile());
+        }
+        if (c.getBeforeRevision() != null) {
+          files.add(c.getBeforeRevision().getFile());
+        }
+      }
+    }
+    return files;
+  }
+
   /**
    * Saves local changes - specific for chosen save strategy.
+   * @param rootsToSave local changes should be saved on these roots.
    */
-  protected abstract void save() throws VcsException;
+  protected abstract void save(Collection<VirtualFile> rootsToSave) throws VcsException;
 
   /**
    * Loads the changes - specific for chosen save strategy.
@@ -136,30 +165,15 @@ public abstract class GitChangesSaver {
    */
   protected abstract void showSavedChanges();
 
-  /**
-   * Utility method - gets {@link FilePath}s of changed files in a single collection.
-   */
-  protected Collection<FilePath> getChangedFiles() {
-    final HashSet<FilePath> files = new HashSet<FilePath>();
-    for (LocalChangeList changeList : myChangeLists) {
-      for (Change c : changeList.getChanges()) {
-        if (c.getAfterRevision() != null) {
-          files.add(c.getAfterRevision().getFile());
-        }
-        if (c.getBeforeRevision() != null) {
-          files.add(c.getBeforeRevision().getFile());
-        }
-      }
-    }
-    return files;
-  }
-
   // Move files back to theirs change lists
   private void restoreChangeLists() {
     UIUtil.invokeLaterIfNeeded(new Runnable() {
       public void run() {
         myChangeManager.invokeAfterUpdate(new Runnable() {
           public void run() {
+            if (myChangeLists == null) {
+              return;
+            }
             for (LocalChangeList changeList : myChangeLists) {
               final Collection<Change> changes = changeList.getChanges();
               LOG.debug(
index 4488d49414a0aa55d466c1fcdfaab6595c74a05c..ad456f7c0606ca888df0d69811c48cd2a8d5754b 100644 (file)
@@ -19,16 +19,23 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.update.UpdatedFiles;
 import com.intellij.openapi.vfs.VirtualFile;
-import git4idea.GitVcs;
+import git4idea.GitUtil;
+import git4idea.branch.GitBranchPair;
 import git4idea.commands.*;
 import git4idea.merge.GitMergeConflictResolver;
 import git4idea.merge.GitMerger;
 import git4idea.ui.GitUIUtil;
+import org.jetbrains.annotations.NotNull;
 
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -37,7 +44,7 @@ import java.util.concurrent.atomic.AtomicReference;
 public class GitMergeUpdater extends GitUpdater {
   private static final Logger LOG = Logger.getInstance(GitMergeUpdater.class);
 
-  private GitVcs myVcs;
+  private final GitUpdateProcess myUpdateProcess;
 
   private enum MergeError {
     CONFLICT,
@@ -45,17 +52,21 @@ public class GitMergeUpdater extends GitUpdater {
     OTHER
   }
 
-  public GitMergeUpdater(Project project, VirtualFile root, ProgressIndicator progressIndicator, UpdatedFiles updatedFiles) {
+  public GitMergeUpdater(Project project,
+                         VirtualFile root,
+                         GitUpdateProcess gitUpdateProcess,
+                         ProgressIndicator progressIndicator,
+                         UpdatedFiles updatedFiles) {
     super(project, root, progressIndicator, updatedFiles);
-    myVcs = GitVcs.getInstance(project);
+    myUpdateProcess = gitUpdateProcess;
   }
 
   @Override
   protected GitUpdateResult doUpdate() {
     final GitMerger merger = new GitMerger(myProject);
-    final GitLineHandler pullHandler = makePullHandler(myRoot);
+    final GitLineHandler mergeHandler = makeMergeHandler(myRoot);
     final AtomicReference<MergeError> mergeError = new AtomicReference<MergeError>(MergeError.OTHER);
-    pullHandler.addLineListener(new GitLineHandlerAdapter() {
+    mergeHandler.addLineListener(new GitLineHandlerAdapter() {
       @Override
       public void onLineAvailable(String line, Key outputType) {
         if (line.contains("Automatic merge failed; fix conflicts and then commit the result")) {
@@ -66,11 +77,11 @@ public class GitMergeUpdater extends GitUpdater {
       }
     });
 
-    final GitTask pullTask = new GitTask(myProject, pullHandler, "git pull");
-    pullTask.setExecuteResultInAwt(false);
-    pullTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());
+    final GitTask mergeTask = new GitTask(myProject, mergeHandler, "git merge");
+    mergeTask.setExecuteResultInAwt(false);
+    mergeTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());
     final AtomicReference<GitUpdateResult> updateResult = new AtomicReference<GitUpdateResult>();
-    pullTask.executeInBackground(true, new GitTaskResultHandlerAdapter() {
+    mergeTask.executeInBackground(true, new GitTaskResultHandlerAdapter() {
       @Override protected void onSuccess() {
         updateResult.set(GitUpdateResult.SUCCESS);
       }
@@ -83,21 +94,22 @@ public class GitMergeUpdater extends GitUpdater {
       @Override protected void onFailure() {
         final MergeError error = mergeError.get();
         if (error == MergeError.CONFLICT) {
-          final boolean allMerged = new GitMergeConflictResolver(myProject, true, "Merge conflicts detected. Resolve them before continuing update.",
-                                                                 "Can't update", "") {
-            @Override protected boolean proceedIfNothingToMerge() throws VcsException {
-              merger.mergeCommit(myRoot);
-              return true;
-            }
-
-            @Override protected boolean proceedAfterAllMerged() throws VcsException {
-              merger.mergeCommit(myRoot);
-              return true;
-            }
-          }.mergeFiles(Collections.singleton(myRoot));
+          final boolean allMerged =
+            new GitMergeConflictResolver(myProject, true, "Merge conflicts detected. Resolve them before continuing update.",
+                                         "Can't update", "") {
+              @Override protected boolean proceedIfNothingToMerge() throws VcsException {
+                merger.mergeCommit(myRoot);
+                return true;
+              }
+
+              @Override protected boolean proceedAfterAllMerged() throws VcsException {
+                merger.mergeCommit(myRoot);
+                return true;
+              }
+            }.mergeFiles(Collections.singleton(myRoot));
           updateResult.set(allMerged ? GitUpdateResult.SUCCESS : GitUpdateResult.INCOMPLETE);
         } else {
-          GitUIUtil.notifyImportantError(myProject, "Error merging", GitUIUtil.stringifyErrors(pullHandler.errors()));
+          GitUIUtil.notifyImportantError(myProject, "Error merging", GitUIUtil.stringifyErrors(mergeHandler.errors()));
           updateResult.set(GitUpdateResult.ERROR);
         }
       }
@@ -105,6 +117,81 @@ public class GitMergeUpdater extends GitUpdater {
     return updateResult.get();
   }
 
+  @Override
+  public boolean isSaveNeeded() {
+    boolean fetchSuccess = fetch();
+    if (!fetchSuccess) {
+      return true; // fail safe: fetch failed, will save the root.
+    }
+
+    // git log --name-status master..origin/master
+    GitBranchPair gitBranchPair = myUpdateProcess.getTrackedBranches().get(myRoot);
+    String currentBranch = gitBranchPair.getBranch().getName();
+    String remoteBranch = gitBranchPair.getTracked().getName();
+    try {
+      Collection<String> remotelyChanged = getRemotelyChangedPaths(currentBranch, remoteBranch);
+      Collection<FilePath> locallyChanged = myUpdateProcess.getSaver().getChangedFiles();
+      for (FilePath localPath : locallyChanged) {
+        if (remotelyChanged.contains(localPath.getPath())) { // found a file which was changed locally and remotely => need to save
+          return true;
+        }
+      }
+      return false;
+    } catch (VcsException e) {
+      LOG.info("failed to get remotely changed files for " + currentBranch + ".." + remoteBranch, e);
+      return true; // fail safe
+    }
+  }
+
+  /**
+   * Fetches the tracked remote for current branch.
+   * @return true if fetch was successful, false in the case of error.
+   * @param remote
+   */
+  private boolean fetch() {
+    final GitLineHandler h = new GitLineHandler(myProject, myRoot, GitCommand.FETCH);
+
+    final GitTask fetchTask = new GitTask(myProject, h, "Fetching changes...");
+    fetchTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());
+    final AtomicBoolean success = new AtomicBoolean();
+    fetchTask.executeInBackground(true, new GitTaskResultHandlerAdapter() {
+      @Override protected void onSuccess() {
+        success.set(true);
+      }
+
+      @Override protected void onCancel() {
+        LOG.info("Cancelled fetch.");
+      }
+
+      @Override protected void onFailure() {
+        LOG.info("Error fetching: " + h.errors());
+      }
+    });
+    return success.get();
+  }
+
+  // git log --name-status master..origin/master
+  private @NotNull Collection<String> getRemotelyChangedPaths(String currentBranch, String remoteBranch) throws VcsException {
+    final GitSimpleHandler toPull = new GitSimpleHandler(myProject, myRoot, GitCommand.LOG);
+    toPull.addParameters("--name-only", "--pretty=format:");
+    toPull.addParameters(currentBranch + ".." + remoteBranch);
+    toPull.setNoSSH(true);
+    toPull.setStdoutSuppressed(true);
+    toPull.setStderrSuppressed(true);
+    final String output = toPull.run();
+
+    final Collection<String> remoteChanges = new HashSet<String>();
+    for (StringScanner s = new StringScanner(output); s.hasMoreData();) {
+      final String relative = s.line();
+      if (StringUtil.isEmptyOrSpaces(relative)) {
+        continue;
+      }
+      final String path = myRoot.getPath() + "/" + GitUtil.unescapePath(relative);
+      remoteChanges.add(path);
+    }
+    return remoteChanges;
+  }
+
   private void cancel() {
     try {
       GitSimpleHandler h = new GitSimpleHandler(myProject, myRoot, GitCommand.RESET);
@@ -116,7 +203,7 @@ public class GitMergeUpdater extends GitUpdater {
     }
   }
 
-  protected GitLineHandler makePullHandler(VirtualFile root) {
+  private GitLineHandler makeMergeHandler(VirtualFile root) {
     GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.PULL);
     h.addParameters("--no-rebase");
     h.addParameters("--no-stat");
index 826a3aebe2a61c0cc968267b9aabb7ac9a1a7c24..6a081e47a3d38521b94c7c507bcbbf288f905408 100644 (file)
@@ -39,11 +39,19 @@ public class GitRebaseUpdater extends GitUpdater {
   private static final Logger LOG = Logger.getInstance(GitRebaseUpdater.class.getName());
   private final GitRebaser myRebaser;
 
-  public GitRebaseUpdater(Project project, VirtualFile root, ProgressIndicator progressIndicator, UpdatedFiles updatedFiles) {
+  public GitRebaseUpdater(Project project,
+                          VirtualFile root,
+                          GitUpdateProcess gitUpdateProcess,
+                          ProgressIndicator progressIndicator,
+                          UpdatedFiles updatedFiles) {
     super(project, root, progressIndicator, updatedFiles);
     myRebaser = new GitRebaser(myProject);
   }
 
+  @Override public boolean isSaveNeeded() {
+    return true;
+  }
+
   protected GitUpdateResult doUpdate() {
     final GitLineHandler pullHandler = makePullHandler(myRoot);
     final GitRebaseProblemDetector rebaseConflictDetector = new GitRebaseProblemDetector();
index 85fc6469d2799acdcb386daad0fbb9f66dc03dcb..e537fb1d02832bdaa0faec8055c320779ac7ff5c 100644 (file)
@@ -19,13 +19,19 @@ import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ContentRevision;
 import com.intellij.openapi.vcs.changes.LocalChangeList;
 import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager;
 import com.intellij.openapi.vcs.changes.shelf.ShelvedChangeList;
 import com.intellij.openapi.vcs.changes.shelf.ShelvedChangesViewManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.GitUtil;
 import git4idea.i18n.GitBundle;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -43,10 +49,10 @@ public class GitShelveChangesSaver extends GitChangesSaver {
   }
 
   @Override
-  protected void save() throws VcsException {
+  protected void save(Collection<VirtualFile> rootsToSave) throws VcsException {
     ArrayList<Change> changes = new ArrayList<Change>();
     for (LocalChangeList l : myChangeLists) {
-      changes.addAll(l.getChanges());
+      changes.addAll(filterChangesByRoots(l.getChanges(), rootsToSave)); // adding only changes from roots which are to be saved
     }
     if (!changes.isEmpty()) {
       myProgressIndicator.setText(GitBundle.getString("update.shelving.changes"));
@@ -83,4 +89,32 @@ public class GitShelveChangesSaver extends GitChangesSaver {
   @Override protected void showSavedChanges() {
     myShelveViewManager.activateView(myShelvedChangeList);
   }
+
+  /**
+   * Goes through the changes and returns only those of them which belong to any of the given roots,
+   * throwing away the changes which don't belong to any of the given roots.
+   */
+  private static @NotNull Collection<Change> filterChangesByRoots(@NotNull Collection<Change> changes, @NotNull Collection<VirtualFile> rootsToSave) {
+    Collection<Change> filteredChanges = new HashSet<Change>();
+    for (Change change : changes) {
+      final ContentRevision beforeRevision = change.getBeforeRevision();
+      if (beforeRevision != null) {
+        final VirtualFile root = GitUtil.getGitRootOrNull(beforeRevision.getFile());
+        if (root != null && rootsToSave.contains(root)) {
+          filteredChanges.add(change);
+          continue;
+        }
+      }
+
+      final ContentRevision afterRevision = change.getAfterRevision();
+      if (afterRevision != null) {
+        final VirtualFile root = GitUtil.getGitRootOrNull(afterRevision.getFile());
+        if (root != null && rootsToSave.contains(root)) {
+          filteredChanges.add(change);
+        }
+      }
+    }
+    return filteredChanges;
+  }
+
 }
index f05c76b7c01a3efe2e0a44822cb34d89ffa1a428..aed097589da14cdc25f9eb7c7d56f43b55e18a28 100644 (file)
@@ -55,8 +55,8 @@ public class GitStashChangesSaver extends GitChangesSaver {
   }
 
   @Override
-  protected void save() throws VcsException {
-    Map<VirtualFile, Collection<Change>> changes = groupChangesByRoots();
+  protected void save(Collection<VirtualFile> rootsToSave) throws VcsException {
+    Map<VirtualFile, Collection<Change>> changes = groupChangesByRoots(rootsToSave);
     convertSeparatorsIfNeeded(changes);
     stash(changes.keySet());
   }
@@ -146,24 +146,28 @@ public class GitStashChangesSaver extends GitChangesSaver {
   }
 
   // Sort changes from myChangesLists by their git roots.
-  private Map<VirtualFile, Collection<Change>> groupChangesByRoots() {
+  // And use only supplied roots, ignoring changes from other roots.
+  private Map<VirtualFile, Collection<Change>> groupChangesByRoots(Collection<VirtualFile> rootsToSave) {
     final Map<VirtualFile, Collection<Change>> sortedChanges = new HashMap<VirtualFile, Collection<Change>>();
     for (LocalChangeList l : myChangeLists) {
       final Collection<Change> changeCollection = l.getChanges();
       for (Change c : changeCollection) {
         if (c.getAfterRevision() != null) {
-          storeChangeInMap(sortedChanges, c, c.getAfterRevision());
+          storeChangeInMap(sortedChanges, c, c.getAfterRevision(), rootsToSave);
         } else if (c.getBeforeRevision() != null) {
-            storeChangeInMap(sortedChanges, c, c.getBeforeRevision());
+          storeChangeInMap(sortedChanges, c, c.getBeforeRevision(), rootsToSave);
         }
       }
     }
     return sortedChanges;
   }
 
-  private static void storeChangeInMap(Map<VirtualFile, Collection<Change>> sortedChanges, Change c, ContentRevision before) {
-    final VirtualFile root = GitUtil.getGitRootOrNull(before.getFile());
-    if (root != null) {
+  private static void storeChangeInMap(Map<VirtualFile, Collection<Change>> sortedChanges,
+                                       Change c,
+                                       ContentRevision contentRevision,
+                                       Collection<VirtualFile> rootsToSave) {
+    final VirtualFile root = GitUtil.getGitRootOrNull(contentRevision.getFile());
+    if (root != null && rootsToSave.contains(root)) {
       Collection<Change> changes = sortedChanges.get(root);
       if (changes == null) {
         changes = new ArrayList<Change>();
index 32320c7986752f0352f93098483b669a72bd40d8..4441eecf836a256ab66cf5db2a3d3fa151e0d4a0 100644 (file)
@@ -20,21 +20,18 @@ import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ex.ProjectManagerEx;
 import com.intellij.openapi.util.Clock;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vcs.AbstractVcsHelper;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.update.UpdatedFiles;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.text.DateFormatUtil;
 import git4idea.GitBranch;
-import git4idea.GitVcs;
+import git4idea.branch.GitBranchPair;
 import git4idea.merge.GitMergeConflictResolver;
 import git4idea.merge.GitMergeUtil;
 import git4idea.merge.GitMerger;
 import git4idea.rebase.GitRebaser;
 
-import java.util.Collection;
-import java.util.Set;
+import java.util.*;
 
 import static git4idea.ui.GitUIUtil.notifyError;
 import static git4idea.ui.GitUIUtil.notifyImportantError;
@@ -52,9 +49,10 @@ public class GitUpdateProcess {
   private final Set<VirtualFile> myRoots;
   private final UpdatedFiles myUpdatedFiles;
   private final ProgressIndicator myProgressIndicator;
-  private final GitVcs myVcs;
-  private final AbstractVcsHelper myVcsHelper;
   private final GitMerger myMerger;
+  private final GitChangesSaver mySaver;
+
+  private final Map<VirtualFile, GitBranchPair> myTrackedBranches = new HashMap<VirtualFile, GitBranchPair>();
 
   public GitUpdateProcess(Project project,
                           ProgressIndicator progressIndicator,
@@ -64,9 +62,9 @@ public class GitUpdateProcess {
     myUpdatedFiles = updatedFiles;
     myProgressIndicator = progressIndicator;
     myProjectManager = ProjectManagerEx.getInstanceEx();
-    myVcs = GitVcs.getInstance(myProject);
-    myVcsHelper = AbstractVcsHelper.getInstance(project);
     myMerger = new GitMerger(myProject);
+    mySaver = GitChangesSaver.getSaver(myProject, myProgressIndicator,
+      "Uncommitted changes before update operation at " + DateFormatUtil.formatDateTime(Clock.getTime()));
   }
 
   /**
@@ -75,22 +73,30 @@ public class GitUpdateProcess {
    */
   public boolean update() {
     LOG.info("update started");
-    final GitChangesSaver saver = GitChangesSaver.getSaver(myProject, myProgressIndicator,
-      "Uncommitted changes before update operation at " + DateFormatUtil.formatDateTime(Clock.getTime()));
 
     myProjectManager.blockReloadingProjectOnExternalChanges();
     try {
       // check if update is possible
       if (checkRebaseInProgress() || checkMergeInProgress() || checkUnmergedFiles()) { return false; }
-      if (!allTrackedBranchesConfigured()) { return false; }
+      if (!checkTrackedBranchesConfigured()) { return false; }
+
+      // define updaters for each root
+      Collection<VirtualFile> rootsToSave = new HashSet<VirtualFile>(1);
+      for (VirtualFile root : myRoots) {
+        final GitUpdater updater = GitUpdater.getUpdater(myProject, this, root, myProgressIndicator, myUpdatedFiles);
+        if (updater.isSaveNeeded()) {
+          rootsToSave.add(root);
+        }
+      }
+
+      mySaver.saveLocalChanges(rootsToSave);
 
-      saver.saveLocalChanges();
       // update each root
       boolean incomplete = false;
       boolean success = true;
       for (final VirtualFile root : myRoots) {
         try {
-          final GitUpdater updater = GitUpdater.getUpdater(myProject, root, myProgressIndicator, myUpdatedFiles);
+          final GitUpdater updater = GitUpdater.getUpdater(myProject, this, root, myProgressIndicator, myUpdatedFiles);
           GitUpdateResult res = updater.update();
           if (res == GitUpdateResult.INCOMPLETE) {
             incomplete = true;
@@ -103,9 +109,9 @@ public class GitUpdateProcess {
         } finally {
           try {
             if (!incomplete) {
-              saver.restoreLocalChanges();
+              mySaver.restoreLocalChanges();
             } else {
-              saver.notifyLocalChangesAreNotRestored();
+              mySaver.notifyLocalChangesAreNotRestored();
             }
           } catch (VcsException e) {
             LOG.info("Couldn't restore local changes after update", e);
@@ -118,7 +124,7 @@ public class GitUpdateProcess {
     } catch (VcsException e) {
       LOG.info("Couldn't save local changes", e);
       notifyError(myProject, "Couldn't save local changes",
-                  "Tried to save uncommitted changes in " + saver.getSaverName() + " before update, but failed with an error.<br/>" +
+                  "Tried to save uncommitted changes in " + mySaver.getSaverName() + " before update, but failed with an error.<br/>" +
                   "Update was cancelled.", true, e);
     } finally {
       myProjectManager.unblockReloadingProjectOnExternalChanges();
@@ -126,12 +132,20 @@ public class GitUpdateProcess {
     return false;
   }
 
+  public Map<VirtualFile, GitBranchPair> getTrackedBranches() {
+    return myTrackedBranches;
+  }
+
+  public GitChangesSaver getSaver() {
+    return mySaver;
+  }
+
   /**
    * For each root check that the repository is on branch, and this branch is tracking a remote branch.
    * If it is not true for at least one of roots, notify and return false.
    * If branch configuration is OK for all roots, return true.
    */
-  private boolean allTrackedBranchesConfigured() {
+  private boolean checkTrackedBranchesConfigured() {
     for (VirtualFile root : myRoots) {
       try {
         final GitBranch branch = GitBranch.current(myProject, root);
@@ -141,8 +155,8 @@ public class GitUpdateProcess {
                                "Checkout a branch to make update possible.");
           return false;
         }
-        final String value = branch.getTrackedRemoteName(myProject, root);
-        if (StringUtil.isEmpty(value)) {
+        final GitBranch tracked = branch.tracked(myProject, root);
+        if (tracked == null) {
           final String branchName = branch.getName();
           notifyImportantError(myProject, "Can't update: no tracked branch",
                                "No tracked branch configured for branch " + branchName +
@@ -150,6 +164,7 @@ public class GitUpdateProcess {
                                "<code>git branch --set-upstream " + branchName + " origin/" + branchName + "</code>");
           return false;
         }
+        myTrackedBranches.put(root, new GitBranchPair(branch, tracked));
       } catch (VcsException e) {
         notifyImportantError(myProject, "Can't update: error identifying tracked branch", e.getLocalizedMessage());
         return false;
index 68816f7e3089331406d864bea9bf032cbbad98f3..230bc435320572e220956dde98b9c593c13bb1f6 100644 (file)
@@ -60,38 +60,48 @@ public abstract class GitUpdater {
   /**
    * Returns proper updater based on the update policy (merge or rebase) selected by user or stored in his .git/config
    *
+   *
+   * @param gitUpdateProcess
    * @param root
    * @param progressIndicator
    * @return {@link GitMergeUpdater} or {@link GitRebaseUpdater}.
    */
-  public static GitUpdater getUpdater(Project project, VirtualFile root, ProgressIndicator progressIndicator, UpdatedFiles updatedFiles) {
+  public static GitUpdater getUpdater(Project project,
+                                      GitUpdateProcess gitUpdateProcess,
+                                      VirtualFile root,
+                                      ProgressIndicator progressIndicator,
+                                      UpdatedFiles updatedFiles) {
     final GitVcsSettings settings = GitVcsSettings.getInstance(project);
     if (settings == null) {
-      return getDefaultUpdaterForBranch(project, root, progressIndicator, updatedFiles);
+      return getDefaultUpdaterForBranch(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
     }
     switch (settings.getUpdateType()) {
       case REBASE:
-        return new GitRebaseUpdater(project, root, progressIndicator, updatedFiles);
+        return new GitRebaseUpdater(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
       case MERGE:
-        return new GitMergeUpdater(project, root, progressIndicator, updatedFiles);
+        return new GitMergeUpdater(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
       case BRANCH_DEFAULT:
         // use default for the branch
-        return getDefaultUpdaterForBranch(project, root, progressIndicator, updatedFiles);
+        return getDefaultUpdaterForBranch(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
     }
-    return getDefaultUpdaterForBranch(project, root, progressIndicator, updatedFiles);
+    return getDefaultUpdaterForBranch(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
   }
 
-  private static GitUpdater getDefaultUpdaterForBranch(Project project, VirtualFile root, ProgressIndicator progressIndicator, UpdatedFiles updatedFiles) {
+  private static GitUpdater getDefaultUpdaterForBranch(Project project,
+                                                       VirtualFile root,
+                                                       GitUpdateProcess gitUpdateProcess,
+                                                       ProgressIndicator progressIndicator,
+                                                       UpdatedFiles updatedFiles) {
     try {
       final GitBranch branchName = GitBranch.current(project, root);
       final String rebase = GitConfigUtil.getValue(project, root, "branch." + branchName + ".rebase");
       if (rebase != null && rebase.equalsIgnoreCase("true")) {
-        return new GitRebaseUpdater(project, root, progressIndicator, updatedFiles);
+        return new GitRebaseUpdater(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
       }
     } catch (VcsException e) {
       LOG.info("getDefaultUpdaterForBranch branch", e);
     }
-    return new GitMergeUpdater(project, root, progressIndicator, updatedFiles);
+    return new GitMergeUpdater(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
   }
 
   public GitUpdateResult update() throws VcsException {
@@ -103,6 +113,13 @@ public abstract class GitUpdater {
     }
   }
 
+  /**
+   * Checks the repository if local changes need to be saved before update.
+   * For rebase local changes need to be saved always, for merge - only in the case if merge affects the same files.
+   * @return true if local changes from this root need to be saved, false if not.
+   */
+  public abstract boolean isSaveNeeded();
+
   /**
    * Performs update (via rebase or merge - depending on the implementing classes).
    */