]> source.dussan.org Git - jgit.git/commitdiff
Implement rebase --continue and --skip 81/2081/3
authorMathias Kinzler <mathias.kinzler@sap.com>
Thu, 9 Dec 2010 15:10:21 +0000 (16:10 +0100)
committerMathias Kinzler <mathias.kinzler@sap.com>
Thu, 9 Dec 2010 15:10:21 +0000 (16:10 +0100)
For --continue, the Rebase command asserts that there are no unmerged
paths in the current repository. Then it checks if a commit is needed.
If yes, the commit message and author are taken from the author_script
and message files, respectively, and a commit is performed before the
next step is applied.
For --skip, the workspace is reset to the current HEAD before applying
the next step.

Includes some tests and a refactoring that extracts Strings in the
code into constants.

Change-Id: I72d9968535727046e737ec20e23239fe79976179
Signed-off-by: Mathias Kinzler <mathias.kinzler@sap.com>
Signed-off-by: Christian Halstrick <christian.halstrick@sap.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java [new file with mode: 0644]

index bddb9ed6e9fbab3232194a11abe14d50b9913f66..35b05e7195b46fa81f7c5714fda255cbbe5be454 100644 (file)
@@ -51,10 +51,12 @@ import java.io.InputStreamReader;
 import org.eclipse.jgit.api.RebaseCommand.Operation;
 import org.eclipse.jgit.api.RebaseResult.Status;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.api.errors.UnmergedPathsException;
 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RepositoryState;
 import org.eclipse.jgit.lib.RepositoryTestCase;
@@ -62,6 +64,16 @@ import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 
 public class RebaseCommandTest extends RepositoryTestCase {
+       private static final String FILE1 = "file1";
+
+       protected Git git;
+
+       @Override
+       protected void setUp() throws Exception {
+               super.setUp();
+               this.git = new Git(db);
+       }
+
        private void createBranch(ObjectId objectId, String branchName)
                        throws IOException {
                RefUpdate updateRef = db.updateRef(branchName);
@@ -88,8 +100,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
                        IOException {
                RevWalk walk = new RevWalk(db);
                RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
-               DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree(),
-                               db.lockDirCache(), commit.getTree());
+               DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree(), db
+                               .lockDirCache(), commit.getTree());
                dco.setFailOnConflict(true);
                dco.checkout();
                walk.release();
@@ -100,14 +112,12 @@ public class RebaseCommandTest extends RepositoryTestCase {
        }
 
        public void testFastForwardWithNewFile() throws Exception {
-               Git git = new Git(db);
-
                // create file1 on master
-               writeTrashFile("file1", "file1");
-               git.add().addFilepattern("file1").call();
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
                RevCommit first = git.commit().setMessage("Add file1").call();
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                // create a topic branch
                createBranch(first, "refs/heads/topic");
                // create file2 on master
@@ -124,32 +134,27 @@ public class RebaseCommandTest extends RepositoryTestCase {
        }
 
        public void testUpToDate() throws Exception {
-               Git git = new Git(db);
-
                // create file1 on master
-               writeTrashFile("file1", "file1");
-               git.add().addFilepattern("file1").call();
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
                RevCommit first = git.commit().setMessage("Add file1").call();
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
 
                RebaseResult res = git.rebase().setUpstream(first).call();
                assertEquals(Status.UP_TO_DATE, res.getStatus());
        }
 
        public void testUnknownUpstream() throws Exception {
-               Git git = new Git(db);
-
                // create file1 on master
-               writeTrashFile("file1", "file1");
-               git.add().addFilepattern("file1").call();
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
                git.commit().setMessage("Add file1").call();
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
 
                try {
-                       git.rebase().setUpstream("refs/heads/xyz")
-                                       .call();
+                       git.rebase().setUpstream("refs/heads/xyz").call();
                        fail("expected exception was not thrown");
                } catch (RefNotFoundException e) {
                        // expected exception
@@ -157,17 +162,15 @@ public class RebaseCommandTest extends RepositoryTestCase {
        }
 
        public void testConflictFreeWithSingleFile() throws Exception {
-               Git git = new Git(db);
-
                // create file1 on master
-               File theFile = writeTrashFile("file1", "1\n2\n3\n");
-               git.add().addFilepattern("file1").call();
+               File theFile = writeTrashFile(FILE1, "1\n2\n3\n");
+               git.add().addFilepattern(FILE1).call();
                RevCommit second = git.commit().setMessage("Add file1").call();
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                // change first line in master and commit
-               writeTrashFile("file1", "1master\n2\n3\n");
+               writeTrashFile(FILE1, "1master\n2\n3\n");
                checkFile(theFile, "1master\n2\n3\n");
-               git.add().addFilepattern("file1").call();
+               git.add().addFilepattern(FILE1).call();
                RevCommit lastMasterChange = git.commit().setMessage(
                                "change file1 in master").call();
 
@@ -177,10 +180,10 @@ public class RebaseCommandTest extends RepositoryTestCase {
                // we have the old content again
                checkFile(theFile, "1\n2\n3\n");
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                // change third line in topic branch
-               writeTrashFile("file1", "1\n2\n3\ntopic\n");
-               git.add().addFilepattern("file1").call();
+               writeTrashFile(FILE1, "1\n2\n3\ntopic\n");
+               git.add().addFilepattern(FILE1).call();
                git.commit().setMessage("change file1 in topic").call();
 
                RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
@@ -193,19 +196,17 @@ public class RebaseCommandTest extends RepositoryTestCase {
        }
 
        public void testDetachedHead() throws Exception {
-               Git git = new Git(db);
-
                // create file1 on master
-               File theFile = writeTrashFile("file1", "1\n2\n3\n");
-               git.add().addFilepattern("file1").call();
+               File theFile = writeTrashFile(FILE1, "1\n2\n3\n");
+               git.add().addFilepattern(FILE1).call();
                RevCommit second = git.commit().setMessage("Add file1").call();
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                // change first line in master and commit
-               writeTrashFile("file1", "1master\n2\n3\n");
+               writeTrashFile(FILE1, "1master\n2\n3\n");
                checkFile(theFile, "1master\n2\n3\n");
-               git.add().addFilepattern("file1").call();
-               RevCommit lastMasterChange = git.commit()
-                               .setMessage("change file1 in master").call();
+               git.add().addFilepattern(FILE1).call();
+               RevCommit lastMasterChange = git.commit().setMessage(
+                               "change file1 in master").call();
 
                // create a topic branch based on second commit
                createBranch(second, "refs/heads/topic");
@@ -213,10 +214,10 @@ public class RebaseCommandTest extends RepositoryTestCase {
                // we have the old content again
                checkFile(theFile, "1\n2\n3\n");
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                // change third line in topic branch
-               writeTrashFile("file1", "1\n2\n3\ntopic\n");
-               git.add().addFilepattern("file1").call();
+               writeTrashFile(FILE1, "1\n2\n3\ntopic\n");
+               git.add().addFilepattern(FILE1).call();
                RevCommit topicCommit = git.commit()
                                .setMessage("change file1 in topic").call();
                checkoutBranch("refs/heads/master");
@@ -226,18 +227,15 @@ public class RebaseCommandTest extends RepositoryTestCase {
                RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
                assertEquals(Status.OK, res.getStatus());
                checkFile(theFile, "1master\n2\n3\ntopic\n");
-               assertEquals(lastMasterChange,
-                               new RevWalk(db).parseCommit(db.resolve(Constants.HEAD))
-                                               .getParent(0));
+               assertEquals(lastMasterChange, new RevWalk(db).parseCommit(
+                               db.resolve(Constants.HEAD)).getParent(0));
 
        }
 
        public void testFilesAddedFromTwoBranches() throws Exception {
-               Git git = new Git(db);
-
                // create file1 on master
-               writeTrashFile("file1", "file1");
-               git.add().addFilepattern("file1").call();
+               writeTrashFile(FILE1, FILE1);
+               git.add().addFilepattern(FILE1).call();
                RevCommit masterCommit = git.commit().setMessage("Add file1 to master")
                                .call();
 
@@ -256,14 +254,14 @@ public class RebaseCommandTest extends RepositoryTestCase {
                git.add().addFilepattern("file3").call();
                git.commit().setMessage("Add file3 to branch file3").call();
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                assertFalse(new File(db.getWorkTree(), "file2").exists());
                assertTrue(new File(db.getWorkTree(), "file3").exists());
 
                RebaseResult res = git.rebase().setUpstream("refs/heads/file2").call();
                assertEquals(Status.OK, res.getStatus());
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                assertTrue(new File(db.getWorkTree(), "file2").exists());
                assertTrue(new File(db.getWorkTree(), "file3").exists());
 
@@ -273,54 +271,40 @@ public class RebaseCommandTest extends RepositoryTestCase {
                                db.resolve(Constants.HEAD)).getParent(0));
 
                checkoutBranch("refs/heads/file2");
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
+               assertTrue(new File(db.getWorkTree(), FILE1).exists());
                assertTrue(new File(db.getWorkTree(), "file2").exists());
                assertFalse(new File(db.getWorkTree(), "file3").exists());
        }
 
-       public void testAbortOnConflict() throws Exception {
-               Git git = new Git(db);
-
+       public void testStopOnConflict() throws Exception {
                // create file1 on master
-               File theFile = writeTrashFile("file1", "1\n2\n3\n");
-               git.add().addFilepattern("file1").call();
-               RevCommit firstInMaster = git.commit().setMessage("Add file1").call();
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
-               // change first line in master and commit
-               writeTrashFile("file1", "1master\n2\n3\n");
-               checkFile(theFile, "1master\n2\n3\n");
-               git.add().addFilepattern("file1").call();
-               git.commit().setMessage("change file1 in master").call();
-
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3");
+               // change first line in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
+               checkFile(FILE1, "1master", "2", "3");
                // create a topic branch based on second commit
                createBranch(firstInMaster, "refs/heads/topic");
                checkoutBranch("refs/heads/topic");
                // we have the old content again
-               checkFile(theFile, "1\n2\n3\n");
+               checkFile(FILE1, "1", "2", "3");
 
-               assertTrue(new File(db.getWorkTree(), "file1").exists());
                // add a line (non-conflicting)
-               writeTrashFile("file1", "1\n2\n3\ntopic4\n");
-               git.add().addFilepattern("file1").call();
-               git.commit().setMessage("add a line to file1 in topic").call();
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "topic4");
 
                // change first line (conflicting)
-               writeTrashFile("file1", "1topic\n2\n3\ntopic4\n");
-               git.add().addFilepattern("file1").call();
-               RevCommit conflicting = git.commit()
-                               .setMessage("change file1 in topic").call();
+               RevCommit conflicting = writeFileAndCommit(FILE1,
+                               "change file1 in topic", "1topic", "2", "3", "topic4");
 
-               // change second line (not conflicting)
-               writeTrashFile("file1", "1topic\n2topic\n3\ntopic4\n");
-               git.add().addFilepattern("file1").call();
-               RevCommit lastTopicCommit = git.commit().setMessage(
-                               "change file1 in topic again").call();
+               RevCommit lastTopicCommit = writeFileAndCommit(FILE1,
+                               "change file1 in topic again", "1topic", "2", "3", "topic4");
 
                RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
                assertEquals(Status.STOPPED, res.getStatus());
                assertEquals(conflicting, res.getCurrentCommit());
-               checkFile(theFile,
-                               "<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\ntopic4\n");
+               checkFile(FILE1,
+                               "<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\ntopic4");
 
                assertEquals(RepositoryState.REBASING_INTERACTIVE, db
                                .getRepositoryState());
@@ -341,7 +325,7 @@ public class RebaseCommandTest extends RepositoryTestCase {
                res = git.rebase().setOperation(Operation.ABORT).call();
                assertEquals(res.getStatus(), Status.ABORTED);
                assertEquals("refs/heads/topic", db.getFullBranch());
-               checkFile(theFile, "1topic\n2topic\n3\ntopic4\n");
+               checkFile(FILE1, "1topic", "2", "3", "topic4");
                RevWalk rw = new RevWalk(db);
                assertEquals(lastTopicCommit, rw
                                .parseCommit(db.resolve(Constants.HEAD)));
@@ -351,12 +335,335 @@ public class RebaseCommandTest extends RepositoryTestCase {
                assertFalse(new File(db.getDirectory(), "rebase-merge").exists());
        }
 
-       public void testAbortOnConflictFileCreationAndDeletion() throws Exception {
-               Git git = new Git(db);
+       public void testStopOnConflictAndContinue() throws Exception {
+               // create file1 on master
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3");
+               // change in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
+
+               checkFile(FILE1, "1master", "2", "3");
+               // create a topic branch based on the first commit
+               createBranch(firstInMaster, "refs/heads/topic");
+               checkoutBranch("refs/heads/topic");
+               // we have the old content again
+               checkFile(FILE1, "1", "2", "3");
+
+               // add a line (non-conflicting)
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "4topic");
+
+               // change first line (conflicting)
+               writeFileAndCommit(FILE1,
+                               "change file1 in topic\n\nThis is conflicting", "1topic", "2",
+                               "3", "4topic");
+
+               // change second line (not conflicting)
+               writeFileAndCommit(FILE1, "change file1 in topic again", "1topic",
+                               "2topic", "3", "4topic");
+
+               RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
+               assertEquals(Status.STOPPED, res.getStatus());
+
+               // continue should throw a meaningful exception
+               try {
+                       res = git.rebase().setOperation(Operation.CONTINUE).call();
+                       fail("Expected Exception not thrown");
+               } catch (UnmergedPathsException e) {
+                       // expected
+               }
+
+               // merge the file; the second topic commit should go through
+               writeFileAndAdd(FILE1, "1topic", "2", "3", "4topic");
+
+               res = git.rebase().setOperation(Operation.CONTINUE).call();
+               assertNotNull(res);
+               assertEquals(Status.OK, res.getStatus());
+               assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+
+               ObjectId headId = db.resolve(Constants.HEAD);
+               RevWalk rw = new RevWalk(db);
+               RevCommit rc = rw.parseCommit(headId);
+               RevCommit parent = rw.parseCommit(rc.getParent(0));
+               assertEquals("change file1 in topic\n\nThis is conflicting", parent
+                               .getFullMessage());
+       }
+
+       public void testStopOnConflictAndFailContinueIfFileIsDirty()
+                       throws Exception {
+               // create file1 on master
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3");
+               // change in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
+
+               checkFile(FILE1, "1master", "2", "3");
+               // create a topic branch based on the first commit
+               createBranch(firstInMaster, "refs/heads/topic");
+               checkoutBranch("refs/heads/topic");
+               // we have the old content again
+               checkFile(FILE1, "1", "2", "3");
+
+               // add a line (non-conflicting)
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "4topic");
+
+               // change first line (conflicting)
+               writeFileAndCommit(FILE1,
+                               "change file1 in topic\n\nThis is conflicting", "1topic", "2",
+                               "3", "4topic");
+
+               // change second line (not conflicting)
+               writeFileAndCommit(FILE1, "change file1 in topic again", "1topic",
+                               "2topic", "3", "4topic");
+
+               RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
+               assertEquals(Status.STOPPED, res.getStatus());
+
+               git.add().addFilepattern(FILE1).call();
+               File trashFile = writeTrashFile(FILE1, "Some local change");
+
+               res = git.rebase().setOperation(Operation.CONTINUE).call();
+               assertNotNull(res);
+               assertEquals(Status.STOPPED, res.getStatus());
+               checkFile(trashFile, "Some local change");
+       }
+
+       public void testStopOnLastConflictAndContinue() throws Exception {
+               // create file1 on master
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3");
+               // change in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
+
+               checkFile(FILE1, "1master", "2", "3");
+               // create a topic branch based on the first commit
+               createBranch(firstInMaster, "refs/heads/topic");
+               checkoutBranch("refs/heads/topic");
+               // we have the old content again
+               checkFile(FILE1, "1", "2", "3");
+
+               // add a line (non-conflicting)
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "4topic");
+
+               // change first line (conflicting)
+               writeFileAndCommit(FILE1,
+                               "change file1 in topic\n\nThis is conflicting", "1topic", "2",
+                               "3", "4topic");
+
+               RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
+               assertEquals(Status.STOPPED, res.getStatus());
+
+               // merge the file; the second topic commit should go through
+               writeFileAndAdd(FILE1, "1topic", "2", "3", "4topic");
+
+               res = git.rebase().setOperation(Operation.CONTINUE).call();
+               assertNotNull(res);
+               assertEquals(Status.OK, res.getStatus());
+               assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+       }
+
+       public void testStopOnLastConflictAndSkip() throws Exception {
+               // create file1 on master
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3");
+               // change in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
+
+               checkFile(FILE1, "1master", "2", "3");
+               // create a topic branch based on the first commit
+               createBranch(firstInMaster, "refs/heads/topic");
+               checkoutBranch("refs/heads/topic");
+               // we have the old content again
+               checkFile(FILE1, "1", "2", "3");
+
+               // add a line (non-conflicting)
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "4topic");
+
+               // change first line (conflicting)
+               writeFileAndCommit(FILE1,
+                               "change file1 in topic\n\nThis is conflicting", "1topic", "2",
+                               "3", "4topic");
+
+               RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
+               assertEquals(Status.STOPPED, res.getStatus());
+
+               // merge the file; the second topic commit should go through
+               writeFileAndAdd(FILE1, "1topic", "2", "3", "4topic");
+
+               res = git.rebase().setOperation(Operation.SKIP).call();
+               assertNotNull(res);
+               assertEquals(Status.OK, res.getStatus());
+               assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+       }
+
+       public void testStopOnConflictAndSkipNoConflict() throws Exception {
+               // create file1 on master
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3");
+               // change in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
+
+               checkFile(FILE1, "1master", "2", "3");
+               // create a topic branch based on the first commit
+               createBranch(firstInMaster, "refs/heads/topic");
+               checkoutBranch("refs/heads/topic");
+               // we have the old content again
+               checkFile(FILE1, "1", "2", "3");
+
+               // add a line (non-conflicting)
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "4topic");
+
+               // change first line (conflicting)
+               writeFileAndCommit(FILE1,
+                               "change file1 in topic\n\nThis is conflicting", "1topic", "2",
+                               "3", "4topic");
+
+               // change third line (not conflicting)
+               writeFileAndCommit(FILE1, "change file1 in topic again", "1topic", "2",
+                               "3topic", "4topic");
+
+               RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
+               assertEquals(Status.STOPPED, res.getStatus());
+
+               res = git.rebase().setOperation(Operation.SKIP).call();
+
+               checkFile(FILE1, "1master", "2", "3topic", "4topic");
+               assertEquals(Status.OK, res.getStatus());
+       }
+
+       public void testStopOnConflictAndSkipWithConflict() throws Exception {
+               // create file1 on master
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3", "4");
+               // change in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2",
+                               "3master", "4");
+
+               checkFile(FILE1, "1master", "2", "3master", "4");
+               // create a topic branch based on the first commit
+               createBranch(firstInMaster, "refs/heads/topic");
+               checkoutBranch("refs/heads/topic");
+               // we have the old content again
+               checkFile(FILE1, "1", "2", "3", "4");
+
+               // add a line (non-conflicting)
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "4", "5topic");
+
+               // change first line (conflicting)
+               writeFileAndCommit(FILE1,
+                               "change file1 in topic\n\nThis is conflicting", "1topic", "2",
+                               "3", "4", "5topic");
+
+               // change third line (conflicting)
+               writeFileAndCommit(FILE1, "change file1 in topic again", "1topic", "2",
+                               "3topic", "4", "5topic");
+
+               RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
+               assertEquals(Status.STOPPED, res.getStatus());
+
+               res = git.rebase().setOperation(Operation.SKIP).call();
+               // TODO is this correct? It is what the command line returns
+               checkFile(FILE1,
+                               "1master\n2\n<<<<<<< OURS\n3master\n=======\n3topic\n>>>>>>> THEIRS\n4\n5topic");
+               assertEquals(Status.STOPPED, res.getStatus());
+       }
+
+       public void testStopOnConflictCommitAndContinue() throws Exception {
+               // create file1 on master
+               RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
+                               "2", "3");
+               // change in master
+               writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
+
+               checkFile(FILE1, "1master", "2", "3");
+               // create a topic branch based on the first commit
+               createBranch(firstInMaster, "refs/heads/topic");
+               checkoutBranch("refs/heads/topic");
+               // we have the old content again
+               checkFile(FILE1, "1", "2", "3");
+
+               // add a line (non-conflicting)
+               writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
+                               "3", "4topic");
+
+               // change first line (conflicting)
+               writeFileAndCommit(FILE1,
+                               "change file1 in topic\n\nThis is conflicting", "1topic", "2",
+                               "3", "4topic");
+
+               // change second line (not conflicting)
+               writeFileAndCommit(FILE1, "change file1 in topic again", "1topic", "2",
+                               "3topic", "4topic");
+
+               RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
+               assertEquals(Status.STOPPED, res.getStatus());
+
+               // continue should throw a meaningful exception
+               try {
+                       res = git.rebase().setOperation(Operation.CONTINUE).call();
+                       fail("Expected Exception not thrown");
+               } catch (UnmergedPathsException e) {
+                       // expected
+               }
+
+               // merge the file; the second topic commit should go through
+               writeFileAndCommit(FILE1, "A different commit message", "1topic", "2",
+                               "3", "4topic");
+
+               res = git.rebase().setOperation(Operation.CONTINUE).call();
+               assertNotNull(res);
+               assertEquals(Status.OK, res.getStatus());
+               assertEquals(RepositoryState.SAFE, db.getRepositoryState());
+
+               ObjectId headId = db.resolve(Constants.HEAD);
+               RevWalk rw = new RevWalk(db);
+               RevCommit rc = rw.parseCommit(headId);
+               RevCommit parent = rw.parseCommit(rc.getParent(0));
+               assertEquals("A different commit message", parent.getFullMessage());
+       }
+
+       private RevCommit writeFileAndCommit(String fileName, String commitMessage,
+                       String... lines) throws Exception {
+               StringBuilder sb = new StringBuilder();
+               for (String line : lines) {
+                       sb.append(line);
+                       sb.append('\n');
+               }
+               writeTrashFile(fileName, sb.toString());
+               git.add().addFilepattern(fileName).call();
+               return git.commit().setMessage(commitMessage).call();
+       }
 
+       private void writeFileAndAdd(String fileName, String... lines)
+                       throws Exception {
+               StringBuilder sb = new StringBuilder();
+               for (String line : lines) {
+                       sb.append(line);
+                       sb.append('\n');
+               }
+               writeTrashFile(fileName, sb.toString());
+               git.add().addFilepattern(fileName).call();
+       }
+
+       private void checkFile(String fileName, String... lines) throws Exception {
+               File file = new File(db.getWorkTree(), fileName);
+               StringBuilder sb = new StringBuilder();
+               for (String line : lines) {
+                       sb.append(line);
+                       sb.append('\n');
+               }
+               checkFile(file, sb.toString());
+       }
+
+       public void testStopOnConflictFileCreationAndDeletion() throws Exception {
                // create file1 on master
-               writeTrashFile("file1", "Hello World");
-               git.add().addFilepattern("file1").call();
+               writeTrashFile(FILE1, "Hello World");
+               git.add().addFilepattern(FILE1).call();
                // create file2 on master
                File file2 = writeTrashFile("file2", "Hello World 2");
                git.add().addFilepattern("file2").call();
@@ -378,10 +685,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
                writeTrashFile("folder6/file1", "Hello World folder6");
                git.add().addFilepattern("folder6/file1").call();
 
-               git.commit()
-                               .setMessage(
-                                               "Add file 4 and folder folder6, delete file2 on master")
-                               .call();
+               git.commit().setMessage(
+                               "Add file 4 and folder folder6, delete file2 on master").call();
 
                // create a topic branch based on second commit
                createBranch(firstInMaster, "refs/heads/topic");
@@ -403,9 +708,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
 
                deleteTrashFile("file5");
                git.add().setUpdate(true).addFilepattern("file5").call();
-               RevCommit conflicting = git.commit()
-                               .setMessage("Delete file5, add file folder6 and file7 in topic")
-                               .call();
+               RevCommit conflicting = git.commit().setMessage(
+                               "Delete file5, add file folder6 and file7 in topic").call();
 
                RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
                assertEquals(Status.STOPPED, res.getStatus());
@@ -429,8 +733,7 @@ public class RebaseCommandTest extends RepositoryTestCase {
                assertEquals(res.getStatus(), Status.ABORTED);
                assertEquals("refs/heads/topic", db.getFullBranch());
                RevWalk rw = new RevWalk(db);
-               assertEquals(conflicting,
-                               rw.parseCommit(db.resolve(Constants.HEAD)));
+               assertEquals(conflicting, rw.parseCommit(db.resolve(Constants.HEAD)));
                assertEquals(RepositoryState.SAFE, db.getRepositoryState());
 
                // rebase- dir in .git must be deleted
@@ -444,6 +747,62 @@ public class RebaseCommandTest extends RepositoryTestCase {
 
        }
 
+       public void testAuthorScriptConverter() throws Exception {
+               // -1 h timezone offset
+               PersonIdent ident = new PersonIdent("Author name", "a.mail@some.com",
+                               123456789123L, -60);
+               String convertedAuthor = git.rebase().toAuthorScript(ident);
+               String[] lines = convertedAuthor.split("\n");
+               assertEquals("GIT_AUTHOR_NAME='Author name'", lines[0]);
+               assertEquals("GIT_AUTHOR_EMAIL='a.mail@some.com'", lines[1]);
+               assertEquals("GIT_AUTHOR_DATE='123456789 -0100'", lines[2]);
+
+               PersonIdent parsedIdent = git.rebase().parseAuthor(
+                               convertedAuthor.getBytes("UTF-8"));
+               assertEquals(ident.getName(), parsedIdent.getName());
+               assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
+               // this is rounded to the last second
+               assertEquals(123456789000L, parsedIdent.getWhen().getTime());
+               assertEquals(ident.getTimeZoneOffset(), parsedIdent.getTimeZoneOffset());
+
+               // + 9.5h timezone offset
+               ident = new PersonIdent("Author name", "a.mail@some.com",
+                               123456789123L, +570);
+               convertedAuthor = git.rebase().toAuthorScript(ident);
+               lines = convertedAuthor.split("\n");
+               assertEquals("GIT_AUTHOR_NAME='Author name'", lines[0]);
+               assertEquals("GIT_AUTHOR_EMAIL='a.mail@some.com'", lines[1]);
+               assertEquals("GIT_AUTHOR_DATE='123456789 +0930'", lines[2]);
+
+               parsedIdent = git.rebase().parseAuthor(
+                               convertedAuthor.getBytes("UTF-8"));
+               assertEquals(ident.getName(), parsedIdent.getName());
+               assertEquals(ident.getEmailAddress(), parsedIdent.getEmailAddress());
+               assertEquals(123456789000L, parsedIdent.getWhen().getTime());
+               assertEquals(ident.getTimeZoneOffset(), parsedIdent.getTimeZoneOffset());
+       }
+
+       public void testRepositoryStateChecks() throws Exception {
+               try {
+                       git.rebase().setOperation(Operation.ABORT).call();
+                       fail("Expected Exception not thrown");
+               } catch (WrongRepositoryStateException e) {
+                       // expected
+               }
+               try {
+                       git.rebase().setOperation(Operation.SKIP).call();
+                       fail("Expected Exception not thrown");
+               } catch (WrongRepositoryStateException e) {
+                       // expected
+               }
+               try {
+                       git.rebase().setOperation(Operation.CONTINUE).call();
+                       fail("Expected Exception not thrown");
+               } catch (WrongRepositoryStateException e) {
+                       // expected
+               }
+       }
+
        private int countPicks() throws IOException {
                int count = 0;
                File todoFile = new File(db.getDirectory(),
index 7c5e98119937c6b287dc82375525a11422c65262..07f0cb1d7de9f2ad2a7291862fdd6347df7786b9 100644 (file)
@@ -69,6 +69,7 @@ cannotReadFile=Cannot read file {0}
 cannotReadHEAD=cannot read HEAD: {0} {1}
 cannotReadObject=Cannot read object
 cannotReadTree=Cannot read tree {0}
+cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD
 cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating.
 cannotStoreObjects=cannot store objects
 cannotUnloadAModifiedTree=Cannot unload a modified tree.
@@ -426,6 +427,7 @@ unknownRepositoryFormat=Unknown repository format
 unknownZlibError=Unknown zlib error.
 unpackException=Exception while parsing pack stream
 unmergedPath=Unmerged path: {0}
+unmergedPaths=Repository contains unmerged paths
 unreadablePackIndex=Unreadable pack index: {0}
 unrecognizedRef=Unrecognized ref: {0}
 unsupportedCommand0=unsupported command 0
index e34d0a58b0a196cfc771c1737b1697fffbcd8d89..87053a8c95f85463f899de7e0fad56cfbece2738 100644 (file)
@@ -129,6 +129,7 @@ public class JGitText extends TranslationBundle {
        /***/ public String cannotReadHEAD;
        /***/ public String cannotReadObject;
        /***/ public String cannotReadTree;
+       /***/ public String cannotRebaseWithoutCurrentHead;
        /***/ public String cannotResolveLocalTrackingRefForUpdating;
        /***/ public String cannotStoreObjects;
        /***/ public String cannotUnloadAModifiedTree;
@@ -485,6 +486,7 @@ public class JGitText extends TranslationBundle {
        /***/ public String unknownRepositoryFormat;
        /***/ public String unknownZlibError;
        /***/ public String unmergedPath;
+       /***/ public String unmergedPaths;      
        /***/ public String unpackException;
        /***/ public String unreadablePackIndex;
        /***/ public String unrecognizedRef;
index eaa96f578a85110c967048f4407e1182931d6ad9..6629c4f8ee924df560d59d999acf1e6199d3ad98 100644 (file)
@@ -47,6 +47,7 @@ import java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -55,7 +56,9 @@ import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.eclipse.jgit.JGitText;
 import org.eclipse.jgit.api.RebaseResult.Status;
@@ -63,14 +66,18 @@ import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.api.errors.NoHeadException;
 import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.api.errors.UnmergedPathsException;
 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
 import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -78,6 +85,8 @@ import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
 import org.eclipse.jgit.util.FileUtils;
 import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -94,6 +103,40 @@ import org.eclipse.jgit.util.RawParseUtils;
  *      >Git documentation about Rebase</a>
  */
 public class RebaseCommand extends GitCommand<RebaseResult> {
+       /**
+        * The name of the "rebase-merge" folder
+        */
+       public static final String REBASE_MERGE = "rebase-merge";
+
+       /**
+        * The name of the "stopped-sha" file
+        */
+       public static final String STOPPED_SHA = "stopped-sha";
+
+       private static final String AUTHOR_SCRIPT = "author-script";
+
+       private static final String DONE = "done";
+
+       private static final String GIT_AUTHOR_DATE = "GIT_AUTHOR_DATE";
+
+       private static final String GIT_AUTHOR_EMAIL = "GIT_AUTHOR_EMAIL";
+
+       private static final String GIT_AUTHOR_NAME = "GIT_AUTHOR_NAME";
+
+       private static final String GIT_REBASE_TODO = "git-rebase-todo";
+
+       private static final String HEAD_NAME = "head-name";
+
+       private static final String INTERACTIVE = "interactive";
+
+       private static final String MESSAGE = "message";
+
+       private static final String ONTO = "onto";
+
+       private static final String PATCH = "patch";
+
+       private static final String REBASE_HEAD = "head";
+
        /**
         * The available operations
         */
@@ -132,7 +175,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
        protected RebaseCommand(Repository repo) {
                super(repo);
                walk = new RevWalk(repo);
-               rebaseDir = new File(repo.getDirectory(), "rebase-merge");
+               rebaseDir = new File(repo.getDirectory(), REBASE_MERGE);
        }
 
        /**
@@ -145,6 +188,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
         */
        public RebaseResult call() throws NoHeadException, RefNotFoundException,
                        JGitInternalException, GitAPIException {
+               RevCommit newHead = null;
                checkCallable();
                checkParameters();
                try {
@@ -158,7 +202,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                        case SKIP:
                                // fall through
                        case CONTINUE:
-                               String upstreamCommitName = readFile(rebaseDir, "onto");
+                               String upstreamCommitName = readFile(rebaseDir, ONTO);
                                this.upstreamCommit = walk.parseCommit(repo
                                                .resolve(upstreamCommitName));
                                break;
@@ -172,16 +216,13 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                                return abort();
 
                        if (this.operation == Operation.CONTINUE)
-                               throw new UnsupportedOperationException(
-                                               "--continue Not yet implemented");
+                               newHead = continueRebase();
 
-                       if (this.operation == Operation.SKIP)
-                               throw new UnsupportedOperationException(
-                                               "--skip Not yet implemented");
+                       List<Step> steps = loadSteps();
 
-                       RevCommit newHead = null;
+                       if (this.operation == Operation.SKIP && !steps.isEmpty())
+                               checkoutCurrentHead();
 
-                       List<Step> steps = loadSteps();
                        ObjectReader or = repo.newObjectReader();
                        int stepsToPop = 0;
 
@@ -211,16 +252,21 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                                }
                                stepsToPop++;
                        }
-                       if (newHead != null) {
+                       if (newHead != null || steps.isEmpty()) {
                                // point the previous head (if any) to the new commit
-                               String headName = readFile(rebaseDir, "head-name");
+                               String headName = readFile(rebaseDir, HEAD_NAME);
                                if (headName.startsWith(Constants.R_REFS)) {
                                        RefUpdate rup = repo.updateRef(headName);
-                                       rup.setNewObjectId(newHead);
-                                       rup.forceUpdate();
+                                       if (newHead != null) {
+                                               rup.setNewObjectId(newHead);
+                                               rup.forceUpdate();
+                                       }
                                        rup = repo.updateRef(Constants.HEAD);
                                        rup.link(headName);
                                }
+                               if (this.operation == Operation.SKIP && steps.isEmpty()) {
+                                       checkoutCurrentHead();
+                               }
                                FileUtils.delete(rebaseDir, FileUtils.RECURSIVE);
                                return new RebaseResult(Status.OK);
                        }
@@ -230,29 +276,118 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                }
        }
 
+       private void checkoutCurrentHead() throws IOException, NoHeadException,
+                       JGitInternalException {
+               ObjectId headTree = repo.resolve(Constants.HEAD + "^{tree}");
+               if (headTree == null)
+                       throw new NoHeadException(
+                                       JGitText.get().cannotRebaseWithoutCurrentHead);
+               DirCache dc = repo.lockDirCache();
+               try {
+                       DirCacheCheckout dco = new DirCacheCheckout(repo, dc, headTree);
+                       dco.setFailOnConflict(false);
+                       boolean needsDeleteFiles = dco.checkout();
+                       if (needsDeleteFiles) {
+                               List<String> fileList = dco.getToBeDeleted();
+                               for (String filePath : fileList) {
+                                       File fileToDelete = new File(repo.getWorkTree(), filePath);
+                                       if (fileToDelete.exists())
+                                               FileUtils.delete(fileToDelete, FileUtils.RECURSIVE
+                                                               | FileUtils.RETRY);
+                               }
+                       }
+               } finally {
+                       dc.unlock();
+               }
+       }
+
+       /**
+        * @return the commit if we had to do a commit, otherwise null
+        * @throws GitAPIException
+        * @throws IOException
+        */
+       private RevCommit continueRebase() throws GitAPIException, IOException {
+               // if there are still conflicts, we throw a specific Exception
+               DirCache dc = repo.readDirCache();
+               boolean hasUnmergedPaths = dc.hasUnmergedPaths();
+               if (hasUnmergedPaths)
+                       throw new UnmergedPathsException();
+
+               // determine whether we need to commit
+               TreeWalk treeWalk = new TreeWalk(repo);
+               treeWalk.reset();
+               treeWalk.setRecursive(true);
+               treeWalk.addTree(new DirCacheIterator(dc));
+               ObjectId id = repo.resolve(Constants.HEAD + "^{tree}");
+               if (id == null)
+                       throw new NoHeadException(
+                                       JGitText.get().cannotRebaseWithoutCurrentHead);
+
+               treeWalk.addTree(id);
+
+               treeWalk.setFilter(TreeFilter.ANY_DIFF);
+
+               boolean needsCommit = treeWalk.next();
+               treeWalk.release();
+
+               if (needsCommit) {
+                       CommitCommand commit = new Git(repo).commit();
+                       commit.setMessage(readFile(rebaseDir, MESSAGE));
+                       commit.setAuthor(parseAuthor());
+                       return commit.call();
+               }
+               return null;
+       }
+
+       private PersonIdent parseAuthor() throws IOException {
+               File authorScriptFile = new File(rebaseDir, AUTHOR_SCRIPT);
+               byte[] raw;
+               try {
+                       raw = IO.readFully(authorScriptFile);
+               } catch (FileNotFoundException notFound) {
+                       return null;
+               }
+               return parseAuthor(raw);
+       }
+
        private RebaseResult stop(RevCommit commitToPick) throws IOException {
-               StringBuilder sb = new StringBuilder(100);
-               sb.append("GIT_AUTHOR_NAME='");
-               sb.append(commitToPick.getAuthorIdent().getName());
-               sb.append("'\n");
-               sb.append("GIT_AUTHOR_EMAIL='");
-               sb.append(commitToPick.getAuthorIdent().getEmailAddress());
-               sb.append("'\n");
-               sb.append("GIT_AUTHOR_DATE='");
-               sb.append(commitToPick.getAuthorIdent().getWhen());
-               sb.append("'\n");
-               createFile(rebaseDir, "author-script", sb.toString());
-               createFile(rebaseDir, "message", commitToPick.getShortMessage());
+               PersonIdent author = commitToPick.getAuthorIdent();
+               String authorScript = toAuthorScript(author);
+               createFile(rebaseDir, AUTHOR_SCRIPT, authorScript);
+               createFile(rebaseDir, MESSAGE, commitToPick.getFullMessage());
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                DiffFormatter df = new DiffFormatter(bos);
                df.setRepository(repo);
                df.format(commitToPick.getParent(0), commitToPick);
-               createFile(rebaseDir, "patch", new String(bos.toByteArray(), "UTF-8"));
-               createFile(rebaseDir, "stopped-sha", repo.newObjectReader().abbreviate(
+               createFile(rebaseDir, PATCH, new String(bos.toByteArray(),
+                               Constants.CHARACTER_ENCODING));
+               createFile(rebaseDir, STOPPED_SHA, repo.newObjectReader().abbreviate(
                                commitToPick).name());
                return new RebaseResult(commitToPick);
        }
 
+       String toAuthorScript(PersonIdent author) {
+               StringBuilder sb = new StringBuilder(100);
+               sb.append(GIT_AUTHOR_NAME);
+               sb.append("='");
+               sb.append(author.getName());
+               sb.append("'\n");
+               sb.append(GIT_AUTHOR_EMAIL);
+               sb.append("='");
+               sb.append(author.getEmailAddress());
+               sb.append("'\n");
+               // the command line uses the "external String"
+               // representation for date and timezone
+               sb.append(GIT_AUTHOR_DATE);
+               sb.append("='");
+               String externalString = author.toExternalString();
+               sb
+                               .append(externalString.substring(externalString
+                                               .lastIndexOf('>') + 2));
+               sb.append("'\n");
+               return sb.toString();
+       }
+
        /**
         * Removes the number of lines given in the parameter from the
         * <code>git-rebase-todo</code> file but preserves comments and other lines
@@ -266,10 +401,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                        return;
                List<String> todoLines = new ArrayList<String>();
                List<String> poppedLines = new ArrayList<String>();
-               File todoFile = new File(rebaseDir, "git-rebase-todo");
-               File doneFile = new File(rebaseDir, "done");
+               File todoFile = new File(rebaseDir, GIT_REBASE_TODO);
+               File doneFile = new File(rebaseDir, DONE);
                BufferedReader br = new BufferedReader(new InputStreamReader(
-                               new FileInputStream(todoFile), "UTF-8"));
+                               new FileInputStream(todoFile), Constants.CHARACTER_ENCODING));
                try {
                        // check if the line starts with a action tag (pick, skip...)
                        while (poppedLines.size() < numSteps) {
@@ -297,7 +432,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                }
 
                BufferedWriter todoWriter = new BufferedWriter(new OutputStreamWriter(
-                               new FileOutputStream(todoFile), "UTF-8"));
+                               new FileOutputStream(todoFile), Constants.CHARACTER_ENCODING));
                try {
                        for (String writeLine : todoLines) {
                                todoWriter.write(writeLine);
@@ -311,7 +446,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                        // append here
                        BufferedWriter doneWriter = new BufferedWriter(
                                        new OutputStreamWriter(
-                                                       new FileOutputStream(doneFile, true), "UTF-8"));
+                                                       new FileOutputStream(doneFile, true),
+                                                       Constants.CHARACTER_ENCODING));
                        try {
                                for (String writeLine : poppedLines) {
                                        doneWriter.write(writeLine);
@@ -363,14 +499,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                // create the folder for the meta information
                rebaseDir.mkdir();
 
-               createFile(repo.getDirectory(), "ORIG_HEAD", headId.name());
-               createFile(rebaseDir, "head", headId.name());
-               createFile(rebaseDir, "head-name", headName);
-               createFile(rebaseDir, "onto", upstreamCommit.name());
-               createFile(rebaseDir, "interactive", "");
+               createFile(repo.getDirectory(), Constants.ORIG_HEAD, headId.name());
+               createFile(rebaseDir, REBASE_HEAD, headId.name());
+               createFile(rebaseDir, HEAD_NAME, headName);
+               createFile(rebaseDir, ONTO, upstreamCommit.name());
+               createFile(rebaseDir, INTERACTIVE, "");
                BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(
-                               new FileOutputStream(new File(rebaseDir, "git-rebase-todo")),
-                               "UTF-8"));
+                               new FileOutputStream(new File(rebaseDir, GIT_REBASE_TODO)),
+                               Constants.CHARACTER_ENCODING));
                fw.write("# Created by EGit: rebasing " + upstreamCommit.name()
                                + " onto " + headId.name());
                fw.newLine();
@@ -404,13 +540,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                if (this.operation != Operation.BEGIN) {
                        // these operations are only possible while in a rebasing state
                        switch (repo.getRepositoryState()) {
-                       case REBASING:
-                               // fall through
                        case REBASING_INTERACTIVE:
-                               // fall through
-                       case REBASING_MERGE:
-                               // fall through
-                       case REBASING_REBASING:
                                break;
                        default:
                                throw new WrongRepositoryStateException(MessageFormat.format(
@@ -438,7 +568,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                File file = new File(parentDir, name);
                FileOutputStream fos = new FileOutputStream(file);
                try {
-                       fos.write(content.getBytes("UTF-8"));
+                       fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
                        fos.write('\n');
                } finally {
                        fos.close();
@@ -447,7 +577,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
 
        private RebaseResult abort() throws IOException {
                try {
-                       String commitId = readFile(repo.getDirectory(), "ORIG_HEAD");
+                       String commitId = readFile(repo.getDirectory(), Constants.ORIG_HEAD);
                        monitor.beginTask(MessageFormat.format(
                                        JGitText.get().abortingRebase, commitId),
                                        ProgressMonitor.UNKNOWN);
@@ -463,7 +593,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                        monitor.endTask();
                }
                try {
-                       String headName = readFile(rebaseDir, "head-name");
+                       String headName = readFile(rebaseDir, HEAD_NAME);
                        if (headName.startsWith(Constants.R_REFS)) {
                                monitor.beginTask(MessageFormat.format(
                                                JGitText.get().resettingHead, headName),
@@ -527,7 +657,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
        }
 
        private List<Step> loadSteps() throws IOException {
-               byte[] buf = IO.readFully(new File(rebaseDir, "git-rebase-todo"));
+               byte[] buf = IO.readFully(new File(rebaseDir, GIT_REBASE_TODO));
                int ptr = 0;
                int tokenBegin = 0;
                ArrayList<Step> r = new ArrayList<Step>();
@@ -656,4 +786,42 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
                        this.action = action;
                }
        }
+
+       PersonIdent parseAuthor(byte[] raw) {
+               if (raw.length == 0)
+                       return null;
+
+               Map<String, String> keyValueMap = new HashMap<String, String>();
+               for (int p = 0; p < raw.length;) {
+                       int end = RawParseUtils.nextLF(raw, p);
+                       if (end == p)
+                               break;
+                       int equalsIndex = RawParseUtils.next(raw, p, '=');
+                       if (equalsIndex == end)
+                               break;
+                       String key = RawParseUtils.decode(raw, p, equalsIndex - 1);
+                       String value = RawParseUtils.decode(raw, equalsIndex + 1, end - 2);
+                       p = end;
+                       keyValueMap.put(key, value);
+               }
+
+               String name = keyValueMap.get(GIT_AUTHOR_NAME);
+               String email = keyValueMap.get(GIT_AUTHOR_EMAIL);
+               String time = keyValueMap.get(GIT_AUTHOR_DATE);
+
+               // the time is saved as <seconds since 1970> <timezone offset>
+               long when = Long.parseLong(time.substring(0, time.indexOf(' '))) * 1000;
+               String tzOffsetString = time.substring(time.indexOf(' ') + 1);
+               int multiplier = -1;
+               if (tzOffsetString.charAt(0) == '+')
+                       multiplier = 1;
+               int hours = Integer.parseInt(tzOffsetString.substring(1, 3));
+               int minutes = Integer.parseInt(tzOffsetString.substring(3, 5));
+               // this is in format (+/-)HHMM (hours and minutes)
+               // we need to convert into minutes
+               int tz = (hours * 60 + minutes) * multiplier;
+               if (name != null && email != null)
+                       return new PersonIdent(name, email, when, tz);
+               return null;
+       }
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java
new file mode 100644 (file)
index 0000000..ab07866
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010,Mathias Kinzler <mathias.kinzler@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.errors;
+
+import org.eclipse.jgit.JGitText;
+
+/**
+ * Thrown when branch deletion fails due to unmerged data
+ */
+public class UnmergedPathsException extends GitAPIException {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * The default constructor with a default message
+        */
+       public UnmergedPathsException() {
+               super(JGitText.get().unmergedPaths);
+       }
+}