summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathias Kinzler <mathias.kinzler@sap.com>2010-12-09 16:10:21 +0100
committerMathias Kinzler <mathias.kinzler@sap.com>2010-12-09 16:10:21 +0100
commit6bca46e1683a07f18f00f6ad552eab79ab50bb88 (patch)
tree69482c520472a14d8720e80c40f1e938c555f2e5
parentc0b49c1366ae429eedc724d5b34c6d41ae249fcf (diff)
downloadjgit-6bca46e1683a07f18f00f6ad552eab79ab50bb88.tar.gz
jgit-6bca46e1683a07f18f00f6ad552eab79ab50bb88.zip
Implement rebase --continue and --skip
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>
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java547
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java266
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java54
5 files changed, 728 insertions, 143 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
index bddb9ed6e9..35b05e7195 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
@@ -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(),
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
index 7c5e981199..07f0cb1d7d 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
@@ -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
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
index e34d0a58b0..87053a8c95 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
@@ -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;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index eaa96f578a..6629c4f8ee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -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;
@@ -95,6 +104,40 @@ import org.eclipse.jgit.util.RawParseUtils;
*/
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
*/
public enum Operation {
@@ -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
index 0000000000..ab078663b2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java
@@ -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);
+ }
+}