Browse Source

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>
stable-0.10
Mathias Kinzler 14 years ago
parent
commit
6bca46e168
  1. 547
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java
  2. 2
      org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties
  3. 2
      org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java
  4. 266
      org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
  5. 54
      org.eclipse.jgit/src/org/eclipse/jgit/api/errors/UnmergedPathsException.java

547
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.RebaseCommand.Operation;
import org.eclipse.jgit.api.RebaseResult.Status; import org.eclipse.jgit.api.RebaseResult.Status;
import org.eclipse.jgit.api.errors.RefNotFoundException; 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.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.lib.RepositoryTestCase;
@ -62,6 +64,16 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
public class RebaseCommandTest extends RepositoryTestCase { 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) private void createBranch(ObjectId objectId, String branchName)
throws IOException { throws IOException {
RefUpdate updateRef = db.updateRef(branchName); RefUpdate updateRef = db.updateRef(branchName);
@ -88,8 +100,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
IOException { IOException {
RevWalk walk = new RevWalk(db); RevWalk walk = new RevWalk(db);
RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD)); RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree(), DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree(), db
db.lockDirCache(), commit.getTree()); .lockDirCache(), commit.getTree());
dco.setFailOnConflict(true); dco.setFailOnConflict(true);
dco.checkout(); dco.checkout();
walk.release(); walk.release();
@ -100,14 +112,12 @@ public class RebaseCommandTest extends RepositoryTestCase {
} }
public void testFastForwardWithNewFile() throws Exception { public void testFastForwardWithNewFile() throws Exception {
Git git = new Git(db);
// create file1 on master // create file1 on master
writeTrashFile("file1", "file1"); writeTrashFile(FILE1, FILE1);
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit first = git.commit().setMessage("Add 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 // create a topic branch
createBranch(first, "refs/heads/topic"); createBranch(first, "refs/heads/topic");
// create file2 on master // create file2 on master
@ -124,32 +134,27 @@ public class RebaseCommandTest extends RepositoryTestCase {
} }
public void testUpToDate() throws Exception { public void testUpToDate() throws Exception {
Git git = new Git(db);
// create file1 on master // create file1 on master
writeTrashFile("file1", "file1"); writeTrashFile(FILE1, FILE1);
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit first = git.commit().setMessage("Add 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(); RebaseResult res = git.rebase().setUpstream(first).call();
assertEquals(Status.UP_TO_DATE, res.getStatus()); assertEquals(Status.UP_TO_DATE, res.getStatus());
} }
public void testUnknownUpstream() throws Exception { public void testUnknownUpstream() throws Exception {
Git git = new Git(db);
// create file1 on master // create file1 on master
writeTrashFile("file1", "file1"); writeTrashFile(FILE1, FILE1);
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
git.commit().setMessage("Add file1").call(); git.commit().setMessage("Add file1").call();
assertTrue(new File(db.getWorkTree(), "file1").exists()); assertTrue(new File(db.getWorkTree(), FILE1).exists());
try { try {
git.rebase().setUpstream("refs/heads/xyz") git.rebase().setUpstream("refs/heads/xyz").call();
.call();
fail("expected exception was not thrown"); fail("expected exception was not thrown");
} catch (RefNotFoundException e) { } catch (RefNotFoundException e) {
// expected exception // expected exception
@ -157,17 +162,15 @@ public class RebaseCommandTest extends RepositoryTestCase {
} }
public void testConflictFreeWithSingleFile() throws Exception { public void testConflictFreeWithSingleFile() throws Exception {
Git git = new Git(db);
// create file1 on master // create file1 on master
File theFile = writeTrashFile("file1", "1\n2\n3\n"); File theFile = writeTrashFile(FILE1, "1\n2\n3\n");
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit second = git.commit().setMessage("Add 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 // 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"); checkFile(theFile, "1master\n2\n3\n");
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit lastMasterChange = git.commit().setMessage( RevCommit lastMasterChange = git.commit().setMessage(
"change file1 in master").call(); "change file1 in master").call();
@ -177,10 +180,10 @@ public class RebaseCommandTest extends RepositoryTestCase {
// we have the old content again // we have the old content again
checkFile(theFile, "1\n2\n3\n"); 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 // change third line in topic branch
writeTrashFile("file1", "1\n2\n3\ntopic\n"); writeTrashFile(FILE1, "1\n2\n3\ntopic\n");
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
git.commit().setMessage("change file1 in topic").call(); git.commit().setMessage("change file1 in topic").call();
RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
@ -193,19 +196,17 @@ public class RebaseCommandTest extends RepositoryTestCase {
} }
public void testDetachedHead() throws Exception { public void testDetachedHead() throws Exception {
Git git = new Git(db);
// create file1 on master // create file1 on master
File theFile = writeTrashFile("file1", "1\n2\n3\n"); File theFile = writeTrashFile(FILE1, "1\n2\n3\n");
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit second = git.commit().setMessage("Add 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 // 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"); checkFile(theFile, "1master\n2\n3\n");
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit lastMasterChange = git.commit() RevCommit lastMasterChange = git.commit().setMessage(
.setMessage("change file1 in master").call(); "change file1 in master").call();
// create a topic branch based on second commit // create a topic branch based on second commit
createBranch(second, "refs/heads/topic"); createBranch(second, "refs/heads/topic");
@ -213,10 +214,10 @@ public class RebaseCommandTest extends RepositoryTestCase {
// we have the old content again // we have the old content again
checkFile(theFile, "1\n2\n3\n"); 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 // change third line in topic branch
writeTrashFile("file1", "1\n2\n3\ntopic\n"); writeTrashFile(FILE1, "1\n2\n3\ntopic\n");
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit topicCommit = git.commit() RevCommit topicCommit = git.commit()
.setMessage("change file1 in topic").call(); .setMessage("change file1 in topic").call();
checkoutBranch("refs/heads/master"); checkoutBranch("refs/heads/master");
@ -226,18 +227,15 @@ public class RebaseCommandTest extends RepositoryTestCase {
RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
assertEquals(Status.OK, res.getStatus()); assertEquals(Status.OK, res.getStatus());
checkFile(theFile, "1master\n2\n3\ntopic\n"); checkFile(theFile, "1master\n2\n3\ntopic\n");
assertEquals(lastMasterChange, assertEquals(lastMasterChange, new RevWalk(db).parseCommit(
new RevWalk(db).parseCommit(db.resolve(Constants.HEAD)) db.resolve(Constants.HEAD)).getParent(0));
.getParent(0));
} }
public void testFilesAddedFromTwoBranches() throws Exception { public void testFilesAddedFromTwoBranches() throws Exception {
Git git = new Git(db);
// create file1 on master // create file1 on master
writeTrashFile("file1", "file1"); writeTrashFile(FILE1, FILE1);
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
RevCommit masterCommit = git.commit().setMessage("Add file1 to master") RevCommit masterCommit = git.commit().setMessage("Add file1 to master")
.call(); .call();
@ -256,14 +254,14 @@ public class RebaseCommandTest extends RepositoryTestCase {
git.add().addFilepattern("file3").call(); git.add().addFilepattern("file3").call();
git.commit().setMessage("Add file3 to branch 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()); assertFalse(new File(db.getWorkTree(), "file2").exists());
assertTrue(new File(db.getWorkTree(), "file3").exists()); assertTrue(new File(db.getWorkTree(), "file3").exists());
RebaseResult res = git.rebase().setUpstream("refs/heads/file2").call(); RebaseResult res = git.rebase().setUpstream("refs/heads/file2").call();
assertEquals(Status.OK, res.getStatus()); 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(), "file2").exists());
assertTrue(new File(db.getWorkTree(), "file3").exists()); assertTrue(new File(db.getWorkTree(), "file3").exists());
@ -273,54 +271,40 @@ public class RebaseCommandTest extends RepositoryTestCase {
db.resolve(Constants.HEAD)).getParent(0)); db.resolve(Constants.HEAD)).getParent(0));
checkoutBranch("refs/heads/file2"); 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()); assertTrue(new File(db.getWorkTree(), "file2").exists());
assertFalse(new File(db.getWorkTree(), "file3").exists()); assertFalse(new File(db.getWorkTree(), "file3").exists());
} }
public void testAbortOnConflict() throws Exception { public void testStopOnConflict() throws Exception {
Git git = new Git(db);
// create file1 on master // create file1 on master
File theFile = writeTrashFile("file1", "1\n2\n3\n"); RevCommit firstInMaster = writeFileAndCommit(FILE1, "Add file1", "1",
git.add().addFilepattern("file1").call(); "2", "3");
RevCommit firstInMaster = git.commit().setMessage("Add file1").call(); // change first line in master
assertTrue(new File(db.getWorkTree(), "file1").exists()); writeFileAndCommit(FILE1, "change file1 in master", "1master", "2", "3");
// change first line in master and commit checkFile(FILE1, "1master", "2", "3");
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();
// create a topic branch based on second commit // create a topic branch based on second commit
createBranch(firstInMaster, "refs/heads/topic"); createBranch(firstInMaster, "refs/heads/topic");
checkoutBranch("refs/heads/topic"); checkoutBranch("refs/heads/topic");
// we have the old content again // 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) // add a line (non-conflicting)
writeTrashFile("file1", "1\n2\n3\ntopic4\n"); writeFileAndCommit(FILE1, "add a line to file1 in topic", "1", "2",
git.add().addFilepattern("file1").call(); "3", "topic4");
git.commit().setMessage("add a line to file1 in topic").call();
// change first line (conflicting) // change first line (conflicting)
writeTrashFile("file1", "1topic\n2\n3\ntopic4\n"); RevCommit conflicting = writeFileAndCommit(FILE1,
git.add().addFilepattern("file1").call(); "change file1 in topic", "1topic", "2", "3", "topic4");
RevCommit conflicting = git.commit()
.setMessage("change file1 in topic").call();
// change second line (not conflicting) RevCommit lastTopicCommit = writeFileAndCommit(FILE1,
writeTrashFile("file1", "1topic\n2topic\n3\ntopic4\n"); "change file1 in topic again", "1topic", "2", "3", "topic4");
git.add().addFilepattern("file1").call();
RevCommit lastTopicCommit = git.commit().setMessage(
"change file1 in topic again").call();
RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
assertEquals(Status.STOPPED, res.getStatus()); assertEquals(Status.STOPPED, res.getStatus());
assertEquals(conflicting, res.getCurrentCommit()); assertEquals(conflicting, res.getCurrentCommit());
checkFile(theFile, checkFile(FILE1,
"<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\ntopic4\n"); "<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\ntopic4");
assertEquals(RepositoryState.REBASING_INTERACTIVE, db assertEquals(RepositoryState.REBASING_INTERACTIVE, db
.getRepositoryState()); .getRepositoryState());
@ -341,7 +325,7 @@ public class RebaseCommandTest extends RepositoryTestCase {
res = git.rebase().setOperation(Operation.ABORT).call(); res = git.rebase().setOperation(Operation.ABORT).call();
assertEquals(res.getStatus(), Status.ABORTED); assertEquals(res.getStatus(), Status.ABORTED);
assertEquals("refs/heads/topic", db.getFullBranch()); assertEquals("refs/heads/topic", db.getFullBranch());
checkFile(theFile, "1topic\n2topic\n3\ntopic4\n"); checkFile(FILE1, "1topic", "2", "3", "topic4");
RevWalk rw = new RevWalk(db); RevWalk rw = new RevWalk(db);
assertEquals(lastTopicCommit, rw assertEquals(lastTopicCommit, rw
.parseCommit(db.resolve(Constants.HEAD))); .parseCommit(db.resolve(Constants.HEAD)));
@ -351,12 +335,335 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertFalse(new File(db.getDirectory(), "rebase-merge").exists()); assertFalse(new File(db.getDirectory(), "rebase-merge").exists());
} }
public void testAbortOnConflictFileCreationAndDeletion() throws Exception { public void testStopOnConflictAndContinue() throws Exception {
Git git = new Git(db); // 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 // create file1 on master
writeTrashFile("file1", "Hello World"); writeTrashFile(FILE1, "Hello World");
git.add().addFilepattern("file1").call(); git.add().addFilepattern(FILE1).call();
// create file2 on master // create file2 on master
File file2 = writeTrashFile("file2", "Hello World 2"); File file2 = writeTrashFile("file2", "Hello World 2");
git.add().addFilepattern("file2").call(); git.add().addFilepattern("file2").call();
@ -378,10 +685,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
writeTrashFile("folder6/file1", "Hello World folder6"); writeTrashFile("folder6/file1", "Hello World folder6");
git.add().addFilepattern("folder6/file1").call(); git.add().addFilepattern("folder6/file1").call();
git.commit() git.commit().setMessage(
.setMessage( "Add file 4 and folder folder6, delete file2 on master").call();
"Add file 4 and folder folder6, delete file2 on master")
.call();
// create a topic branch based on second commit // create a topic branch based on second commit
createBranch(firstInMaster, "refs/heads/topic"); createBranch(firstInMaster, "refs/heads/topic");
@ -403,9 +708,8 @@ public class RebaseCommandTest extends RepositoryTestCase {
deleteTrashFile("file5"); deleteTrashFile("file5");
git.add().setUpdate(true).addFilepattern("file5").call(); git.add().setUpdate(true).addFilepattern("file5").call();
RevCommit conflicting = git.commit() RevCommit conflicting = git.commit().setMessage(
.setMessage("Delete file5, add file folder6 and file7 in topic") "Delete file5, add file folder6 and file7 in topic").call();
.call();
RebaseResult res = git.rebase().setUpstream("refs/heads/master").call(); RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
assertEquals(Status.STOPPED, res.getStatus()); assertEquals(Status.STOPPED, res.getStatus());
@ -429,8 +733,7 @@ public class RebaseCommandTest extends RepositoryTestCase {
assertEquals(res.getStatus(), Status.ABORTED); assertEquals(res.getStatus(), Status.ABORTED);
assertEquals("refs/heads/topic", db.getFullBranch()); assertEquals("refs/heads/topic", db.getFullBranch());
RevWalk rw = new RevWalk(db); RevWalk rw = new RevWalk(db);
assertEquals(conflicting, assertEquals(conflicting, rw.parseCommit(db.resolve(Constants.HEAD)));
rw.parseCommit(db.resolve(Constants.HEAD)));
assertEquals(RepositoryState.SAFE, db.getRepositoryState()); assertEquals(RepositoryState.SAFE, db.getRepositoryState());
// rebase- dir in .git must be deleted // 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 { private int countPicks() throws IOException {
int count = 0; int count = 0;
File todoFile = new File(db.getDirectory(), File todoFile = new File(db.getDirectory(),

2
org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties

@ -69,6 +69,7 @@ cannotReadFile=Cannot read file {0}
cannotReadHEAD=cannot read HEAD: {0} {1} cannotReadHEAD=cannot read HEAD: {0} {1}
cannotReadObject=Cannot read object cannotReadObject=Cannot read object
cannotReadTree=Cannot read tree {0} cannotReadTree=Cannot read tree {0}
cannotRebaseWithoutCurrentHead=Can not rebase without a current HEAD
cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating. cannotResolveLocalTrackingRefForUpdating=Cannot resolve local tracking ref {0} for updating.
cannotStoreObjects=cannot store objects cannotStoreObjects=cannot store objects
cannotUnloadAModifiedTree=Cannot unload a modified tree. cannotUnloadAModifiedTree=Cannot unload a modified tree.
@ -426,6 +427,7 @@ unknownRepositoryFormat=Unknown repository format
unknownZlibError=Unknown zlib error. unknownZlibError=Unknown zlib error.
unpackException=Exception while parsing pack stream unpackException=Exception while parsing pack stream
unmergedPath=Unmerged path: {0} unmergedPath=Unmerged path: {0}
unmergedPaths=Repository contains unmerged paths
unreadablePackIndex=Unreadable pack index: {0} unreadablePackIndex=Unreadable pack index: {0}
unrecognizedRef=Unrecognized ref: {0} unrecognizedRef=Unrecognized ref: {0}
unsupportedCommand0=unsupported command 0 unsupportedCommand0=unsupported command 0

2
org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java

@ -129,6 +129,7 @@ public class JGitText extends TranslationBundle {
/***/ public String cannotReadHEAD; /***/ public String cannotReadHEAD;
/***/ public String cannotReadObject; /***/ public String cannotReadObject;
/***/ public String cannotReadTree; /***/ public String cannotReadTree;
/***/ public String cannotRebaseWithoutCurrentHead;
/***/ public String cannotResolveLocalTrackingRefForUpdating; /***/ public String cannotResolveLocalTrackingRefForUpdating;
/***/ public String cannotStoreObjects; /***/ public String cannotStoreObjects;
/***/ public String cannotUnloadAModifiedTree; /***/ public String cannotUnloadAModifiedTree;
@ -485,6 +486,7 @@ public class JGitText extends TranslationBundle {
/***/ public String unknownRepositoryFormat; /***/ public String unknownRepositoryFormat;
/***/ public String unknownZlibError; /***/ public String unknownZlibError;
/***/ public String unmergedPath; /***/ public String unmergedPath;
/***/ public String unmergedPaths;
/***/ public String unpackException; /***/ public String unpackException;
/***/ public String unreadablePackIndex; /***/ public String unreadablePackIndex;
/***/ public String unrecognizedRef; /***/ public String unrecognizedRef;

266
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.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -55,7 +56,9 @@ import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.eclipse.jgit.JGitText; import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.api.RebaseResult.Status; 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.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.RefNotFoundException; 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.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate; 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.lib.RefUpdate.Result;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; 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.FileUtils;
import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.RawParseUtils;
@ -94,6 +103,40 @@ import org.eclipse.jgit.util.RawParseUtils;
* >Git documentation about Rebase</a> * >Git documentation about Rebase</a>
*/ */
public class RebaseCommand extends GitCommand<RebaseResult> { 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 * The available operations
*/ */
@ -132,7 +175,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
protected RebaseCommand(Repository repo) { protected RebaseCommand(Repository repo) {
super(repo); super(repo);
walk = new RevWalk(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, public RebaseResult call() throws NoHeadException, RefNotFoundException,
JGitInternalException, GitAPIException { JGitInternalException, GitAPIException {
RevCommit newHead = null;
checkCallable(); checkCallable();
checkParameters(); checkParameters();
try { try {
@ -158,7 +202,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
case SKIP: case SKIP:
// fall through // fall through
case CONTINUE: case CONTINUE:
String upstreamCommitName = readFile(rebaseDir, "onto"); String upstreamCommitName = readFile(rebaseDir, ONTO);
this.upstreamCommit = walk.parseCommit(repo this.upstreamCommit = walk.parseCommit(repo
.resolve(upstreamCommitName)); .resolve(upstreamCommitName));
break; break;
@ -172,16 +216,13 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return abort(); return abort();
if (this.operation == Operation.CONTINUE) if (this.operation == Operation.CONTINUE)
throw new UnsupportedOperationException( newHead = continueRebase();
"--continue Not yet implemented");
if (this.operation == Operation.SKIP) List<Step> steps = loadSteps();
throw new UnsupportedOperationException(
"--skip Not yet implemented");
RevCommit newHead = null; if (this.operation == Operation.SKIP && !steps.isEmpty())
checkoutCurrentHead();
List<Step> steps = loadSteps();
ObjectReader or = repo.newObjectReader(); ObjectReader or = repo.newObjectReader();
int stepsToPop = 0; int stepsToPop = 0;
@ -211,16 +252,21 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} }
stepsToPop++; stepsToPop++;
} }
if (newHead != null) { if (newHead != null || steps.isEmpty()) {
// point the previous head (if any) to the new commit // 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)) { if (headName.startsWith(Constants.R_REFS)) {
RefUpdate rup = repo.updateRef(headName); RefUpdate rup = repo.updateRef(headName);
rup.setNewObjectId(newHead); if (newHead != null) {
rup.forceUpdate(); rup.setNewObjectId(newHead);
rup.forceUpdate();
}
rup = repo.updateRef(Constants.HEAD); rup = repo.updateRef(Constants.HEAD);
rup.link(headName); rup.link(headName);
} }
if (this.operation == Operation.SKIP && steps.isEmpty()) {
checkoutCurrentHead();
}
FileUtils.delete(rebaseDir, FileUtils.RECURSIVE); FileUtils.delete(rebaseDir, FileUtils.RECURSIVE);
return new RebaseResult(Status.OK); 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 { private RebaseResult stop(RevCommit commitToPick) throws IOException {
StringBuilder sb = new StringBuilder(100); PersonIdent author = commitToPick.getAuthorIdent();
sb.append("GIT_AUTHOR_NAME='"); String authorScript = toAuthorScript(author);
sb.append(commitToPick.getAuthorIdent().getName()); createFile(rebaseDir, AUTHOR_SCRIPT, authorScript);
sb.append("'\n"); createFile(rebaseDir, MESSAGE, commitToPick.getFullMessage());
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());
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DiffFormatter df = new DiffFormatter(bos); DiffFormatter df = new DiffFormatter(bos);
df.setRepository(repo); df.setRepository(repo);
df.format(commitToPick.getParent(0), commitToPick); df.format(commitToPick.getParent(0), commitToPick);
createFile(rebaseDir, "patch", new String(bos.toByteArray(), "UTF-8")); createFile(rebaseDir, PATCH, new String(bos.toByteArray(),
createFile(rebaseDir, "stopped-sha", repo.newObjectReader().abbreviate( Constants.CHARACTER_ENCODING));
createFile(rebaseDir, STOPPED_SHA, repo.newObjectReader().abbreviate(
commitToPick).name()); commitToPick).name());
return new RebaseResult(commitToPick); 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 * Removes the number of lines given in the parameter from the
* <code>git-rebase-todo</code> file but preserves comments and other lines * <code>git-rebase-todo</code> file but preserves comments and other lines
@ -266,10 +401,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return; return;
List<String> todoLines = new ArrayList<String>(); List<String> todoLines = new ArrayList<String>();
List<String> poppedLines = new ArrayList<String>(); List<String> poppedLines = new ArrayList<String>();
File todoFile = new File(rebaseDir, "git-rebase-todo"); File todoFile = new File(rebaseDir, GIT_REBASE_TODO);
File doneFile = new File(rebaseDir, "done"); File doneFile = new File(rebaseDir, DONE);
BufferedReader br = new BufferedReader(new InputStreamReader( BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(todoFile), "UTF-8")); new FileInputStream(todoFile), Constants.CHARACTER_ENCODING));
try { try {
// check if the line starts with a action tag (pick, skip...) // check if the line starts with a action tag (pick, skip...)
while (poppedLines.size() < numSteps) { while (poppedLines.size() < numSteps) {
@ -297,7 +432,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} }
BufferedWriter todoWriter = new BufferedWriter(new OutputStreamWriter( BufferedWriter todoWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(todoFile), "UTF-8")); new FileOutputStream(todoFile), Constants.CHARACTER_ENCODING));
try { try {
for (String writeLine : todoLines) { for (String writeLine : todoLines) {
todoWriter.write(writeLine); todoWriter.write(writeLine);
@ -311,7 +446,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
// append here // append here
BufferedWriter doneWriter = new BufferedWriter( BufferedWriter doneWriter = new BufferedWriter(
new OutputStreamWriter( new OutputStreamWriter(
new FileOutputStream(doneFile, true), "UTF-8")); new FileOutputStream(doneFile, true),
Constants.CHARACTER_ENCODING));
try { try {
for (String writeLine : poppedLines) { for (String writeLine : poppedLines) {
doneWriter.write(writeLine); doneWriter.write(writeLine);
@ -363,14 +499,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
// create the folder for the meta information // create the folder for the meta information
rebaseDir.mkdir(); rebaseDir.mkdir();
createFile(repo.getDirectory(), "ORIG_HEAD", headId.name()); createFile(repo.getDirectory(), Constants.ORIG_HEAD, headId.name());
createFile(rebaseDir, "head", headId.name()); createFile(rebaseDir, REBASE_HEAD, headId.name());
createFile(rebaseDir, "head-name", headName); createFile(rebaseDir, HEAD_NAME, headName);
createFile(rebaseDir, "onto", upstreamCommit.name()); createFile(rebaseDir, ONTO, upstreamCommit.name());
createFile(rebaseDir, "interactive", ""); createFile(rebaseDir, INTERACTIVE, "");
BufferedWriter fw = new BufferedWriter(new OutputStreamWriter( BufferedWriter fw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(new File(rebaseDir, "git-rebase-todo")), new FileOutputStream(new File(rebaseDir, GIT_REBASE_TODO)),
"UTF-8")); Constants.CHARACTER_ENCODING));
fw.write("# Created by EGit: rebasing " + upstreamCommit.name() fw.write("# Created by EGit: rebasing " + upstreamCommit.name()
+ " onto " + headId.name()); + " onto " + headId.name());
fw.newLine(); fw.newLine();
@ -404,13 +540,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
if (this.operation != Operation.BEGIN) { if (this.operation != Operation.BEGIN) {
// these operations are only possible while in a rebasing state // these operations are only possible while in a rebasing state
switch (repo.getRepositoryState()) { switch (repo.getRepositoryState()) {
case REBASING:
// fall through
case REBASING_INTERACTIVE: case REBASING_INTERACTIVE:
// fall through
case REBASING_MERGE:
// fall through
case REBASING_REBASING:
break; break;
default: default:
throw new WrongRepositoryStateException(MessageFormat.format( throw new WrongRepositoryStateException(MessageFormat.format(
@ -438,7 +568,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
File file = new File(parentDir, name); File file = new File(parentDir, name);
FileOutputStream fos = new FileOutputStream(file); FileOutputStream fos = new FileOutputStream(file);
try { try {
fos.write(content.getBytes("UTF-8")); fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
fos.write('\n'); fos.write('\n');
} finally { } finally {
fos.close(); fos.close();
@ -447,7 +577,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
private RebaseResult abort() throws IOException { private RebaseResult abort() throws IOException {
try { try {
String commitId = readFile(repo.getDirectory(), "ORIG_HEAD"); String commitId = readFile(repo.getDirectory(), Constants.ORIG_HEAD);
monitor.beginTask(MessageFormat.format( monitor.beginTask(MessageFormat.format(
JGitText.get().abortingRebase, commitId), JGitText.get().abortingRebase, commitId),
ProgressMonitor.UNKNOWN); ProgressMonitor.UNKNOWN);
@ -463,7 +593,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
monitor.endTask(); monitor.endTask();
} }
try { try {
String headName = readFile(rebaseDir, "head-name"); String headName = readFile(rebaseDir, HEAD_NAME);
if (headName.startsWith(Constants.R_REFS)) { if (headName.startsWith(Constants.R_REFS)) {
monitor.beginTask(MessageFormat.format( monitor.beginTask(MessageFormat.format(
JGitText.get().resettingHead, headName), JGitText.get().resettingHead, headName),
@ -527,7 +657,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
} }
private List<Step> loadSteps() throws IOException { 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 ptr = 0;
int tokenBegin = 0; int tokenBegin = 0;
ArrayList<Step> r = new ArrayList<Step>(); ArrayList<Step> r = new ArrayList<Step>();
@ -656,4 +786,42 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
this.action = action; 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;
}
} }

54
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);
}
}
Loading…
Cancel
Save