summaryrefslogtreecommitdiffstatsabout
diff options
context:
space:
mode:
authorPhilipp Thun2011-03-24 07:24:23 (EDT)
committer Philipp Thun2011-03-24 07:24:23 (EDT)
commit0b5ad24915824a8c999bb6951b131a958e418306 (patch)
tree95efbadc7ecabf133767ccdde3c8699773be6048
parent55b7bd247ef86c6650c9acc364f426cec38723b5 (diff)
downloadjgit-0b5ad24915824a8c999bb6951b131a958e418306.zip
jgit-0b5ad24915824a8c999bb6951b131a958e418306.tar.gz
jgit-0b5ad24915824a8c999bb6951b131a958e418306.tar.bz2
Introduce FAILED result for RebaseCommandrefs/changes/47/2647/3
In case an underlying cherry-pick fails due to uncommitted changes, a RebaseCommand shall fail and roll-back changes. Change-Id: Ic22eb047fb03ac2c8391f777036b7dbf22a1b061 Signed-off-by: Philipp Thun <philipp.thun@sap.com>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java340
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java72
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java39
3 files changed, 424 insertions, 27 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index 8486b70..2b7d0e5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -57,6 +57,7 @@ import java.io.InputStreamReader;
import org.eclipse.jgit.api.RebaseCommand.Action;
import org.eclipse.jgit.api.RebaseCommand.Operation;
import org.eclipse.jgit.api.RebaseResult.Status;
+import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
@@ -67,6 +68,7 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
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.eclipse.jgit.revwalk.RevWalk;
import org.junit.Before;
@@ -878,6 +880,344 @@ public class RebaseCommandTest extends RepositoryTestCase {
}
}
+ @Test
+ public void testRebaseWithUntrackedFile() throws Exception {
+ // create file1, add and commit
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / create untracked file3
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file3", "untracked file3");
+
+ // rebase
+ assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master")
+ .call().getStatus());
+ }
+
+ @Test
+ @SuppressWarnings("null")
+ public void testRebaseWithUnstagedTopicChange() throws Exception {
+ // create file1, add and commit
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file2
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "unstaged file2");
+
+ // rebase
+ JGitInternalException exception = null;
+ try {
+ git.rebase().setUpstream("refs/heads/master").call();
+ } catch (JGitInternalException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals("Checkout conflict with files: \nfile2",
+ exception.getMessage());
+ }
+
+ @Test
+ @SuppressWarnings("null")
+ public void testRebaseWithUncommittedTopicChange() throws Exception {
+ // create file1, add and commit
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file2 and add
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "uncommitted file2");
+ git.add().addFilepattern("file2").call();
+ // do not commit
+
+ // rebase
+ JGitInternalException exception = null;
+ try {
+ git.rebase().setUpstream("refs/heads/master").call();
+ } catch (JGitInternalException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals("Checkout conflict with files: \nfile2",
+ exception.getMessage());
+ }
+
+ @Test
+ @SuppressWarnings("null")
+ public void testRebaseWithUnstagedMasterChange() throws Exception {
+ // create file1, add and commit
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file1
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile(FILE1, "unstaged modified file1");
+
+ // rebase
+ JGitInternalException exception = null;
+ try {
+ git.rebase().setUpstream("refs/heads/master").call();
+ } catch (JGitInternalException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals("Checkout conflict with files: \nfile1",
+ exception.getMessage());
+ }
+
+ @Test
+ @SuppressWarnings("null")
+ public void testRebaseWithUncommittedMasterChange() throws Exception {
+ // create file1, add and commit
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file1 and add
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile(FILE1, "uncommitted modified file1");
+ git.add().addFilepattern(FILE1).call();
+ // do not commit
+
+ // rebase
+ JGitInternalException exception = null;
+ try {
+ git.rebase().setUpstream("refs/heads/master").call();
+ } catch (JGitInternalException e) {
+ exception = e;
+ }
+ assertNotNull(exception);
+ assertEquals("Checkout conflict with files: \nfile1",
+ exception.getMessage());
+ }
+
+ @Test
+ public void testRebaseWithUnstagedMasterChangeBaseCommit() throws Exception {
+ // create file0 + file1, add and commit
+ writeTrashFile("file0", "file0");
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern("file0").addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file0
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file0", "unstaged modified file0");
+
+ // rebase
+ assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master")
+ .call().getStatus());
+ }
+
+ @Test
+ public void testRebaseWithUncommittedMasterChangeBaseCommit()
+ throws Exception {
+ // create file0 + file1, add and commit
+ File file0 = writeTrashFile("file0", "file0");
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern("file0").addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file0 and add
+ checkoutBranch("refs/heads/topic");
+ write(file0, "unstaged modified file0");
+ git.add().addFilepattern("file0").call();
+ // do not commit
+
+ // get current index state
+ String indexState = indexState(CONTENT);
+
+ // rebase
+ RebaseResult result = git.rebase().setUpstream("refs/heads/master")
+ .call();
+ assertEquals(Status.FAILED, result.getStatus());
+ // staged file0 causes DIRTY_INDEX
+ assertEquals(1, result.getFailingPaths().size());
+ assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths()
+ .get("file0"));
+ assertEquals("unstaged modified file0", read(file0));
+ // index shall be unchanged
+ assertEquals(indexState, indexState(CONTENT));
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ }
+
+ @Test
+ public void testRebaseWithUnstagedMasterChangeOtherCommit()
+ throws Exception {
+ // create file0, add and commit
+ writeTrashFile("file0", "file0");
+ git.add().addFilepattern("file0").call();
+ git.commit().setMessage("commit0").call();
+ // create file1, add and commit
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file0
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file0", "unstaged modified file0");
+
+ // rebase
+ assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master")
+ .call().getStatus());
+ }
+
+ @Test
+ public void testRebaseWithUncommittedMasterChangeOtherCommit()
+ throws Exception {
+ // create file0, add and commit
+ File file0 = writeTrashFile("file0", "file0");
+ git.add().addFilepattern("file0").call();
+ git.commit().setMessage("commit0").call();
+ // create file1, add and commit
+ writeTrashFile(FILE1, "file1");
+ git.add().addFilepattern(FILE1).call();
+ RevCommit commit = git.commit().setMessage("commit1").call();
+
+ // create topic branch and checkout / create file2, add and commit
+ createBranch(commit, "refs/heads/topic");
+ checkoutBranch("refs/heads/topic");
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("commit2").call();
+
+ // checkout master branch / modify file1, add and commit
+ checkoutBranch("refs/heads/master");
+ writeTrashFile(FILE1, "modified file1");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("commit3").call();
+
+ // checkout topic branch / modify file0 and add
+ checkoutBranch("refs/heads/topic");
+ write(file0, "unstaged modified file0");
+ git.add().addFilepattern("file0").call();
+ // do not commit
+
+ // get current index state
+ String indexState = indexState(CONTENT);
+
+ // rebase
+ RebaseResult result = git.rebase().setUpstream("refs/heads/master")
+ .call();
+ assertEquals(Status.FAILED, result.getStatus());
+ // staged file0 causes DIRTY_INDEX
+ assertEquals(1, result.getFailingPaths().size());
+ assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths()
+ .get("file0"));
+ assertEquals("unstaged modified file0", read(file0));
+ // index shall be unchanged
+ assertEquals(indexState, indexState(CONTENT));
+ assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+ }
+
private int countPicks() throws IOException {
int count = 0;
File todoFile = new File(db.getDirectory(),
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 0559e78..cf2111e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -199,7 +199,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
switch (operation) {
case ABORT:
try {
- return abort();
+ return abort(new RebaseResult(Status.ABORTED));
} catch (IOException ioe) {
throw new JGitInternalException(ioe.getMessage(), ioe);
}
@@ -217,12 +217,12 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
if (monitor.isCancelled())
- return abort();
+ return abort(new RebaseResult(Status.ABORTED));
- if (this.operation == Operation.CONTINUE)
+ if (operation == Operation.CONTINUE)
newHead = continueRebase();
- if (this.operation == Operation.SKIP)
+ if (operation == Operation.SKIP)
newHead = checkoutCurrentHead();
ObjectReader or = repo.newObjectReader();
@@ -238,23 +238,37 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
.parseCommit(ids.iterator().next());
if (monitor.isCancelled())
return new RebaseResult(commitToPick);
- monitor.beginTask(MessageFormat.format(
- JGitText.get().applyingCommit, commitToPick
- .getShortMessage()), ProgressMonitor.UNKNOWN);
- // if the first parent of commitToPick is the current HEAD,
- // we do a fast-forward instead of cherry-pick to avoid
- // unnecessary object rewriting
- newHead = tryFastForward(commitToPick);
- lastStepWasForward = newHead != null;
- if (!lastStepWasForward)
- // TODO if the content of this commit is already merged here
- // we should skip this step in order to avoid confusing
- // pseudo-changed
- newHead = new Git(repo).cherryPick().include(commitToPick)
- .call().getNewHead();
- monitor.endTask();
- if (newHead == null) {
- return stop(commitToPick);
+ try {
+ monitor.beginTask(MessageFormat.format(
+ JGitText.get().applyingCommit,
+ commitToPick.getShortMessage()),
+ ProgressMonitor.UNKNOWN);
+ // if the first parent of commitToPick is the current HEAD,
+ // we do a fast-forward instead of cherry-pick to avoid
+ // unnecessary object rewriting
+ newHead = tryFastForward(commitToPick);
+ lastStepWasForward = newHead != null;
+ if (!lastStepWasForward) {
+ // TODO if the content of this commit is already merged
+ // here we should skip this step in order to avoid
+ // confusing pseudo-changed
+ CherryPickResult cherryPickResult = new Git(repo)
+ .cherryPick().include(commitToPick).call();
+ switch (cherryPickResult.getStatus()) {
+ case FAILED:
+ if (operation == Operation.BEGIN)
+ return abort(new RebaseResult(
+ cherryPickResult.getFailingPaths()));
+ else
+ return stop(commitToPick);
+ case CONFLICTING:
+ return stop(commitToPick);
+ case OK:
+ newHead = cherryPickResult.getNewHead();
+ }
+ }
+ } finally {
+ monitor.endTask();
}
}
if (newHead != null) {
@@ -685,17 +699,23 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
}
- private RebaseResult abort() throws IOException {
+ private RebaseResult abort(RebaseResult result) throws IOException {
try {
String commitId = readFile(repo.getDirectory(), Constants.ORIG_HEAD);
monitor.beginTask(MessageFormat.format(
JGitText.get().abortingRebase, commitId),
ProgressMonitor.UNKNOWN);
+ DirCacheCheckout dco;
RevCommit commit = walk.parseCommit(repo.resolve(commitId));
- // no head in order to reset --hard
- DirCacheCheckout dco = new DirCacheCheckout(repo, repo
- .lockDirCache(), commit.getTree());
+ if (result.getStatus().equals(Status.FAILED)) {
+ RevCommit head = walk.parseCommit(repo.resolve(Constants.HEAD));
+ dco = new DirCacheCheckout(repo, head.getTree(),
+ repo.lockDirCache(), commit.getTree());
+ } else {
+ dco = new DirCacheCheckout(repo, repo.lockDirCache(),
+ commit.getTree());
+ }
dco.setFailOnConflict(false);
dco.checkout();
walk.release();
@@ -724,7 +744,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
// cleanup the files
FileUtils.delete(rebaseDir, FileUtils.RECURSIVE);
- return new RebaseResult(Status.ABORTED);
+ return result;
} finally {
monitor.endTask();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java
index b60336c..f750f8c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java
@@ -42,6 +42,10 @@
*/
package org.eclipse.jgit.api;
+import java.util.Map;
+
+import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
import org.eclipse.jgit.revwalk.RevCommit;
/**
@@ -65,6 +69,10 @@ public class RebaseResult {
*/
STOPPED,
/**
+ * Failed; the original HEAD was restored
+ */
+ FAILED,
+ /**
* Already up-to-date
*/
UP_TO_DATE,
@@ -81,17 +89,37 @@ public class RebaseResult {
private final RevCommit currentCommit;
+ private Map<String, MergeFailureReason> failingPaths;
+
RebaseResult(Status status) {
this.mySatus = status;
currentCommit = null;
}
+ /**
+ * Create <code>RebaseResult</code> with status {@link Status#STOPPED}
+ *
+ * @param commit
+ * current commit
+ */
RebaseResult(RevCommit commit) {
- this.mySatus = Status.STOPPED;
+ mySatus = Status.STOPPED;
currentCommit = commit;
}
/**
+ * Create <code>RebaseResult</code> with status {@link Status#FAILED}
+ *
+ * @param failingPaths
+ * list of paths causing this rebase to fail abnormally
+ */
+ RebaseResult(Map<String, MergeFailureReason> failingPaths) {
+ mySatus = Status.FAILED;
+ currentCommit = null;
+ this.failingPaths = failingPaths;
+ }
+
+ /**
* @return the overall status
*/
public Status getStatus() {
@@ -105,4 +133,13 @@ public class RebaseResult {
public RevCommit getCurrentCommit() {
return currentCommit;
}
+
+ /**
+ * @return the list of paths causing this rebase to fail abnormally (see
+ * {@link ResolveMerger#getFailingPaths()} for details) if status is
+ * {@link Status#FAILED}, otherwise <code>null</code>
+ */
+ public Map<String, MergeFailureReason> getFailingPaths() {
+ return failingPaths;
+ }
}