Git update: extract fetch; don't try to update if not needed.
authorKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Fri, 18 Mar 2011 12:15:03 +0000 (15:15 +0300)
committerKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Fri, 18 Mar 2011 12:15:03 +0000 (15:15 +0300)
1. Fetch all roots in the update start, before all blockings etc.
2. Check if update is needed for a root. If not, don't bother to check if stash needed etc.
3. It solves the bug that changes are stashed for rebase even if rebase does nothing.

plugins/git4idea/src/git4idea/update/GitFetcher.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/update/GitMergeUpdater.java
plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java
plugins/git4idea/src/git4idea/update/GitUpdateProcess.java
plugins/git4idea/src/git4idea/update/GitUpdater.java

diff --git a/plugins/git4idea/src/git4idea/update/GitFetcher.java b/plugins/git4idea/src/git4idea/update/GitFetcher.java
new file mode 100644 (file)
index 0000000..8122815
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.update;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.commands.*;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author Kirill Likhodedov
+ */
+public class GitFetcher {
+
+  private static final Logger LOG = Logger.getInstance(GitFetcher.class);
+  private final Project myProject;
+  private final Collection<VcsException> myErrors = new HashSet<VcsException>();
+
+  public GitFetcher(Project project) {
+    myProject = project;
+  }
+
+  /**
+   * Invokes 'git fetch'.
+   * @param notify specify true to notify about errors.
+   * @return true if fetch was successful, false in the case of error.
+   */
+  public boolean fetch(VirtualFile root) {
+    final GitLineHandler h = new GitLineHandler(myProject, root, 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());
+        myErrors.addAll(h.errors());
+      }
+    });
+    return success.get();
+  }
+
+  /**
+   * @return true if last {@link #fetch(com.intellij.openapi.vfs.VirtualFile) fetch} performed by this GitFetcher was successful.
+   */
+  public boolean isSuccess() {
+    return myErrors.isEmpty();
+  }
+
+  public Collection<VcsException> getErrors() {
+    return myErrors;
+  }
+
+}
index 1634d699bce7826b05f212c87bf6d6e16baff4f3..8c15ff51bcd0af7b15f0789613176d696d8b6a55 100644 (file)
@@ -41,7 +41,6 @@ import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -50,7 +49,6 @@ import java.util.concurrent.atomic.AtomicReference;
 public class GitMergeUpdater extends GitUpdater {
   private static final Logger LOG = Logger.getInstance(GitMergeUpdater.class);
 
-  private final GitUpdateProcess myUpdateProcess;
   private final ChangeListManager myChangeListManager;
 
   public GitMergeUpdater(Project project,
@@ -58,8 +56,7 @@ public class GitMergeUpdater extends GitUpdater {
                          GitUpdateProcess gitUpdateProcess,
                          ProgressIndicator progressIndicator,
                          UpdatedFiles updatedFiles) {
-    super(project, root, progressIndicator, updatedFiles);
-    myUpdateProcess = gitUpdateProcess;
+    super(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
     myChangeListManager = ChangeListManager.getInstance(myProject);
   }
 
@@ -133,11 +130,6 @@ public class GitMergeUpdater extends GitUpdater {
 
   @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();
@@ -157,55 +149,6 @@ public class GitMergeUpdater extends GitUpdater {
     }
   }
 
-  /**
-   * 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(@NotNull String currentBranch, @NotNull 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);
index 0b52d679a1f086d84189948b0363892ffc98e744..0ff486b4f108a72724ff8437734693f5e501e106 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vcs.update.UpdatedFiles;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.ui.UIUtil;
+import git4idea.branch.GitBranchPair;
 import git4idea.commands.*;
 import git4idea.merge.GitMergeConflictResolver;
 import git4idea.rebase.GitRebaseProblemDetector;
@@ -45,7 +46,7 @@ public class GitRebaseUpdater extends GitUpdater {
                           GitUpdateProcess gitUpdateProcess,
                           ProgressIndicator progressIndicator,
                           UpdatedFiles updatedFiles) {
-    super(project, root, progressIndicator, updatedFiles);
+    super(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
     myRebaser = new GitRebaser(myProject);
   }
 
@@ -55,11 +56,15 @@ public class GitRebaseUpdater extends GitUpdater {
 
   protected GitUpdateResult doUpdate() {
     LOG.info("doUpdate ");
-    final GitLineHandler pullHandler = makePullHandler(myRoot);
+    GitBranchPair gitBranchPair = myUpdateProcess.getTrackedBranches().get(myRoot);
+    String remoteBranch = gitBranchPair.getTracked().getName();
+
+    final GitLineHandler rebaseHandler = new GitLineHandler(myProject, myRoot, GitCommand.REBASE);
+    rebaseHandler.addParameters(remoteBranch);
     final GitRebaseProblemDetector rebaseConflictDetector = new GitRebaseProblemDetector();
-    pullHandler.addLineListener(rebaseConflictDetector);
+    rebaseHandler.addLineListener(rebaseConflictDetector);
 
-    GitTask pullTask = new GitTask(myProject, pullHandler, "git pull");
+    GitTask pullTask = new GitTask(myProject, rebaseHandler, "git rebase");
     pullTask.setExecuteResultInAwt(false);
     pullTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());
     final AtomicReference<GitUpdateResult> updateResult = new AtomicReference<GitUpdateResult>();
@@ -80,7 +85,7 @@ public class GitRebaseUpdater extends GitUpdater {
     });
 
     if (failure.get()) {
-      updateResult.set(handleRebaseFailure(rebaseConflictDetector, pullHandler));
+      updateResult.set(handleRebaseFailure(rebaseConflictDetector, rebaseHandler));
     }
     return updateResult.get();
   }
@@ -123,14 +128,6 @@ public class GitRebaseUpdater extends GitUpdater {
     myRoot.refresh(false, true);
   }
 
-  protected GitLineHandler makePullHandler(VirtualFile root) {
-    final GitLineHandler h = new GitLineHandler(myProject, root, GitCommand.PULL);
-    h.addParameters("--rebase");
-    h.addParameters("--no-stat");
-    h.addParameters("-v");
-    return h;
-  }
-
   /**
    * Check and process locally modified files
    *
index 52f22fa3844f5942df058c13afe6da1f7a09763d..46cb33c88244c31f719af439866468b7fa3055ca 100644 (file)
@@ -16,6 +16,7 @@
 package git4idea.update;
 
 import com.intellij.ide.GeneralSettings;
+import com.intellij.notification.NotificationType;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
@@ -34,6 +35,7 @@ import git4idea.merge.GitMergeConflictResolver;
 import git4idea.merge.GitMergeUtil;
 import git4idea.merge.GitMerger;
 import git4idea.rebase.GitRebaser;
+import git4idea.ui.GitUIUtil;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.*;
@@ -97,6 +99,10 @@ public class GitUpdateProcess {
   public boolean update(boolean forceRebase) {
     LOG.info("update started|" + (forceRebase ? " force rebase" : ""));
 
+    if (!fetchAndNotify()) {
+      return false;
+    }
+
     final boolean saveOnFrameDeactivation = myGeneralSettings.isSaveOnFrameDeactivation();
     final boolean syncOnFrameDeactivation = myGeneralSettings.isSyncOnFrameActivation();
     myProjectManager.blockReloadingProjectOnExternalChanges();
@@ -123,7 +129,9 @@ public class GitUpdateProcess {
         final GitUpdater updater = forceRebase
                                    ? new GitRebaseUpdater(myProject, root, this, myProgressIndicator, myUpdatedFiles)
                                    : GitUpdater.getUpdater(myProject, this, root, myProgressIndicator, myUpdatedFiles);
-        updaters.put(root, updater);
+        if (updater.isUpdateNeeded()) {
+          updaters.put(root, updater);
+        }
         LOG.info("update| root=" + root + " ,updater=" + updater);
       }
 
@@ -186,6 +194,19 @@ public class GitUpdateProcess {
     return false;
   }
 
+  // fetch all roots. If an error happens, return false and notify about errors.
+  private boolean fetchAndNotify() {
+    GitFetcher fetcher = new GitFetcher(myProject);
+    for (VirtualFile root : myRoots) {
+      fetcher.fetch(root);
+    }
+    if (!fetcher.isSuccess()) {
+      GitUIUtil.notifyMessage(myProject, "Update failed", "Couldn't fetch", NotificationType.ERROR, true, fetcher.getErrors());
+      return false;
+    }
+    return true;
+  }
+
   public Map<VirtualFile, GitBranchPair> getTrackedBranches() {
     return myTrackedBranches;
   }
index 230bc435320572e220956dde98b9c593c13bb1f6..cb1b3287826054405a7ed9b3294f3691d911956a 100644 (file)
@@ -18,18 +18,27 @@ 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.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 git4idea.GitBranch;
 import git4idea.GitRevisionNumber;
+import git4idea.GitUtil;
 import git4idea.GitVcs;
+import git4idea.branch.GitBranchPair;
+import git4idea.commands.GitCommand;
+import git4idea.commands.GitSimpleHandler;
+import git4idea.commands.StringScanner;
 import git4idea.config.GitConfigUtil;
 import git4idea.config.GitVcsSettings;
 import git4idea.merge.MergeChangeCollector;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
 
 /**
  * Updates a single repository via merge or rebase.
@@ -41,6 +50,7 @@ public abstract class GitUpdater {
 
   protected final Project myProject;
   protected final VirtualFile myRoot;
+  protected final GitUpdateProcess myUpdateProcess;
   protected final ProgressIndicator myProgressIndicator;
   protected final UpdatedFiles myUpdatedFiles;
   protected final AbstractVcsHelper myVcsHelper;
@@ -48,9 +58,14 @@ public abstract class GitUpdater {
 
   protected GitRevisionNumber myBefore; // The revision that was before update
 
-  protected GitUpdater(Project project, VirtualFile root, ProgressIndicator progressIndicator, UpdatedFiles updatedFiles) {
+  protected GitUpdater(Project project,
+                       VirtualFile root,
+                       GitUpdateProcess gitUpdateProcess,
+                       ProgressIndicator progressIndicator,
+                       UpdatedFiles updatedFiles) {
     myProject = project;
     myRoot = root;
+    myUpdateProcess = gitUpdateProcess;
     myProgressIndicator = progressIndicator;
     myUpdatedFiles = updatedFiles;
     myVcsHelper = AbstractVcsHelper.getInstance(project);
@@ -120,6 +135,22 @@ public abstract class GitUpdater {
    */
   public abstract boolean isSaveNeeded();
 
+  /**
+   * Checks if update is needed, i.e. if there are remote changes that weren't merged into the current branch.
+   * @return true if update is needed, false otherwise.
+   */
+  public boolean isUpdateNeeded() throws VcsException {
+    GitBranchPair gitBranchPair = myUpdateProcess.getTrackedBranches().get(myRoot);
+    String currentBranch = gitBranchPair.getBranch().getName();
+    String remoteBranch = gitBranchPair.getTracked().getName();
+    Collection<String> remotelyChanged = getRemotelyChangedPaths(currentBranch, remoteBranch);
+    if (remotelyChanged.isEmpty()) {
+      LOG.info("isSaveNeeded No remote changes, save is not needed");
+      return false;
+    }
+    return true;
+  }
+
   /**
    * Performs update (via rebase or merge - depending on the implementing classes).
    */
@@ -139,4 +170,30 @@ public abstract class GitUpdater {
       throw exceptions.get(0);
     }
   }
+
+  /**
+   * Returns paths which have changed remotely comparing to the current branch, i.e. performs
+   * <code>git log --name-status master..origin/master</code>
+   */
+  protected @NotNull Collection<String> getRemotelyChangedPaths(@NotNull String currentBranch, @NotNull 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;
+  }
+
 }