myChanges;
+
+ private static class Change {
+
+ public void apply() {
+ switch (myType) {
+ case MODIFIED:
+ echo(myFile, myContent);
+ break;
+ case ADDED:
+ touch(myFile, myContent);
+ break;
+ case DELETED:
+ throw new UnsupportedOperationException("Not implemented yet");
+ case MOVED:
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+ }
+
+ enum Type {
+ MODIFIED, ADDED, DELETED, MOVED
+ }
+
+ private final Type myType;
+ private final String myFile;
+ private final String myContent;
+
+ Change(Type type, String filename, String content) {
+ myType = type;
+ myFile = filename;
+ myContent = content;
+ }
+
+ }
+
+ private enum ParsingStage {
+ MESSAGE,
+ DATA,
+ CHANGES
+ }
+
+ /**
+ * Format:
+ *
+ commit subject
+
+ and optional description
+ -----
+ Author: John Bro
+ Changes:
+ M file.txt "feature changes"
+ *
+ */
+ public static CommitDetails parse(String hash, String details) {
+ CommitDetails commit = new CommitDetails();
+ commit.myHash = hash;
+
+ StringBuilder message = new StringBuilder();
+ Collection changes = new ArrayList();
+ ParsingStage stage = ParsingStage.MESSAGE;
+ for (String line : details.split("\n")) {
+ if (line.equals("-----")) {
+ stage = ParsingStage.DATA;
+ continue;
+ }
+ else if (line.equals("Changes:")) {
+ stage = ParsingStage.CHANGES;
+ continue;
+ }
+
+ if (stage == ParsingStage.MESSAGE) {
+ message.append(line);
+ }
+ else if (stage == ParsingStage.CHANGES) {
+ changes.add(parseChange(line));
+ }
+ else if (line.toLowerCase().startsWith("author: ")) {
+ commit.myAuthor = line.substring("author: ".length());
+ }
+ }
+
+ commit.myMessage = message.toString();
+ commit.myChanges = changes;
+ return commit;
+ }
+
+ private static Change parseChange(String change) {
+ int firstSpace = change.indexOf(' ');
+ int secondSpace = change.indexOf(' ', firstSpace + 1);
+ return new Change(parseType(change.substring(0, firstSpace)),
+ change.substring(firstSpace + 1, secondSpace),
+ change.substring(secondSpace + 1));
+ }
+
+ private static Change.Type parseType(String type) {
+ if (type.equals("M")) {
+ return Change.Type.MODIFIED;
+ }
+ else if (type.equals("A")) {
+ return Change.Type.ADDED;
+ }
+ else if (type.equals("D")) {
+ return Change.Type.DELETED;
+ }
+ else if (type.equals("R")) {
+ return Change.Type.MOVED;
+ }
+ return null;
+ }
+
+ /**
+ * @return real commit details.
+ */
+ public CommitDetails apply() {
+ for (Change change : myChanges) {
+ change.apply();
+ }
+
+ String commitOutput = git(String.format("commit -am '%1$s' --author '%2$s <%2$s@example.com>'", myMessage, myAuthor));
+ CommitDetails realCommit = parseHashFromCommitOutput(commitOutput);
+ virtualCommits.register(myHash, realCommit);
+ return realCommit;
+ }
+
+ CommitDetails parseHashFromCommitOutput(String commitOutput) {
+ String line = commitOutput.split("\n")[0];
+ Pattern reg = Pattern.compile("^\\s*\\[.+ ([a-fA-F0-9]+)\\] (.+)$");
+ Matcher matcher = reg.matcher(line);
+ boolean matches = matcher.matches();
+ assert matches;
+ return new CommitDetails().hash(matcher.group(1)).message(matcher.group(2));
+ }
+
+ private CommitDetails hash(String hash) {
+ myHash = hash;
+ return this;
+ }
+
+ private CommitDetails message(String message) {
+ myMessage = message;
+ return this;
+ }
+
+ public String getHash() {
+ return myHash;
+ }
+
+ public String getMessage() {
+ return myMessage;
+ }
+
+}
diff --git a/plugins/git4idea/test-stepdefs/git4idea/GeneralStepdefs.java b/plugins/git4idea/test-stepdefs/git4idea/GeneralStepdefs.java
new file mode 100644
index 000000000000..51137075adbc
--- /dev/null
+++ b/plugins/git4idea/test-stepdefs/git4idea/GeneralStepdefs.java
@@ -0,0 +1,80 @@
+package git4idea;/*
+ * 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.
+ */
+
+import com.intellij.dvcs.test.MockProject;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.io.FileUtil;
+import cucumber.annotation.After;
+import cucumber.annotation.Before;
+import cucumber.annotation.en.Given;
+import git4idea.test.GitTestImpl;
+import git4idea.test.GitTestPlatformFacade;
+
+import java.io.File;
+import java.io.IOException;
+
+import static com.intellij.dvcs.test.Executor.cd;
+import static com.intellij.dvcs.test.Executor.mkdir;
+import static git4idea.GitCucumberWorld.*;
+import static git4idea.test.GitExecutor.git;
+import static git4idea.test.GitExecutor.touch;
+import static git4idea.test.GitScenarios.checkout;
+import static git4idea.test.GitTestInitUtil.createRepository;
+
+/**
+ * @author Kirill Likhodedov
+ */
+public class GeneralStepdefs {
+
+ @Before
+ public void setUpProject() throws IOException {
+ myTestRoot = FileUtil.createTempDirectory("", "").getPath();
+ cd(myTestRoot);
+ myProjectRoot = mkdir("project");
+ myProject = new MockProject(myProjectRoot);
+ myPlatformFacade = new GitTestPlatformFacade();
+ myGit = new GitTestImpl();
+ mySettings = myPlatformFacade.getSettings(myProject);
+
+ cd(myProjectRoot);
+ myRepository = createRepository(myProjectRoot, myPlatformFacade, myProject);
+
+ virtualCommits = new GitTestVirtualCommitsHolder();
+ }
+
+ @After
+ public void cleanup() {
+ FileUtil.delete(new File(myTestRoot));
+ Disposer.dispose(myProject);
+ }
+
+ @Given("^file (.*) \"(.*)\" on master$")
+ public void file_file_txt_on_master(String filename, String content) throws Throwable {
+ checkout(myRepository, "master");
+ touch(filename, content);
+ git("add %s", filename);
+ git("commit -m 'adding %s'", filename);
+ }
+
+ @Given("^commit (.+) on branch (.+)$")
+ public void commit_on_branch_feature(String hash, String branch, String commitDetails) throws Throwable {
+ CommitDetails commit = CommitDetails.parse(hash, commitDetails);
+ checkout(branch);
+ commit.apply();
+ checkout("master");
+ }
+
+}
diff --git a/plugins/git4idea/test-stepdefs/git4idea/GitCherryPickStepdefs.java b/plugins/git4idea/test-stepdefs/git4idea/GitCherryPickStepdefs.java
new file mode 100644
index 000000000000..cfcaa0f05810
--- /dev/null
+++ b/plugins/git4idea/test-stepdefs/git4idea/GitCherryPickStepdefs.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.intellij.dvcs.test.MockVirtualFile;
+import com.intellij.notification.Notification;
+import com.intellij.openapi.vcs.FilePathImpl;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.LocalChangeList;
+import com.intellij.openapi.vcs.history.VcsRevisionNumber;
+import com.intellij.openapi.vfs.newvfs.impl.NullVirtualFile;
+import com.intellij.testFramework.vcs.MockChangeListManager;
+import com.intellij.testFramework.vcs.MockContentRevision;
+import cucumber.annotation.en.And;
+import cucumber.annotation.en.Given;
+import cucumber.annotation.en.Then;
+import cucumber.annotation.en.When;
+import git4idea.history.browser.GitCherryPicker;
+import git4idea.history.browser.GitCommit;
+import git4idea.history.browser.SHAHash;
+import git4idea.history.wholeTree.AbstractHash;
+import git4idea.test.TestNotificator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+
+import static com.intellij.dvcs.test.Executor.echo;
+import static git4idea.GitCucumberWorld.*;
+import static git4idea.test.GitExecutor.git;
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * @author Kirill Likhodedov
+ */
+
+public class GitCherryPickStepdefs {
+
+ @Given("^(enabled|disabled) auto-commit in the settings$")
+ public void auto_commit_in_the_settings(String state) {
+ boolean enabled = state.equals("enabled");
+ myPlatformFacade.getSettings(myProject).setAutoCommitOnCherryPick(enabled);
+ }
+
+ @When("^I cherry-pick the commit (.+)$")
+ public void I_cherry_pick_the_commit(String hash) {
+ CommitDetails realCommit = virtualCommits.getRealCommit(hash);
+ new GitCherryPicker(myProject, myGit, myPlatformFacade, mySettings.isAutoCommitOnCherryPick())
+ .cherryPick(Collections.singletonMap(myRepository, Collections.singletonList(mockCommit(realCommit.getHash(), realCommit.getMessage()))));
+ }
+
+ private static GitCommit mockCommit(String hash, String message) {
+ AbstractHash ahash = AbstractHash.create(hash);
+ List changes = new ArrayList();
+ changes.add(new Change(null, new MockContentRevision(new FilePathImpl(new MockVirtualFile("name")), VcsRevisionNumber.NULL)));
+ return new GitCommit(NullVirtualFile.INSTANCE, ahash, SHAHash.emulate(ahash), "John Smith", null, null, message, message,
+ null, null, null, null, null, null, null, changes, 0);
+ }
+
+ @Then("^the last commit is$")
+ public void the_last_commit_is(String message) {
+ String actual = git("log -1 --pretty=%B");
+ message = virtualCommits.replaceVirtualHashes(message);
+ assertEquals("Commit doesn't match", message, trimHash(actual));
+ }
+
+ @And("^there is notification '(.*)'$")
+ public void there_is_notification(String title) {
+ assertEquals("Notification title is incorrect", title, lastNotification().getTitle());
+ }
+
+ private static Notification lastNotification() {
+ return ((TestNotificator)myPlatformFacade.getNotificator(myProject)).getLastNotification();
+ }
+
+ @And("^no new changelists are created$")
+ public void no_new_changelists_are_created() {
+ assertOnlyDefaultChangelist();
+ }
+
+ void assertOnlyDefaultChangelist() {
+ String DEFAULT = MockChangeListManager.DEFAULT_CHANGE_LIST_NAME;
+ assertChangeLists(Collections.singleton(DEFAULT), DEFAULT);
+ }
+
+ void assertChangeLists(Collection changeLists, String activeChangelist) {
+ ChangeListManager changeListManager = myPlatformFacade.getChangeListManager(myProject);
+ List lists = changeListManager.getChangeLists();
+ Collection listNames = Collections2.transform(lists, new Function() {
+ @Override
+ public String apply(LocalChangeList input) {
+ return input.getName();
+ }
+ });
+ assertEquals("Change lists are different", new ArrayList(changeLists), new ArrayList(listNames));
+ assertEquals("Wrong active changelist", activeChangelist, changeListManager.getDefaultChangeList().getName());
+ }
+
+ @Given("^(.+) is locally modified:$")
+ public void is_locally_modified(String filename, String content) {
+ echo(filename, content);
+ }
+
+ String trimHash(String commitMessage) {
+ int hashStart = commitMessage.lastIndexOf(' ') + 1;
+ String hash = commitMessage.substring(hashStart);
+ return commitMessage.replace(hash, hash.substring(0, 7)) + ")";
+ }
+
+ @Then("^nothing is committed$")
+ public void nothing_is_committed() {
+ assertFalse("Working tree is unexpectedly clean", git("diff").trim().isEmpty());
+ }
+
+ @And("^error notification '(.+)' is shown:$")
+ public void error_notification_is_shown(String title, String content) {
+ assertEquals("Notification title is incorrect", title, lastNotification().getTitle());
+ assertEquals("Notification content is incorrect", virtualCommits.replaceVirtualHashes(content),
+ convertNotificationHtml(lastNotification().getContent()));
+ }
+
+ private static String convertNotificationHtml(String content) {
+ return content.replaceAll("
", Matcher.quoteReplacement("\n"));
+ }
+}
\ No newline at end of file
diff --git a/plugins/git4idea/test-stepdefs/git4idea/GitCucumberWorld.java b/plugins/git4idea/test-stepdefs/git4idea/GitCucumberWorld.java
index 375c000f9ce5..b92dbc55d816 100644
--- a/plugins/git4idea/test-stepdefs/git4idea/GitCucumberWorld.java
+++ b/plugins/git4idea/test-stepdefs/git4idea/GitCucumberWorld.java
@@ -2,6 +2,7 @@ package git4idea;
import com.intellij.dvcs.test.MockProject;
import git4idea.commands.Git;
+import git4idea.config.GitVcsSettings;
import git4idea.repo.GitRepository;
/**
@@ -18,5 +19,7 @@ public class GitCucumberWorld {
public static GitPlatformFacade myPlatformFacade;
public static Git myGit;
public static GitRepository myRepository;
+ public static GitVcsSettings mySettings;
+ public static GitTestVirtualCommitsHolder virtualCommits;
}
diff --git a/plugins/git4idea/test-stepdefs/git4idea/GitTestVirtualCommitsHolder.java b/plugins/git4idea/test-stepdefs/git4idea/GitTestVirtualCommitsHolder.java
new file mode 100644
index 000000000000..57d6c01c88c9
--- /dev/null
+++ b/plugins/git4idea/test-stepdefs/git4idea/GitTestVirtualCommitsHolder.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * So called Virtual Commits are used in Cucumber feature files, so that the writer of a test could refer to commits in the natural way:
+ * via hashes. We can't define a hash to the commit when committing to Git, therefore to match these two sets we store them here.
+ *
+ * @author Kirill Likhodedov
+ */
+public class GitTestVirtualCommitsHolder {
+
+ // virtual hash -> commit details
+ private Map commits = new HashMap();
+
+ void register(String virtualHash, CommitDetails CommitInfo) {
+ commits.put(virtualHash, CommitInfo);
+ }
+
+ CommitDetails getRealCommit(String virtualHash) {
+ return commits.get(virtualHash);
+ }
+
+ String replaceVirtualHashes(String message) {
+ for (Map.Entry entry : commits.entrySet()) {
+ message = message.replace(entry.getKey(), entry.getValue().getHash());
+ }
+ return message;
+ }
+
+}
diff --git a/plugins/git4idea/testFramework/git4idea/test/GitExecutor.groovy b/plugins/git4idea/testFramework/git4idea/test/GitExecutor.groovy
index 3b3c3b4e5672..263aa9dd1317 100644
--- a/plugins/git4idea/testFramework/git4idea/test/GitExecutor.groovy
+++ b/plugins/git4idea/testFramework/git4idea/test/GitExecutor.groovy
@@ -95,10 +95,16 @@ class GitExecutor extends Executor {
}
public static String git(GitRepository repository, String command) {
- cd repository.root.path
+ if (repository != null) {
+ cd repository.root.path
+ }
git command
}
+ public static String git(String formatString, String... args) {
+ return git(String.format(formatString, args))
+ }
+
public static void cd(GitRepository repository) {
cd repository.root.path
}
diff --git a/plugins/git4idea/testFramework/git4idea/test/GitScenarios.groovy b/plugins/git4idea/testFramework/git4idea/test/GitScenarios.groovy
index 02d8f66c90a8..9953bce904d1 100644
--- a/plugins/git4idea/testFramework/git4idea/test/GitScenarios.groovy
+++ b/plugins/git4idea/testFramework/git4idea/test/GitScenarios.groovy
@@ -154,16 +154,17 @@ class GitScenarios {
git("commit -m just_a_commit")
}
- public static boolean branchExists(GitRepository repo, String branch) {
+ public static boolean branchExists(GitRepository repo = null, String branch) {
git(repo, "branch").contains(branch)
}
- public static void checkout(GitRepository repository, String branch) {
- if (branchExists(repository, branch)) {
+ public static void checkout(GitRepository repository = null, String branch) {
+ if (branch.equals("master") || branchExists(repository, branch)) {
git("checkout $branch")
}
else {
git("checkout -b $branch")
}
}
+
}
diff --git a/plugins/git4idea/testFramework/git4idea/test/GitTestImpl.groovy b/plugins/git4idea/testFramework/git4idea/test/GitTestImpl.groovy
index fddc63797e4c..83121783cb66 100644
--- a/plugins/git4idea/testFramework/git4idea/test/GitTestImpl.groovy
+++ b/plugins/git4idea/testFramework/git4idea/test/GitTestImpl.groovy
@@ -32,10 +32,12 @@ import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
import java.lang.reflect.Method
+import static com.intellij.dvcs.test.Executor.cd;
+import static GitExecutor.git;
+
/**
* @author Kirill Likhodedov
*/
-@Mixin(GitExecutor)
public class GitTestImpl implements Git {
@NotNull
@@ -197,13 +199,13 @@ public class GitTestImpl implements Git {
@NotNull String hash,
boolean autoCommit,
@NotNull GitLineHandlerListener... listeners) {
- execute(repository, "cherry-pick -x ${autoCommit ? "" : "-n"} $hash")
+ return execute(repository, "cherry-pick -x ${autoCommit ? "" : "-n"} $hash", listeners);
}
@NotNull
@Override
public GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) {
- execute(repository, "ls-files --unmerged")
+ return execute(repository, "ls-files --unmerged");
}
@NotNull
@@ -216,28 +218,41 @@ public class GitTestImpl implements Git {
}
private static GitCommandResult commandResult(String output) {
- boolean success = !output.split("\n").collect { isError(it) }.contains(true)
- return new GitCommandResult(success, 0, Collections.emptyList(), Arrays.asList(StringUtil.splitByLines(output)))
+ Collection err = new ArrayList<>();
+ Collection out = new ArrayList<>();
+ for (String line : output.split("\n")) {
+ if (isError(line)) {
+ err.add(line);
+ }
+ else {
+ out.add(line);
+ }
+ }
+ boolean success = err.isEmpty();
+ return new GitCommandResult(success, 0, err, out);
}
private static boolean isError(String s) {
// we don't want to make that method public, since it is reused only in the test.
- Method m = GitImpl.class.getDeclaredMethod("isError", String.class)
- m.setAccessible(true)
- return m.invoke(null, s) as boolean
+ Method m = GitImpl.class.getDeclaredMethod("isError", String.class);
+ m.setAccessible(true);
+ return (boolean) m.invoke(null, s);
}
- static def feedOutput(String output, GitLineHandlerListener... listeners) {
+ private static void feedOutput(String output, GitLineHandlerListener... listeners) {
listeners.each { GitLineHandlerListener listener ->
- output.split("\n").each { listener.onLineAvailable(it, ProcessOutputTypes.STDERR) }
+ String split = output.split("\n")
+ for (String line : split) {
+ listener.onLineAvailable(line, ProcessOutputTypes.STDERR);
+ }
}
}
- def execute(GitRepository repository, String operation, GitLineHandlerListener... listeners) {
- cd repository.root.path
- def out = git(operation)
- feedOutput(out, listeners)
- commandResult(out)
+ private static GitCommandResult execute(GitRepository repository, String operation, GitLineHandlerListener... listeners) {
+ cd(repository.getRoot().getPath());
+ String out = git(operation);
+ feedOutput(out, listeners);
+ return commandResult(out);
}
}
diff --git a/plugins/git4idea/testFramework/git4idea/test/GitTestInitUtil.groovy b/plugins/git4idea/testFramework/git4idea/test/GitTestInitUtil.groovy
index 850323ee78dd..4674d988b5f3 100644
--- a/plugins/git4idea/testFramework/git4idea/test/GitTestInitUtil.groovy
+++ b/plugins/git4idea/testFramework/git4idea/test/GitTestInitUtil.groovy
@@ -6,6 +6,9 @@ import git4idea.GitPlatformFacade
import git4idea.repo.GitRepository
import git4idea.repo.GitRepositoryImpl
+import static com.intellij.dvcs.test.Executor.*
+import static git4idea.test.GitExecutor.*
+
/**
*
* @author Kirill Likhodedov
@@ -20,17 +23,17 @@ class GitTestInitUtil {
* @param repoRoot
*/
public static void initRepo(String repoRoot) {
- com.intellij.dvcs.test.Executor.cd repoRoot
- git4idea.test.GitExecutor.git("init")
+ cd repoRoot
+ git("init")
setupUsername();
- com.intellij.dvcs.test.Executor.touch("initial.txt")
- git4idea.test.GitExecutor.git("add initial.txt")
- git4idea.test.GitExecutor.git("commit -m initial")
+ touch("initial.txt")
+ git("add initial.txt")
+ git("commit -m initial")
}
public static void setupUsername() {
- git4idea.test.GitExecutor.git("config user.name $USER_NAME")
- git4idea.test.GitExecutor.git("config user.email $USER_EMAIL")
+ git("config user.name $USER_NAME")
+ git("config user.email $USER_EMAIL")
}
public static GitRepository createRepository(String rootDir, GitPlatformFacade platformFacade, Project project) {