Add support for rebase interactive 'reword' command
'reword' command is used to change commit message of any commit in git history. Bug: 394575 Change-Id: Ic974e76dfd923fd6f0cb8f07d1a6fbecd9abbf31 Signed-off-by: Dariusz Luksza <dariusz@luksza.org> Signed-off-by: Chris Aniszczyk <zx@twitter.com>
This commit is contained in:
parent
9051af3c4d
commit
84fb2b59d1
|
@ -53,10 +53,12 @@
|
|||
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;
|
||||
|
@ -1523,6 +1525,62 @@ public void testRebaseShouldBeAbleToHandleEmptyLinesInRebaseTodoFile()
|
|||
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");
|
||||
|
|
|
@ -177,6 +177,8 @@ public enum Operation {
|
|||
|
||||
private final File rebaseDir;
|
||||
|
||||
private InteractiveHandler interactiveHandler;
|
||||
|
||||
/**
|
||||
* @param repo
|
||||
*/
|
||||
|
@ -254,6 +256,30 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
|
|||
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);
|
||||
|
@ -295,6 +321,17 @@ public RebaseResult call() throws GitAPIException, NoHeadException,
|
|||
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();
|
||||
}
|
||||
|
@ -492,6 +529,8 @@ private void popSteps(int numSteps) throws IOException {
|
|||
String popCandidate = br.readLine();
|
||||
if (popCandidate == null)
|
||||
break;
|
||||
if (popCandidate.length() == 0)
|
||||
continue;
|
||||
if (popCandidate.charAt(0) == '#')
|
||||
continue;
|
||||
int spaceIndex = popCandidate.indexOf(' ');
|
||||
|
@ -564,9 +603,9 @@ private RebaseResult initFilesAndRewind() throws IOException,
|
|||
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,
|
||||
|
@ -647,6 +686,10 @@ else if (walk.isMergedInto(headCommit, upstream)) {
|
|||
return null;
|
||||
}
|
||||
|
||||
private boolean isInteractive() {
|
||||
return interactiveHandler != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if we can fast-forward and returns the new head if it is possible
|
||||
*
|
||||
|
@ -988,15 +1031,59 @@ public RebaseCommand setProgressMonitor(ProgressMonitor monitor) {
|
|||
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;
|
||||
}
|
||||
|
@ -1007,15 +1094,20 @@ public String toString() {
|
|||
}
|
||||
|
||||
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;
|
||||
|
@ -1026,6 +1118,36 @@ static class Step {
|
|||
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 + ", "
|
||||
|
|
Loading…
Reference in New Issue