]> source.dussan.org Git - jgit.git/commitdiff
Interactive Rebase: Do actions if there were conflicts 18/18118/5
authorStefan Lay <stefan.lay@sap.com>
Wed, 6 Nov 2013 10:16:34 +0000 (11:16 +0100)
committerMatthias Sohn <matthias.sohn@sap.com>
Fri, 8 Nov 2013 22:43:11 +0000 (23:43 +0100)
If a commit was marked for edit, reword, squash or fixup, but the
interactive rebase stopped because of a conflict, the step was not done
after conflict resolution. This is done now.

Change-Id: If8e7ccc50469165744f2b8a53d180f9ba0f72330
Signed-off-by: Stefan Lay <stefan.lay@sap.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java

index 531e1b0e2c352b9588ffd5eb8ec2434be30e8e5a..241d099d197961f618f7fa78afb15eca0d035658 100644 (file)
@@ -2327,6 +2327,213 @@ public class RebaseCommandTest extends RepositoryTestCase {
 
        }
 
+       @Test
+       public void testRebaseShouldStopForEditInCaseOfConflict()
+                       throws Exception {
+               // create file1 on master
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Add file1\nnew line").call();
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
+
+               //change file1
+               writeTrashFile(FILE1, FILE1 + "a");
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Change file1").call();
+
+               //change file1
+               writeTrashFile(FILE1, FILE1 + "b");
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Change file1").call();
+
+               RebaseResult result = git.rebase().setUpstream("HEAD~2")
+                               .runInteractively(new InteractiveHandler() {
+
+                                       public void prepareSteps(List<RebaseTodoLine> steps) {
+                                               steps.remove(0);
+                                               steps.get(0).setAction(Action.EDIT);
+                                       }
+
+                                       public String modifyCommitMessage(String commit) {
+                                               return commit;
+                                       }
+                               }).call();
+               assertEquals(Status.STOPPED, result.getStatus());
+               git.add().addFilepattern(FILE1).call();
+               result = git.rebase().setOperation(Operation.CONTINUE).call();
+               assertEquals(Status.EDIT, result.getStatus());
+
+       }
+
+       @Test
+       public void testRebaseShouldStopForRewordInCaseOfConflict()
+                       throws Exception {
+               // create file1 on master
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Add file1\nnew line").call();
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
+
+               // change file1
+               writeTrashFile(FILE1, FILE1 + "a");
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Change file1").call();
+
+               // change file1
+               writeTrashFile(FILE1, FILE1 + "b");
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Change file1").call();
+
+               RebaseResult result = git.rebase().setUpstream("HEAD~2")
+                               .runInteractively(new InteractiveHandler() {
+
+                                       public void prepareSteps(List<RebaseTodoLine> steps) {
+                                               steps.remove(0);
+                                               steps.get(0).setAction(Action.REWORD);
+                                       }
+
+                                       public String modifyCommitMessage(String commit) {
+                                               return "rewritten commit message";
+                                       }
+                               }).call();
+               assertEquals(Status.STOPPED, result.getStatus());
+               git.add().addFilepattern(FILE1).call();
+               result = git.rebase().runInteractively(new InteractiveHandler() {
+
+                       public void prepareSteps(List<RebaseTodoLine> steps) {
+                               steps.remove(0);
+                               steps.get(0).setAction(Action.REWORD);
+                       }
+
+                       public String modifyCommitMessage(String commit) {
+                               return "rewritten commit message";
+                       }
+               }).setOperation(Operation.CONTINUE).call();
+               assertEquals(Status.OK, result.getStatus());
+               Iterator<RevCommit> logIterator = git.log().all().call().iterator();
+               String actualCommitMag = logIterator.next().getShortMessage();
+               assertEquals("rewritten commit message", actualCommitMag);
+
+       }
+
+       @Test
+       public void testRebaseShouldSquashInCaseOfConflict() throws Exception {
+               // create file1 on master
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Add file1\nnew line").call();
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
+
+               // change file2
+               writeTrashFile("file2", "file2");
+               git.add().addFilepattern("file2").call();
+               git.commit().setMessage("Change file2").call();
+
+               // change file1
+               writeTrashFile(FILE1, FILE1 + "a");
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Change file1").call();
+
+               // change file1
+               writeTrashFile(FILE1, FILE1 + "b");
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Change file1").call();
+
+               RebaseResult result = git.rebase().setUpstream("HEAD~3")
+                               .runInteractively(new InteractiveHandler() {
+
+                                       public void prepareSteps(List<RebaseTodoLine> steps) {
+                                               steps.get(0).setAction(Action.PICK);
+                                               steps.remove(1);
+                                               steps.get(1).setAction(Action.SQUASH);
+                                       }
+
+                                       public String modifyCommitMessage(String commit) {
+                                               return "squashed message";
+                                       }
+                               }).call();
+               assertEquals(Status.STOPPED, result.getStatus());
+               git.add().addFilepattern(FILE1).call();
+               result = git.rebase().runInteractively(new InteractiveHandler() {
+
+                       public void prepareSteps(List<RebaseTodoLine> steps) {
+                               steps.get(0).setAction(Action.PICK);
+                               steps.remove(1);
+                               steps.get(1).setAction(Action.SQUASH);
+                       }
+
+                       public String modifyCommitMessage(String commit) {
+                               return "squashed message";
+                       }
+               }).setOperation(Operation.CONTINUE).call();
+               assertEquals(Status.OK, result.getStatus());
+               Iterator<RevCommit> logIterator = git.log().all().call().iterator();
+               String actualCommitMag = logIterator.next().getShortMessage();
+               assertEquals("squashed message", actualCommitMag);
+       }
+
+       @Test
+       public void testRebaseShouldFixupInCaseOfConflict() throws Exception {
+               // create file1 on master
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Add file1").call();
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
+
+               // change file2
+               writeTrashFile("file2", "file2");
+               git.add().addFilepattern("file2").call();
+               git.commit().setMessage("Change file2").call();
+
+               // change file1
+               writeTrashFile(FILE1, FILE1 + "a");
+               git.add().addFilepattern(FILE1).call();
+               git.commit().setMessage("Change file1").call();
+
+               // change file1, add file3
+               writeTrashFile(FILE1, FILE1 + "b");
+               writeTrashFile("file3", "file3");
+               git.add().addFilepattern(FILE1).call();
+               git.add().addFilepattern("file3").call();
+               git.commit().setMessage("Change file1, add file3").call();
+
+               RebaseResult result = git.rebase().setUpstream("HEAD~3")
+                               .runInteractively(new InteractiveHandler() {
+
+                                       public void prepareSteps(List<RebaseTodoLine> steps) {
+                                               steps.get(0).setAction(Action.PICK);
+                                               steps.remove(1);
+                                               steps.get(1).setAction(Action.FIXUP);
+                                       }
+
+                                       public String modifyCommitMessage(String commit) {
+                                               return commit;
+                                       }
+                               }).call();
+               assertEquals(Status.STOPPED, result.getStatus());
+               git.add().addFilepattern(FILE1).call();
+               result = git.rebase().runInteractively(new InteractiveHandler() {
+
+                       public void prepareSteps(List<RebaseTodoLine> steps) {
+                               steps.get(0).setAction(Action.PICK);
+                               steps.remove(1);
+                               steps.get(1).setAction(Action.FIXUP);
+                       }
+
+                       public String modifyCommitMessage(String commit) {
+                               return "commit";
+                       }
+               }).setOperation(Operation.CONTINUE).call();
+               assertEquals(Status.OK, result.getStatus());
+               Iterator<RevCommit> logIterator = git.log().all().call().iterator();
+               String actualCommitMsg = logIterator.next().getShortMessage();
+               assertEquals("Change file2", actualCommitMsg);
+               actualCommitMsg = logIterator.next().getShortMessage();
+               assertEquals("Add file1", actualCommitMsg);
+               assertTrue(new File(db.getWorkTree(), "file3").exists());
+
+       }
+
        private File getTodoFile() {
                File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO);
                return todoFile;
index ea48688ff92495129ebb8a995c1d6cf3b26eacb4..80f46ccf5a9bb2822b6098aaab6a880f7402ae1b 100644 (file)
@@ -61,11 +61,13 @@ import java.util.regex.Pattern;
 import org.eclipse.jgit.api.RebaseResult.Status;
 import org.eclipse.jgit.api.ResetCommand.ResetType;
 import org.eclipse.jgit.api.errors.CheckoutConflictException;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.InvalidRebaseStepException;
 import org.eclipse.jgit.api.errors.InvalidRefNameException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.NoHeadException;
+import org.eclipse.jgit.api.errors.NoMessageException;
 import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
 import org.eclipse.jgit.api.errors.UnmergedPathsException;
@@ -75,6 +77,7 @@ import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -197,6 +200,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
 
        private boolean stopAfterInitialization = false;
 
+       private RevCommit newHead;
+
+       private boolean lastStepWasForward;
+
        /**
         * @param repo
         */
@@ -220,8 +227,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
         */
        public RebaseResult call() throws GitAPIException, NoHeadException,
                        RefNotFoundException, WrongRepositoryStateException {
-               RevCommit newHead = null;
-               boolean lastStepWasForward = false;
+               newHead = null;
+               lastStepWasForward = false;
                checkCallable();
                checkParameters();
                try {
@@ -261,7 +268,19 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
 
                        if (operation == Operation.CONTINUE) {
                                newHead = continueRebase();
-
+                               List<RebaseTodoLine> doneLines = repo.readRebaseTodo(
+                                               rebaseState.getPath(DONE), true);
+                               RebaseTodoLine step = doneLines.get(doneLines.size() - 1);
+                               if (newHead != null
+                                               && step.getAction() != Action.PICK) {
+                                       RebaseTodoLine newStep = new RebaseTodoLine(
+                                                       step.getAction(),
+                                                       AbbreviatedObjectId.fromObjectId(newHead),
+                                                       step.getShortMessage());
+                                       RebaseResult result = processStep(newStep, false);
+                                       if (result != null)
+                                               return result;
+                               }
                                File amendFile = rebaseState.getFile(AMEND);
                                boolean amendExists = amendFile.exists();
                                if (amendExists) {
@@ -280,8 +299,6 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                        if (operation == Operation.SKIP)
                                newHead = checkoutCurrentHead();
 
-                       ObjectReader or = repo.newObjectReader();
-
                        List<RebaseTodoLine> steps = repo.readRebaseTodo(
                                        rebaseState.getPath(GIT_REBASE_TODO), false);
                        if (steps.size() == 0) {
@@ -296,81 +313,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                        for (int i = 0; i < steps.size(); i++) {
                                RebaseTodoLine step = steps.get(i);
                                popSteps(1);
-                               if (Action.COMMENT.equals(step.getAction()))
-                                       continue;
-                               Collection<ObjectId> ids = or.resolve(step.getCommit());
-                               if (ids.size() != 1)
-                                       throw new JGitInternalException(
-                                                       "Could not resolve uniquely the abbreviated object ID");
-                               RevCommit commitToPick = walk
-                                               .parseCommit(ids.iterator().next());
-                               if (monitor.isCancelled())
-                                       return new RebaseResult(commitToPick, Status.STOPPED);
-                               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
-                                               String ourCommitName = getOurCommitName();
-                                               CherryPickResult cherryPickResult = new Git(repo)
-                                                               .cherryPick().include(commitToPick)
-                                                               .setOurCommitName(ourCommitName)
-                                                               .setReflogPrefix("rebase:").call(); //$NON-NLS-1$
-                                               switch (cherryPickResult.getStatus()) {
-                                               case FAILED:
-                                                       if (operation == Operation.BEGIN)
-                                                               return abort(new RebaseResult(
-                                                                               cherryPickResult.getFailingPaths()));
-                                                       else
-                                                               return stop(commitToPick, Status.STOPPED);
-                                               case CONFLICTING:
-                                                       return stop(commitToPick, Status.STOPPED);
-                                               case OK:
-                                                       newHead = cherryPickResult.getNewHead();
-                                               }
-                                       }
-                                       boolean isSquash = false;
-                                       switch (step.getAction()) {
-                                       case PICK:
-                                               continue; // continue rebase process on pick command
-                                       case REWORD:
-                                               String oldMessage = commitToPick.getFullMessage();
-                                               String newMessage = interactiveHandler
-                                                               .modifyCommitMessage(oldMessage);
-                                               newHead = new Git(repo).commit().setMessage(newMessage)
-                                                               .setAmend(true).call();
-                                               continue;
-                                       case EDIT:
-                                               rebaseState.createFile(AMEND, commitToPick.name());
-                                               return stop(commitToPick, Status.EDIT);
-                                       case COMMENT:
-                                               break;
-                                       case SQUASH:
-                                               isSquash = true;
-                                               //$FALL-THROUGH$
-                                       case FIXUP:
-                                               resetSoftToParent();
-                                               RebaseTodoLine nextStep = (i >= steps.size() - 1 ? null
-                                                               : steps.get(i + 1));
-                                               File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP);
-                                               File messageSquashFile = rebaseState
-                                                               .getFile(MESSAGE_SQUASH);
-                                               if (isSquash && messageFixupFile.exists())
-                                                               messageFixupFile.delete();
-                                               newHead = doSquashFixup(isSquash, commitToPick,
-                                                               nextStep, messageFixupFile, messageSquashFile);
-                                       }
-                               } finally {
-                                       monitor.endTask();
+                               RebaseResult result = processStep(step, true);
+                               if (result != null) {
+                                       return result;
                                }
                        }
                        return finishRebase(newHead, lastStepWasForward);
@@ -381,6 +326,98 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                }
        }
 
+       private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick)
+                       throws IOException, GitAPIException {
+               if (Action.COMMENT.equals(step.getAction()))
+                       return null;
+               ObjectReader or = repo.newObjectReader();
+
+               Collection<ObjectId> ids = or.resolve(step.getCommit());
+               if (ids.size() != 1)
+                       throw new JGitInternalException(
+                                       "Could not resolve uniquely the abbreviated object ID");
+               RevCommit commitToPick = walk.parseCommit(ids.iterator().next());
+               if (shouldPick) {
+                       if (monitor.isCancelled())
+                               return new RebaseResult(commitToPick, Status.STOPPED);
+                       RebaseResult result = cherryPickCommit(commitToPick);
+                       if (result != null)
+                               return result;
+               }
+               boolean isSquash = false;
+               switch (step.getAction()) {
+               case PICK:
+                       return null; // continue rebase process on pick command
+               case REWORD:
+                       String oldMessage = commitToPick.getFullMessage();
+                       String newMessage = interactiveHandler
+                                       .modifyCommitMessage(oldMessage);
+                       newHead = new Git(repo).commit().setMessage(newMessage)
+                                       .setAmend(true).call();
+                       return null;
+               case EDIT:
+                       rebaseState.createFile(AMEND, commitToPick.name());
+                       return stop(commitToPick, Status.EDIT);
+               case COMMENT:
+                       break;
+               case SQUASH:
+                       isSquash = true;
+                       //$FALL-THROUGH$
+               case FIXUP:
+                       resetSoftToParent();
+                       List<RebaseTodoLine> steps = repo.readRebaseTodo(
+                                       rebaseState.getPath(GIT_REBASE_TODO), false);
+                       RebaseTodoLine nextStep = steps.size() > 0 ? steps.get(0) : null;
+                       File messageFixupFile = rebaseState.getFile(MESSAGE_FIXUP);
+                       File messageSquashFile = rebaseState.getFile(MESSAGE_SQUASH);
+                       if (isSquash && messageFixupFile.exists())
+                               messageFixupFile.delete();
+                       newHead = doSquashFixup(isSquash, commitToPick, nextStep,
+                                       messageFixupFile, messageSquashFile);
+               }
+               return null;
+       }
+
+       private RebaseResult cherryPickCommit(RevCommit commitToPick)
+                       throws IOException, GitAPIException, NoMessageException,
+                       UnmergedPathsException, ConcurrentRefUpdateException,
+                       WrongRepositoryStateException, NoHeadException {
+               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
+                               String ourCommitName = getOurCommitName();
+                               CherryPickResult cherryPickResult = new Git(repo).cherryPick()
+                                               .include(commitToPick).setOurCommitName(ourCommitName)
+                                               .setReflogPrefix("rebase:").call(); //$NON-NLS-1$
+                               switch (cherryPickResult.getStatus()) {
+                               case FAILED:
+                                       if (operation == Operation.BEGIN)
+                                               return abort(new RebaseResult(
+                                                               cherryPickResult.getFailingPaths()));
+                                       else
+                                               return stop(commitToPick, Status.STOPPED);
+                               case CONFLICTING:
+                                       return stop(commitToPick, Status.STOPPED);
+                               case OK:
+                                       newHead = cherryPickResult.getNewHead();
+                               }
+                       }
+                       return null;
+               } finally {
+                       monitor.endTask();
+               }
+       }
+
        private RebaseResult finishRebase(RevCommit newHead,
                        boolean lastStepWasForward) throws IOException {
                String headName = rebaseState.readFile(HEAD_NAME);