import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.Iterator;
import java.util.List;
import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.RebaseCommand.Action;
+import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler;
import org.eclipse.jgit.api.RebaseCommand.Operation;
import org.eclipse.jgit.api.RebaseCommand.Step;
import org.eclipse.jgit.api.RebaseResult.Status;
assertEquals("2222222", steps.get(1).commit.name());
}
+ @Test
+ public void testParseRewordCommand() throws Exception {
+ String todo = "pick 1111111 Commit 1\n"
+ + "reword 2222222 Commit 2\n";
+ write(getTodoFile(), todo);
+
+ RebaseCommand rebaseCommand = git.rebase();
+ List<Step> steps = rebaseCommand.loadSteps();
+
+ assertEquals(2, steps.size());
+ assertEquals("1111111", steps.get(0).commit.name());
+ assertEquals("2222222", steps.get(1).commit.name());
+ assertEquals(Action.REWORD, steps.get(1).action);
+ }
+
+ @Test
+ public void testRebaseInteractiveReword() throws Exception {
+ // create file1 on master
+ writeTrashFile(FILE1, FILE1);
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("Add file1").call();
+ assertTrue(new File(db.getWorkTree(), FILE1).exists());
+
+ // create file2 on master
+ writeTrashFile("file2", "file2");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("Add file2").call();
+ assertTrue(new File(db.getWorkTree(), "file2").exists());
+
+ // update FILE1 on master
+ writeTrashFile(FILE1, "blah");
+ git.add().addFilepattern(FILE1).call();
+ git.commit().setMessage("updated file1 on master").call();
+
+ writeTrashFile("file2", "more change");
+ git.add().addFilepattern("file2").call();
+ git.commit().setMessage("update file2 on side").call();
+
+ RebaseResult res = git.rebase().setUpstream("HEAD~2")
+ .runInteractively(new InteractiveHandler() {
+ public void prepareSteps(List<Step> steps) {
+ steps.get(0).action = Action.REWORD;
+ }
+ public String modifyCommitMessage(String commit) {
+ return "rewritten commit message";
+ }
+ }).call();
+ assertTrue(new File(db.getWorkTree(), "file2").exists());
+ checkFile(new File(db.getWorkTree(), "file2"), "more change");
+ assertEquals(Status.OK, res.getStatus());
+ Iterator<RevCommit> logIterator = git.log().all().call().iterator();
+ logIterator.next(); // skip first commit;
+ String actualCommitMag = logIterator.next().getShortMessage();
+ assertEquals("rewritten commit message", actualCommitMag);
+ }
+
private File getTodoFile() {
File todoFile = new File(db.getDirectory(),
"rebase-merge/git-rebase-todo");
private final File rebaseDir;
+ private InteractiveHandler interactiveHandler;
+
/**
* @param repo
*/
ObjectReader or = repo.newObjectReader();
List<Step> steps = loadSteps();
+ if (isInteractive()) {
+ interactiveHandler.prepareSteps(steps);
+ BufferedWriter fw = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(new File(
+ rebaseDir, GIT_REBASE_TODO)),
+ Constants.CHARACTER_ENCODING));
+ fw.newLine();
+ try {
+ StringBuilder sb = new StringBuilder();
+ for (Step step : steps) {
+ sb.setLength(0);
+ sb.append(step.action.token);
+ sb.append(" ");
+ sb.append(step.commit.name());
+ sb.append(" ");
+ sb.append(new String(step.shortMessage,
+ Constants.CHARACTER_ENCODING).trim());
+ fw.write(sb.toString());
+ fw.newLine();
+ }
+ } finally {
+ fw.close();
+ }
+ }
for (Step step : steps) {
popSteps(1);
Collection<ObjectId> ids = or.resolve(step.commit);
newHead = cherryPickResult.getNewHead();
}
}
+ switch (step.action) {
+ case PICK:
+ continue; // continue rebase process on pick command
+ case REWORD:
+ String oldMessage = commitToPick.getFullMessage();
+ String newMessage = interactiveHandler
+ .modifyCommitMessage(oldMessage);
+ newHead = new Git(repo).commit().setMessage(newMessage)
+ .setAmend(true).call();
+ continue;
+ }
} finally {
monitor.endTask();
}
String popCandidate = br.readLine();
if (popCandidate == null)
break;
+ if (popCandidate.length() == 0)
+ continue;
if (popCandidate.charAt(0) == '#')
continue;
int spaceIndex = popCandidate.indexOf(' ');
RevCommit headCommit = walk.lookupCommit(headId);
RevCommit upstream = walk.lookupCommit(upstreamCommit.getId());
- if (walk.isMergedInto(upstream, headCommit))
+ if (!isInteractive() && walk.isMergedInto(upstream, headCommit))
return RebaseResult.UP_TO_DATE_RESULT;
- else if (walk.isMergedInto(headCommit, upstream)) {
+ else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
// head is already merged into upstream, fast-foward
monitor.beginTask(MessageFormat.format(
JGitText.get().resettingHead,
return null;
}
+ private boolean isInteractive() {
+ return interactiveHandler != null;
+ }
+
/**
* checks if we can fast-forward and returns the new head if it is possible
*
return this;
}
- static enum Action {
- PICK("pick"); // later add SQUASH, EDIT, etc.
+ /**
+ * Enables interactive rebase
+ *
+ * @param handler
+ * @return this
+ */
+ public RebaseCommand runInteractively(InteractiveHandler handler) {
+ this.interactiveHandler = handler;
+ return this;
+ }
+
+ /**
+ * Allows configure rebase interactive process and modify commit message
+ */
+ public interface InteractiveHandler {
+ /**
+ * Given list of {@code steps} should be modified according to user
+ * rebase configuration
+ * @param steps
+ * initial configuration of rebase interactive
+ */
+ void prepareSteps(List<Step> steps);
+
+ /**
+ * Used for editing commit message on REWORD
+ *
+ * @param commit
+ * @return new commit message
+ */
+ String modifyCommitMessage(String commit);
+ }
+
+ /**
+ * Describes rebase actions
+ */
+ public static enum Action {
+ /** Use commit */
+ PICK("pick", "p"),
+ /** Use commit, but edit the commit message */
+ REWORD("reword", "r"); // later add SQUASH, EDIT, etc.
private final String token;
- private Action(String token) {
+ private final String shortToken;
+
+ private Action(String token, String shortToken) {
this.token = token;
+ this.shortToken = shortToken;
}
+ /**
+ * @return full action token name
+ */
public String toToken() {
return this.token;
}
}
static Action parse(String token) {
- if (token.equals("pick") || token.equals("p"))
+ if (token.equals(PICK.token) || token.equals(PICK.shortToken))
return PICK;
+ if (token.equals(REWORD.token) || token.equals(REWORD.shortToken))
+ return REWORD;
throw new JGitInternalException(MessageFormat.format(
JGitText.get().unknownOrUnsupportedCommand, token,
PICK.toToken()));
}
}
- static class Step {
+ /**
+ * Describes single rebase step
+ */
+ public static class Step {
Action action;
AbbreviatedObjectId commit;
this.action = action;
}
+ /**
+ * @return rebase action type
+ */
+ public Action getAction() {
+ return action;
+ }
+
+ /**
+ * @param action
+ */
+ public void setAction(Action action) {
+ this.action = action;
+ }
+
+ /**
+ * @return abbreviated commit SHA-1 of commit that action will be
+ * performed on
+ */
+ public AbbreviatedObjectId getCommit() {
+ return commit;
+ }
+
+ /**
+ * @return short message commit of commit that action will be performed
+ * on
+ */
+ public byte[] getShortMessage() {
+ return shortMessage;
+ }
+
@Override
public String toString() {
return "Step[" + action + ", "