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.
--- /dev/null
+/*
+ * 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;
+ }
+
+}
import java.io.File;
import java.util.*;
-import java.util.concurrent.atomic.AtomicBoolean;
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,
GitUpdateProcess gitUpdateProcess,
ProgressIndicator progressIndicator,
UpdatedFiles updatedFiles) {
- super(project, root, progressIndicator, updatedFiles);
- myUpdateProcess = gitUpdateProcess;
+ super(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
myChangeListManager = ChangeListManager.getInstance(myProject);
}
@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();
}
}
- /**
- * 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);
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;
GitUpdateProcess gitUpdateProcess,
ProgressIndicator progressIndicator,
UpdatedFiles updatedFiles) {
- super(project, root, progressIndicator, updatedFiles);
+ super(project, root, gitUpdateProcess, progressIndicator, updatedFiles);
myRebaser = new GitRebaser(myProject);
}
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>();
});
if (failure.get()) {
- updateResult.set(handleRebaseFailure(rebaseConflictDetector, pullHandler));
+ updateResult.set(handleRebaseFailure(rebaseConflictDetector, rebaseHandler));
}
return updateResult.get();
}
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
*
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;
import git4idea.merge.GitMergeUtil;
import git4idea.merge.GitMerger;
import git4idea.rebase.GitRebaser;
+import git4idea.ui.GitUIUtil;
import org.jetbrains.annotations.NotNull;
import java.util.*;
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();
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);
}
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;
}
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.
protected final Project myProject;
protected final VirtualFile myRoot;
+ protected final GitUpdateProcess myUpdateProcess;
protected final ProgressIndicator myProgressIndicator;
protected final UpdatedFiles myUpdatedFiles;
protected final AbstractVcsHelper myVcsHelper;
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);
*/
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).
*/
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;
+ }
+
}