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 88bd6acfd..1646c7b48 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 153c10000..eea661cb3 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 */ -public class CherryPickCommand extends GitCommand { +public class CherryPickCommand extends GitCommand { private List commits = new LinkedList(); - private List cherryPickedRefs = new LinkedList(); - /** * @param repo */ @@ -94,14 +92,11 @@ public class CherryPickCommand extends GitCommand { * 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 - * null 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 cherryPickedRefs = new LinkedList(); checkCallable(); RevWalk revWalk = new RevWalk(repo); @@ -152,7 +147,11 @@ public class CherryPickCommand extends GitCommand { .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 { } finally { revWalk.release(); } - return newHead; + return new CherryPickResult(newHead, cherryPickedRefs); } /** @@ -198,13 +197,4 @@ public class CherryPickCommand extends GitCommand { return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name, commit.copy())); } - - /** - * @return the list of successfully cherry-picked {@link Ref}'s. Never - * null but maybe an empty list if no commit was - * successfully cherry-picked - */ - public List 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 000000000..8019e95a1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011, Philipp Thun + * 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 cherryPickedRefs; + + private final Map failingPaths; + + /** + * @param newHead + * commit the head points at after this cherry-pick + * @param cherryPickedRefs + * list of successfully cherry-picked Ref's + */ + public CherryPickResult(RevCommit newHead, List 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 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 CherryPickResult 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, + * null if {@link #getStatus} is not + * {@link CherryPickStatus#OK} + */ + public RevCommit getNewHead() { + return newHead; + } + + /** + * @return the list of successfully cherry-picked Ref's, + * null if {@link #getStatus} is not + * {@link CherryPickStatus#OK} + */ + public List getCherryPickedRefs() { + return cherryPickedRefs; + } + + /** + * @return the list of paths causing this cherry-pick to fail abnormally + * (see {@link ResolveMerger#getFailingPaths()} for details), + * null if {@link #getStatus} is not + * {@link CherryPickStatus#FAILED} + */ + public Map 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 72e92b476..0559e7836 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 { // 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);