From 84fb2b59d11418d2fa753d27de11775ddc18adde Mon Sep 17 00:00:00 2001 From: Dariusz Luksza Date: Wed, 3 Oct 2012 09:59:30 +0200 Subject: [PATCH] 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 Signed-off-by: Chris Aniszczyk --- .../eclipse/jgit/api/RebaseCommandTest.java | 58 ++++++++ .../org/eclipse/jgit/api/RebaseCommand.java | 136 +++++++++++++++++- 2 files changed, 187 insertions(+), 7 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 81730b93a..07ce7606b 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 @@ -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 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 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 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"); 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 6f87349e5..ece861f7f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -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 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 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 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 + ", "