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")