During update project check if the tracked branch is deleted after fetch

And display a notification in that case, instead of failing later.
Also don't store the tracked branch information in a field: it is
enough to pass it in a local variable.

Relates to IDEA-169877.
This commit is contained in:
Kirill Likhodedov
2017-03-25 22:05:40 +00:00
parent 409bf22ffd
commit ec652e0cc8
2 changed files with 73 additions and 34 deletions
@@ -15,6 +15,7 @@
*/
package git4idea.update;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.dvcs.DvcsUtil;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.diagnostic.Logger;
@@ -57,6 +58,7 @@ import java.util.Map;
import static com.intellij.dvcs.DvcsUtil.getShortRepositoryName;
import static git4idea.GitUtil.getRootsFromRepositories;
import static git4idea.GitUtil.mention;
import static git4idea.util.GitUIUtil.*;
/**
@@ -78,8 +80,6 @@ public class GitUpdateProcess {
@NotNull private final ProgressIndicator myProgressIndicator;
@NotNull private final GitMerger myMerger;
private final Map<VirtualFile, GitBranchPair> myTrackedBranches = new HashMap<>();
public GitUpdateProcess(@NotNull Project project,
@Nullable ProgressIndicator progressIndicator,
@NotNull Collection<GitRepository> repositories,
@@ -122,7 +122,10 @@ public class GitUpdateProcess {
}
// check if update is possible
if (checkRebaseInProgress() || isMergeInProgress() || areUnmergedFiles() || !checkTrackedBranchesConfigured()) {
if (checkRebaseInProgress() || isMergeInProgress() || areUnmergedFiles()) {
return GitUpdateResult.NOT_READY;
}
if (checkTrackedBranchesConfiguration() == null) {
return GitUpdateResult.NOT_READY;
}
@@ -144,9 +147,14 @@ public class GitUpdateProcess {
@NotNull
private GitUpdateResult updateImpl(@NotNull UpdateMethod updateMethod) {
Map<VirtualFile, GitBranchPair> trackedBranches = checkTrackedBranchesConfiguration();
if (trackedBranches == null) {
return GitUpdateResult.NOT_READY;
}
Map<GitRepository, GitUpdater> updaters;
try {
updaters = defineUpdaters(updateMethod);
updaters = defineUpdaters(updateMethod, trackedBranches);
}
catch (VcsException e) {
LOG.info(e);
@@ -171,8 +179,7 @@ public class GitUpdateProcess {
GitRebaseOverMergeProblem.Decision decision = GitRebaseOverMergeProblem.showDialog();
if (decision == GitRebaseOverMergeProblem.Decision.MERGE_INSTEAD) {
for (GitRepository repo : problematicRoots) {
updaters.put(repo, new GitMergeUpdater(myProject, myGit, repo.getRoot(), myTrackedBranches,
myProgressIndicator, myUpdatedFiles));
updaters.put(repo, new GitMergeUpdater(myProject, myGit, repo.getRoot(), trackedBranches, myProgressIndicator, myUpdatedFiles));
}
}
else if (decision == GitRebaseOverMergeProblem.Decision.CANCEL_OPERATION) {
@@ -272,12 +279,13 @@ public class GitUpdateProcess {
}
@NotNull
private Map<GitRepository, GitUpdater> defineUpdaters(@NotNull UpdateMethod updateMethod) throws VcsException {
private Map<GitRepository, GitUpdater> defineUpdaters(@NotNull UpdateMethod updateMethod,
@NotNull Map<VirtualFile, GitBranchPair> trackedBranches) throws VcsException {
final Map<GitRepository, GitUpdater> updaters = new HashMap<>();
LOG.info("updateImpl: defining updaters...");
for (GitRepository repository : myRepositories) {
VirtualFile root = repository.getRoot();
GitUpdater updater = GitUpdater.getUpdater(myProject, myGit, myTrackedBranches, root, myProgressIndicator, myUpdatedFiles,
GitUpdater updater = GitUpdater.getUpdater(myProject, myGit, trackedBranches, root, myProgressIndicator, myUpdatedFiles,
updateMethod);
if (updater.isUpdateNeeded()) {
updaters.put(repository, updater);
@@ -301,12 +309,13 @@ public class GitUpdateProcess {
}
/**
* For each root check that the repository is on branch, and this branch is tracking a remote branch,
* and the remote branch exists.
* 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.
* For each root check that the repository is on branch, and this branch is tracking a remote branch, and the remote branch exists.
* If it is not true for at least one of roots, notify and return null.
* If branch configuration is OK for all roots, return the collected tracking branch information.
*/
private boolean checkTrackedBranchesConfigured() {
@Nullable
private Map<VirtualFile, GitBranchPair> checkTrackedBranchesConfiguration() {
Map<VirtualFile, GitBranchPair> trackedBranches = ContainerUtil.newHashMap();
LOG.info("checking tracked branch configuration...");
for (GitRepository repository : myRepositories) {
VirtualFile root = repository.getRoot();
@@ -315,34 +324,38 @@ public class GitUpdateProcess {
LOG.info("checkTrackedBranchesConfigured: current branch is null in " + repository);
notifyImportantError(myProject, "Can't update: no current branch",
"You are in 'detached HEAD' state, which means that you're not on any branch" +
rootStringIfNeeded(root) +
mention(repository) + "<br/>" +
"Checkout a branch to make update possible.");
return false;
return null;
}
GitBranchTrackInfo trackInfo = GitBranchUtil.getTrackInfoForBranch(repository, branch);
if (trackInfo == null) {
final String branchName = branch.getName();
LOG.info(String.format("checkTrackedBranchesConfigured: no track info for current branch %s in %s", branch, repository));
String recommendedCommand = String.format(GitVersionSpecialty.KNOWS_SET_UPSTREAM_TO.existsIn(repository.getVcs().getVersion()) ?
"git branch --set-upstream-to origin/%1$s %1$s" :
"git branch --set-upstream %1$s origin/%1$s", branchName);
notifyImportantError(myProject, "Can't update: no tracked branch",
"No tracked branch configured for branch " + code(branchName) +
rootStringIfNeeded(root) +
"To make your branch track a remote branch call, for example,<br/>" +
"<code>" + recommendedCommand + "</code>");
return false;
notifyImportantError(repository.getProject(), "Can't Update", getNoTrackedBranchError(repository, branchName));
return null;
}
myTrackedBranches.put(root, new GitBranchPair(branch, trackInfo.getRemoteBranch()));
trackedBranches.put(root, new GitBranchPair(branch, trackInfo.getRemoteBranch()));
}
return true;
return trackedBranches;
}
private String rootStringIfNeeded(@NotNull VirtualFile root) {
if (myRepositories.size() < 2) {
return ".<br/>";
}
return "<br/>in Git repository " + code(root.getPresentableUrl()) + "<br/>";
@VisibleForTesting
@NotNull
static String getNoTrackedBranchError(@NotNull GitRepository repository, @NotNull String branchName) {
String recommendedCommand = recommendSetupTrackingCommand(repository, branchName);
return "No tracked branch configured for branch " + code(branchName) +
mention(repository) +
" or the branch doesn't exist.<br/>" +
"To make your branch track a remote branch call, for example,<br/>" +
"<code>" + recommendedCommand + "</code>";
}
@NotNull
private static String recommendSetupTrackingCommand(@NotNull GitRepository repository, @NotNull String branchName) {
return String.format(GitVersionSpecialty.KNOWS_SET_UPSTREAM_TO.existsIn(repository.getVcs().getVersion()) ?
"git branch --set-upstream-to origin/%1$s %1$s" :
"git branch --set-upstream %1$s origin/%1$s", branchName);
}
/**
@@ -20,9 +20,7 @@ import com.intellij.openapi.vcs.Executor.cd
import com.intellij.openapi.vcs.update.UpdatedFiles
import git4idea.config.UpdateMethod
import git4idea.repo.GitRepository
import git4idea.test.git
import git4idea.test.last
import git4idea.test.tacp
import git4idea.test.*
import java.io.File
class GitMultiRepoUpdateTest : GitUpdateBaseTest() {
@@ -66,6 +64,34 @@ class GitMultiRepoUpdateTest : GitUpdateBaseTest() {
assertEquals("Couldn't find the hash from bro", hash, git("log -1 --no-merges --pretty=%H"))
}
fun `test update fails if branch is deleted in one of repositories`() {
listOf(bro, bromunity).forEach {
cd(it)
git("checkout -b feature")
git("push -u origin feature")
}
listOf(repository, community).forEach {
cd(it)
git("pull")
git("checkout -b feature origin/feature")
it.update()
}
// commit in one repo to let update work
cd(bro)
tac("bro.txt")
git("push")
// remove branch in another repo
cd(bromunity)
git("push origin :feature")
val updateProcess = GitUpdateProcess(myProject, EmptyProgressIndicator(), repositories(), UpdatedFiles.create(), false)
val result = updateProcess.update(UpdateMethod.MERGE)
assertEquals("Update result is incorrect", GitUpdateResult.NOT_READY, result)
assertErrorNotification("Can't Update", GitUpdateProcess.getNoTrackedBranchError(community, "feature"))
}
private fun updateWithMerge(): GitUpdateResult {
return GitUpdateProcess(myProject, EmptyProgressIndicator(), repositories(), UpdatedFiles.create(), false).update(UpdateMethod.MERGE)
}