blob: ccc3fd625a94b216cdd8ba58fa66193fa4bbd0fe [file] [log] [blame]
/*
* 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.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
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.*;
import git4idea.branch.GitBranchPair;
import git4idea.branch.GitBranchUtil;
import git4idea.commands.Git;
import git4idea.commands.GitCommand;
import git4idea.commands.GitSimpleHandler;
import git4idea.config.GitConfigUtil;
import git4idea.config.GitVcsSettings;
import git4idea.merge.MergeChangeCollector;
import git4idea.repo.GitRepositoryManager;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Map;
/**
* Updates a single repository via merge or rebase.
* @see GitRebaseUpdater
* @see GitMergeUpdater
*/
public abstract class GitUpdater {
private static final Logger LOG = Logger.getInstance(GitUpdater.class);
@NotNull protected final Project myProject;
@NotNull protected final Git myGit;
@NotNull protected final VirtualFile myRoot;
@NotNull protected final Map<VirtualFile, GitBranchPair> myTrackedBranches;
@NotNull protected final ProgressIndicator myProgressIndicator;
@NotNull protected final UpdatedFiles myUpdatedFiles;
@NotNull protected final AbstractVcsHelper myVcsHelper;
@NotNull protected final GitRepositoryManager myRepositoryManager;
protected final GitVcs myVcs;
protected GitRevisionNumber myBefore; // The revision that was before update
protected GitUpdater(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root,
@NotNull Map<VirtualFile, GitBranchPair> trackedBranches, @NotNull ProgressIndicator progressIndicator,
@NotNull UpdatedFiles updatedFiles) {
myProject = project;
myGit = git;
myRoot = root;
myTrackedBranches = trackedBranches;
myProgressIndicator = progressIndicator;
myUpdatedFiles = updatedFiles;
myVcsHelper = AbstractVcsHelper.getInstance(project);
myVcs = GitVcs.getInstance(project);
myRepositoryManager = GitUtil.getRepositoryManager(myProject);
}
/**
* Returns proper updater based on the update policy (merge or rebase) selected by user or stored in his .git/config
* @return {@link GitMergeUpdater} or {@link GitRebaseUpdater}.
*/
@NotNull
public static GitUpdater getUpdater(@NotNull Project project, @NotNull Git git, @NotNull Map<VirtualFile, GitBranchPair> trackedBranches,
@NotNull VirtualFile root, @NotNull ProgressIndicator progressIndicator,
@NotNull UpdatedFiles updatedFiles) {
final GitVcsSettings settings = GitVcsSettings.getInstance(project);
if (settings == null) {
return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles);
}
switch (settings.getUpdateType()) {
case REBASE:
return new GitRebaseUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
case MERGE:
return new GitMergeUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
case BRANCH_DEFAULT:
// use default for the branch
return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles);
}
return getDefaultUpdaterForBranch(project, git, root, trackedBranches, progressIndicator, updatedFiles);
}
@NotNull
private static GitUpdater getDefaultUpdaterForBranch(@NotNull Project project, @NotNull Git git, @NotNull VirtualFile root,
@NotNull Map<VirtualFile, GitBranchPair> trackedBranches,
@NotNull ProgressIndicator progressIndicator, @NotNull UpdatedFiles updatedFiles) {
try {
GitLocalBranch branch = GitBranchUtil.getCurrentBranch(project, root);
boolean rebase = false;
if (branch != null) {
String rebaseValue = GitConfigUtil.getValue(project, root, "branch." + branch.getName() + ".rebase");
rebase = rebaseValue != null && rebaseValue.equalsIgnoreCase("true");
}
if (rebase) {
return new GitRebaseUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
}
} catch (VcsException e) {
LOG.info("getDefaultUpdaterForBranch branch", e);
}
return new GitMergeUpdater(project, git, root, trackedBranches, progressIndicator, updatedFiles);
}
@NotNull
public GitUpdateResult update() throws VcsException {
markStart(myRoot);
try {
return doUpdate();
} finally {
markEnd(myRoot);
}
}
/**
* 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 or there is something in the index.
* @return true if local changes from this root need to be saved, false if not.
*/
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 = myTrackedBranches.get(myRoot);
String currentBranch = gitBranchPair.getBranch().getName();
GitBranch dest = gitBranchPair.getDest();
assert dest != null;
String remoteBranch = dest.getName();
if (! hasRemoteChanges(currentBranch, remoteBranch)) {
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).
*/
protected abstract GitUpdateResult doUpdate();
protected void markStart(VirtualFile root) throws VcsException {
// remember the current position
myBefore = GitRevisionNumber.resolve(myProject, root, "HEAD");
}
protected void markEnd(VirtualFile root) throws VcsException {
// find out what have changed, this is done even if the process was cancelled.
final MergeChangeCollector collector = new MergeChangeCollector(myProject, root, myBefore);
final ArrayList<VcsException> exceptions = new ArrayList<VcsException>();
collector.collect(myUpdatedFiles, exceptions);
if (!exceptions.isEmpty()) {
throw exceptions.get(0);
}
}
protected boolean hasRemoteChanges(@NotNull String currentBranch, @NotNull String remoteBranch) throws VcsException {
GitSimpleHandler handler = new GitSimpleHandler(myProject, myRoot, GitCommand.REV_LIST);
handler.setSilent(true);
handler.addParameters("-1");
handler.addParameters(currentBranch + ".." + remoteBranch);
String output = handler.run();
return output != null && !output.isEmpty();
}
@NotNull
protected String makeProgressTitle(@NotNull String operation) {
return myRepositoryManager.moreThanOneRoot() ? String.format("%s %s...", operation, myRoot.getName()) : operation + "...";
}
}