From 6dda4ec06567b6b51a88f0dadb3a31916e5e7a4e Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Thu, 11 Oct 2012 16:02:42 +0400 Subject: [PATCH] [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. --- .../git4idea/branch/GitCheckoutOperation.java | 11 ++- .../git4idea/branch/GitMergeOperation.java | 11 ++- ...ocalChangesWouldBeOverwrittenDetector.java | 80 +++++++++++++++++++ .../commands/GitMessageWithFilesDetector.java | 7 -- .../git4idea/config/GitVersionSpecialty.java | 12 +++ .../branch/GitBranchWorkerTest.groovy | 31 ++++--- 6 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java diff --git a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java index 480fb5defb2d..bfe7c2543ffa 100644 --- a/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitCheckoutOperation.java @@ -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; } diff --git a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java index 87692a4e7644..55b3f54fa576 100644 --- a/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java +++ b/plugins/git4idea/src/git4idea/branch/GitMergeOperation.java @@ -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.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; } diff --git a/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java b/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java new file mode 100644 index 000000000000..b62f12806761 --- /dev/null +++ b/plugins/git4idea/src/git4idea/commands/GitLocalChangesWouldBeOverwrittenDetector.java @@ -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; + +/** + *

Detects the error which happens, when some untracked working tree files prevent merge or checkout.

+ * + *

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.

+ * + * @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)); + } + } +} diff --git a/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java b/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java index 526cb4ad0805..a7cdfe1c84bb 100644 --- a/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java +++ b/plugins/git4idea/src/git4idea/commands/GitMessageWithFilesDetector.java @@ -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; diff --git a/plugins/git4idea/src/git4idea/config/GitVersionSpecialty.java b/plugins/git4idea/src/git4idea/config/GitVersionSpecialty.java index fbb0826f1fd8..37a2d4aff285 100644 --- a/plugins/git4idea/src/git4idea/config/GitVersionSpecialty.java +++ b/plugins/git4idea/src/git4idea/config/GitVersionSpecialty.java @@ -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) { diff --git a/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy b/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy index 6af91bfd4817..e7d6005fcf13 100644 --- a/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy +++ b/plugins/git4idea/tests/git4idea/branch/GitBranchWorkerTest.groovy @@ -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 paths) { @@ -334,11 +342,11 @@ class GitBranchWorkerTest { @Test public void "agree to smart checkout should smart checkout"() { - agree_to_smart_operation("checkout", "Checked out feature") + def localChanges = agree_to_smart_operation("checkout", "Checked out feature") 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 feature to master
Delete feature") + def localChanges = agree_to_smart_operation("merge", + "Merged feature to master
Delete feature") 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 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 prepareLocalChangesOverwrittenBy(GitRepository repository, int numFiles = 1) { def localChanges = [] for (int i = 0; i < numFiles; i++) { localChanges.add("local${i}.txt")