[git] Support the older format of the "Local changes" error

"Local changes would be overwritten by checkout/merge" message has different format in older Gits (1.7.0.2 for example), which is not detected by the standard detector.

 Define a separate GitLocalChangesWouldBeOverwrittenDetector to handle both new and old formats. Use it instead of GitMessageWithFilesDetector.

 Define versions (1.7.1 and older) which have this old format.
 Don't test the set of local changes for these old versions, because Git returns only one in that case.
This commit is contained in:
Kirill Likhodedov
2012-10-11 17:35:16 +04:00
parent 15d09089a8
commit 6dda4ec065
6 changed files with 123 additions and 29 deletions
@@ -34,7 +34,6 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static git4idea.commands.GitMessageWithFilesDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT;
import static git4idea.util.GitUIUtil.code;
/**
@@ -69,14 +68,14 @@ class GitCheckoutOperation extends GitBranchOperation {
final GitRepository repository = next();
VirtualFile root = repository.getRoot();
GitMessageWithFilesDetector localChangesOverwrittenByCheckout =
new GitMessageWithFilesDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT, root);
GitLocalChangesWouldBeOverwrittenDetector localChangesDetector =
new GitLocalChangesWouldBeOverwrittenDetector(root, GitLocalChangesWouldBeOverwrittenDetector.Operation.CHECKOUT);
GitSimpleEventDetector unmergedFiles = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED_PREVENTING_CHECKOUT);
GitUntrackedFilesOverwrittenByOperationDetector untrackedOverwrittenByCheckout =
new GitUntrackedFilesOverwrittenByOperationDetector(root);
GitCommandResult result = myGit.checkout(repository, myStartPointReference, myNewBranch, false,
localChangesOverwrittenByCheckout, unmergedFiles, untrackedOverwrittenByCheckout);
localChangesDetector, unmergedFiles, untrackedOverwrittenByCheckout);
if (result.success()) {
refresh(repository);
markSuccessful(repository);
@@ -85,8 +84,8 @@ class GitCheckoutOperation extends GitBranchOperation {
fatalUnmergedFilesError();
fatalErrorHappened = true;
}
else if (localChangesOverwrittenByCheckout.wasMessageDetected()) {
boolean smartCheckoutSucceeded = smartCheckoutOrNotify(repository, localChangesOverwrittenByCheckout);
else if (localChangesDetector.wasMessageDetected()) {
boolean smartCheckoutSucceeded = smartCheckoutOrNotify(repository, localChangesDetector);
if (!smartCheckoutSucceeded) {
fatalErrorHappened = true;
}
@@ -42,8 +42,6 @@ import javax.swing.event.HyperlinkEvent;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static git4idea.commands.GitMessageWithFilesDetector.Event.LOCAL_CHANGES_OVERWRITTEN_BY_MERGE;
/**
* @author Kirill Likhodedov
*/
@@ -83,7 +81,8 @@ class GitMergeOperation extends GitBranchOperation {
LOG.info("next repository: " + repository);
VirtualFile root = repository.getRoot();
GitMessageWithFilesDetector localChangesOverwrittenByMerge = new GitMessageWithFilesDetector(LOCAL_CHANGES_OVERWRITTEN_BY_MERGE, root);
GitLocalChangesWouldBeOverwrittenDetector localChangesDetector =
new GitLocalChangesWouldBeOverwrittenDetector(root, GitLocalChangesWouldBeOverwrittenDetector.Operation.MERGE);
GitSimpleEventDetector unmergedFiles = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED_PREVENTING_MERGE);
GitUntrackedFilesOverwrittenByOperationDetector untrackedOverwrittenByMerge =
new GitUntrackedFilesOverwrittenByOperationDetector(root);
@@ -91,7 +90,7 @@ class GitMergeOperation extends GitBranchOperation {
GitSimpleEventDetector alreadyUpToDateDetector = new GitSimpleEventDetector(GitSimpleEventDetector.Event.ALREADY_UP_TO_DATE);
GitCommandResult result = myGit.merge(repository, myBranchToMerge, Collections.<String>emptyList(),
localChangesOverwrittenByMerge, unmergedFiles, untrackedOverwrittenByMerge, mergeConflict,
localChangesDetector, unmergedFiles, untrackedOverwrittenByMerge, mergeConflict,
alreadyUpToDateDetector);
if (result.success()) {
LOG.info("Merged successfully");
@@ -106,9 +105,9 @@ class GitMergeOperation extends GitBranchOperation {
fatalUnmergedFilesError();
fatalErrorHappened = true;
}
else if (localChangesOverwrittenByMerge.wasMessageDetected()) {
else if (localChangesDetector.wasMessageDetected()) {
LOG.info("Local changes would be overwritten by merge!");
boolean smartMergeSucceeded = proposeSmartMergePerformAndNotify(repository, localChangesOverwrittenByMerge);
boolean smartMergeSucceeded = proposeSmartMergePerformAndNotify(repository, localChangesDetector);
if (!smartMergeSucceeded) {
fatalErrorHappened = true;
}
@@ -0,0 +1,80 @@
/*
* Copyright 2000-2012 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.commands;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>Detects the error which happens, when some untracked working tree files prevent merge or checkout.</p>
*
* <p>Using a separate detector class instead of defining just an {@link GitMessageWithFilesDetector.Event},
* because older versions of Git have other format of displaying this error that doesn't match any existing detectors.</p>
*
* @author Kirill Likhodedov
*/
public class GitLocalChangesWouldBeOverwrittenDetector extends GitMessageWithFilesDetector {
private static final Pattern OLD_CHECKOUT_PATTERN = Pattern.compile(
".*You have local changes to '(.*)'; cannot switch branches.*"
);
private static final Pattern OLD_MERGE_PATTERN = Pattern.compile(
".*Your local changes to '(.*)' would be overwritten by merge.*"
);
// common for checkout and merge
public static final Event NEW_PATTERN = new Event(
"Your local changes to the following files would be overwritten by",
"commit your changes or stash them before");
@NotNull private final Operation myOperation;
public enum Operation {
CHECKOUT(OLD_CHECKOUT_PATTERN),
MERGE(OLD_MERGE_PATTERN);
@NotNull private final Pattern myPattern;
Operation(@NotNull Pattern pattern) {
myPattern = pattern;
}
@NotNull
public Pattern getPattern() {
return myPattern;
}
}
public GitLocalChangesWouldBeOverwrittenDetector(@NotNull VirtualFile root, @NotNull Operation operation) {
super(NEW_PATTERN, root);
myOperation = operation;
}
@Override
public void onLineAvailable(@NotNull String line, @NotNull Key outputType) {
super.onLineAvailable(line, outputType);
Matcher m = myOperation.getPattern().matcher(line);
if (m.matches()) {
myMessageDetected = true;
myAffectedFiles.add(m.group(1));
}
}
}
@@ -98,13 +98,6 @@ public class GitMessageWithFilesDetector implements GitLineHandlerListener {
}
public static class Event {
public static final Event LOCAL_CHANGES_OVERWRITTEN_BY_MERGE = new Event(
"Your local changes to the following files would be overwritten by merge",
"commit your changes or stash them before");
public static final Event LOCAL_CHANGES_OVERWRITTEN_BY_CHECKOUT = new Event(
"Your local changes to the following files would be overwritten by checkout",
"commit your changes or stash them before");
private final String myMessageStartMarker;
private final String myMessageEndMarker;
@@ -85,6 +85,18 @@ public enum GitVersionSpecialty {
}
},
/**
* Old style of messages returned by Git in the following 2 situations:
* - untracked files would be overwritten by checkout/merge;
* - local changes would be overwritten by checkout/merge;
*/
OLD_STYLE_OF_UNTRACKED_AND_LOCAL_CHANGES_WOULD_BE_OVERWRITTEN {
@Override
public boolean existsIn(@NotNull GitVersion version) {
return version.isOlderOrEqual(new GitVersion(1, 7, 1, 0));
}
},
DOESNT_DEFINE_HOME_ENV_VAR {
@Override
public boolean existsIn(@NotNull GitVersion version) {
@@ -28,6 +28,8 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.vcs.MockChangeListManager
import git4idea.PlatformFacade
import git4idea.commands.Git
import git4idea.config.GitVersion
import git4idea.config.GitVersionSpecialty
import git4idea.history.browser.GitCommit
import git4idea.repo.GitRepository
import git4idea.repo.GitRepositoryImpl
@@ -316,9 +318,15 @@ class GitBranchWorkerTest {
])
assertNotNull "Local changes were not shown in the dialog", changes
assertEquals "Incorrect set of local changes was shown in the dialog",
localChanges,
changes.collect({ FileUtil.getRelativePath(myUltimate.root.path, it.afterRevision.file.path, File.separatorChar) })
if (newGitVersion()) {
assertEquals "Incorrect set of local changes was shown in the dialog",
localChanges,
changes.collect({ FileUtil.getRelativePath(myUltimate.root.path, it.afterRevision.file.path, File.separatorChar) })
}
}
boolean newGitVersion() {
return !GitVersionSpecialty.OLD_STYLE_OF_UNTRACKED_AND_LOCAL_CHANGES_WOULD_BE_OVERWRITTEN.existsIn(GitVersion.parse(git("version")));
}
Change[] changesFromFiles(Collection<String> paths) {
@@ -334,11 +342,11 @@ class GitBranchWorkerTest {
@Test
public void "agree to smart checkout should smart checkout"() {
agree_to_smart_operation("checkout", "Checked out <b><code>feature</code></b>")
def localChanges = agree_to_smart_operation("checkout", "Checked out <b><code>feature</code></b>")
assertCurrentBranch("feature");
cd myUltimate
def actual = cat("local.txt")
def actual = cat(localChanges[0])
assertEquals("Content doesn't match",
"""line with branch changes
common content
@@ -350,10 +358,11 @@ line with master changes
@Test
public void "agree to smart merge should smart merge"() {
agree_to_smart_operation("merge", "Merged <b><code>feature</code></b> to <b><code>master</code></b><br/><a href='delete'>Delete feature</a>")
def localChanges = agree_to_smart_operation("merge",
"Merged <b><code>feature</code></b> to <b><code>master</code></b><br/><a href='delete'>Delete feature</a>")
cd myUltimate
def actual = cat("local.txt")
def actual = cat(localChanges[0])
assertEquals("Content doesn't match",
"""line with branch changes
common content
@@ -363,17 +372,19 @@ line with master changes
""", actual)
}
def agree_to_smart_operation(String operation, String expectedSuccessMessage) {
prepareLocalChangesOverwrittenBy(myUltimate)
Collection<String> agree_to_smart_operation(String operation, String expectedSuccessMessage) {
def localChanges = prepareLocalChangesOverwrittenBy(myUltimate)
AgreeToSmartOperationTestUiHandler handler = new AgreeToSmartOperationTestUiHandler()
checkoutOrMerge(operation, "feature", handler)
assertNotNull "No success notification was shown", handler.mySuccessMessage
assertEquals "Success message is incorrect", expectedSuccessMessage, handler.mySuccessMessage
localChanges
}
def prepareLocalChangesOverwrittenBy(GitRepository repository, int numFiles = 1) {
Collection<String> prepareLocalChangesOverwrittenBy(GitRepository repository, int numFiles = 1) {
def localChanges = []
for (int i = 0; i < numFiles; i++) {
localChanges.add("local${i}.txt")