summaryrefslogtreecommitdiffstatsabout
diff options
context:
space:
mode:
authorPhilipp Thun2011-03-23 05:24:14 (EDT)
committer Mathias Kinzler2011-03-23 05:24:14 (EDT)
commita21e508a318664dac23701ca29d5f0b64d36a76a (patch)
tree080712e1c619a0522a90d975b423074a2a0e1b63
parent770c733687d9f2f71f30822f9691427bf83b7577 (diff)
downloadjgit-a21e508a318664dac23701ca29d5f0b64d36a76a.zip
jgit-a21e508a318664dac23701ca29d5f0b64d36a76a.tar.gz
jgit-a21e508a318664dac23701ca29d5f0b64d36a76a.tar.bz2
Introduce CherryPickResultrefs/changes/46/2646/4
In order to distinguish cherry-pick failures caused by conflicts vs. 'abnormal failures' (e.g. due to unstaged changes or a dirty worktree), a CherryPickResult class is introduced and returned by CherryPickCommand.call() instead of a RevCommit. This new class is similar to MergeResult and RebaseResult. The CherryPickResult contains all necessary information, e.g. paths causing the cherry-pick (a merge called within, respectively) to fail. This allows callers to better react on failures. Change-Id: I5db57b9259e82ed118e4bf4ec94463efe68b8c1f Signed-off-by: Philipp Thun <philipp.thun@sap.com> Signed-off-by: Mathias Kinzler <mathias.kinzler@sap.com>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java73
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java164
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java4
4 files changed, 250 insertions, 23 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
index 88bd6ac..1646c7b 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java
@@ -49,9 +49,12 @@ import java.io.File;
import java.io.IOException;
import java.util.Iterator;
+import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.RepositoryTestCase;
+import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
@@ -99,4 +102,74 @@ public class CherryPickCommandTest extends RepositoryTestCase {
assertEquals("create a", history.next().getFullMessage());
assertFalse(history.hasNext());
}
+
+ @Test
+ public void testCherryPickDirtyIndex() throws Exception {
+ Git git = new Git(db);
+ RevCommit sideCommit = prepareCherryPick(git);
+
+ // modify and add file a
+ writeTrashFile("a", "a(modified)");
+ git.add().addFilepattern("a").call();
+ // do not commit
+
+ doCherryPickAndCheckResult(git, sideCommit,
+ MergeFailureReason.DIRTY_INDEX);
+ }
+
+ @Test
+ public void testCherryPickDirtyWorktree() throws Exception {
+ Git git = new Git(db);
+ RevCommit sideCommit = prepareCherryPick(git);
+
+ // modify file a
+ writeTrashFile("a", "a(modified)");
+ // do not add and commit
+
+ doCherryPickAndCheckResult(git, sideCommit,
+ MergeFailureReason.DIRTY_WORKTREE);
+ }
+
+ private RevCommit prepareCherryPick(final Git git) throws Exception {
+ // create, add and commit file a
+ writeTrashFile("a", "a");
+ git.add().addFilepattern("a").call();
+ RevCommit firstMasterCommit = git.commit().setMessage("first master")
+ .call();
+
+ // create and checkout side branch
+ createBranch(firstMasterCommit, "refs/heads/side");
+ checkoutBranch("refs/heads/side");
+ // modify, add and commit file a
+ writeTrashFile("a", "a(side)");
+ git.add().addFilepattern("a").call();
+ RevCommit sideCommit = git.commit().setMessage("side").call();
+
+ // checkout master branch
+ checkoutBranch("refs/heads/master");
+ // modify, add and commit file a
+ writeTrashFile("a", "a(master)");
+ git.add().addFilepattern("a").call();
+ git.commit().setMessage("second master").call();
+ return sideCommit;
+ }
+
+ private void doCherryPickAndCheckResult(final Git git,
+ final RevCommit sideCommit, final MergeFailureReason reason)
+ throws Exception {
+ // get current index state
+ String indexState = indexState(CONTENT);
+
+ // cherry-pick
+ CherryPickResult result = git.cherryPick().include(sideCommit.getId())
+ .call();
+ assertEquals(CherryPickStatus.FAILED, result.getStatus());
+ // staged file a causes DIRTY_INDEX
+ assertEquals(1, result.getFailingPaths().size());
+ assertEquals(reason, result.getFailingPaths().get("a"));
+ assertEquals("a(modified)", read(new File(db.getWorkTree(), "a")));
+ // index shall be unchanged
+ assertEquals(indexState, indexState(CONTENT));
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 153c100..eea661c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -49,8 +49,8 @@ import java.util.List;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException;
import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.lib.AnyObjectId;
@@ -76,11 +76,9 @@ import org.eclipse.jgit.treewalk.FileTreeIterator;
* href="http://www.kernel.org/pub/software/scm/git/docs/git-cherry-pick.html"
* >Git documentation about cherry-pick</a>
*/
-public class CherryPickCommand extends GitCommand<RevCommit> {
+public class CherryPickCommand extends GitCommand<CherryPickResult> {
private List<Ref> commits = new LinkedList<Ref>();
- private List<Ref> cherryPickedRefs = new LinkedList<Ref>();
-
/**
* @param repo
*/
@@ -94,14 +92,11 @@ public class CherryPickCommand extends GitCommand<RevCommit> {
* this class. Each instance of this class should only be used for one
* invocation of the command. Don't call this method twice on an instance.
*
- * @return on success the {@link RevCommit} pointed to by the new HEAD is
- * returned. If a failure occurred during cherry-pick
- * <code>null</code> is returned. The list of successfully
- * cherry-picked {@link Ref}'s can be obtained by calling
- * {@link #getCherryPickedRefs()}
+ * @return the result of the cherry-pick
*/
- public RevCommit call() throws GitAPIException {
+ public CherryPickResult call() throws GitAPIException {
RevCommit newHead = null;
+ List<Ref> cherryPickedRefs = new LinkedList<Ref>();
checkCallable();
RevWalk revWalk = new RevWalk(repo);
@@ -152,7 +147,11 @@ public class CherryPickCommand extends GitCommand<RevCommit> {
.setAuthor(srcCommit.getAuthorIdent()).call();
cherryPickedRefs.add(src);
} else {
- return null;
+ if (merger.failedAbnormally())
+ return new CherryPickResult(merger.getFailingPaths());
+
+ // merge conflicts
+ return CherryPickResult.CONFLICT;
}
}
} catch (IOException e) {
@@ -163,7 +162,7 @@ public class CherryPickCommand extends GitCommand<RevCommit> {
} finally {
revWalk.release();
}
- return newHead;
+ return new CherryPickResult(newHead, cherryPickedRefs);
}
/**
@@ -198,13 +197,4 @@ public class CherryPickCommand extends GitCommand<RevCommit> {
return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
commit.copy()));
}
-
- /**
- * @return the list of successfully cherry-picked {@link Ref}'s. Never
- * <code>null</code> but maybe an empty list if no commit was
- * successfully cherry-picked
- */
- public List<Ref> getCherryPickedRefs() {
- return cherryPickedRefs;
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
new file mode 100644
index 0000000..8019e95
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2011, Philipp Thun <philipp.thun@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.api;
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * Encapsulates the result of a {@link CherryPickCommand}.
+ */
+public class CherryPickResult {
+
+ /**
+ * The cherry-pick status
+ */
+ public enum CherryPickStatus {
+ /** */
+ OK {
+ @Override
+ public String toString() {
+ return "Ok";
+ }
+ },
+ /** */
+ FAILED {
+ public String toString() {
+ return "Failed";
+ }
+ },
+ /** */
+ CONFLICTING {
+ public String toString() {
+ return "Conflicting";
+ }
+ }
+ }
+
+ private final CherryPickStatus status;
+
+ private final RevCommit newHead;
+
+ private final List<Ref> cherryPickedRefs;
+
+ private final Map<String, MergeFailureReason> failingPaths;
+
+ /**
+ * @param newHead
+ * commit the head points at after this cherry-pick
+ * @param cherryPickedRefs
+ * list of successfully cherry-picked <code>Ref</code>'s
+ */
+ public CherryPickResult(RevCommit newHead, List<Ref> cherryPickedRefs) {
+ this.status = CherryPickStatus.OK;
+ this.newHead = newHead;
+ this.cherryPickedRefs = cherryPickedRefs;
+ this.failingPaths = null;
+ }
+
+ /**
+ * @param failingPaths
+ * list of paths causing this cherry-pick to fail abnormally (see
+ * {@link ResolveMerger#getFailingPaths()} for details)
+ */
+ public CherryPickResult(Map<String, MergeFailureReason> failingPaths) {
+ this.status = CherryPickStatus.FAILED;
+ this.newHead = null;
+ this.cherryPickedRefs = null;
+ this.failingPaths = failingPaths;
+ }
+
+ private CherryPickResult(CherryPickStatus status) {
+ this.status = status;
+ this.newHead = null;
+ this.cherryPickedRefs = null;
+ this.failingPaths = null;
+ }
+
+ /**
+ * A <code>CherryPickResult</code> with status
+ * {@link CherryPickStatus#CONFLICTING}
+ */
+ public static CherryPickResult CONFLICT = new CherryPickResult(
+ CherryPickStatus.CONFLICTING);
+
+ /**
+ * @return the status this cherry-pick resulted in
+ */
+ public CherryPickStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * @return the commit the head points at after this cherry-pick,
+ * <code>null</code> if {@link #getStatus} is not
+ * {@link CherryPickStatus#OK}
+ */
+ public RevCommit getNewHead() {
+ return newHead;
+ }
+
+ /**
+ * @return the list of successfully cherry-picked <code>Ref</code>'s,
+ * <code>null</code> if {@link #getStatus} is not
+ * {@link CherryPickStatus#OK}
+ */
+ public List<Ref> getCherryPickedRefs() {
+ return cherryPickedRefs;
+ }
+
+ /**
+ * @return the list of paths causing this cherry-pick to fail abnormally
+ * (see {@link ResolveMerger#getFailingPaths()} for details),
+ * <code>null</code> if {@link #getStatus} is not
+ * {@link CherryPickStatus#FAILED}
+ */
+ public Map<String, MergeFailureReason> getFailingPaths() {
+ return failingPaths;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 72e92b4..0559e78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -84,8 +84,8 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
@@ -251,7 +251,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
// we should skip this step in order to avoid confusing
// pseudo-changed
newHead = new Git(repo).cherryPick().include(commitToPick)
- .call();
+ .call().getNewHead();
monitor.endTask();
if (newHead == null) {
return stop(commitToPick);