diff options
7 files changed, 663 insertions, 289 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 ef5a1eaa0c..c1d2b22136 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 @@ -60,10 +60,8 @@ 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; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.UnmergedPathsException; @@ -73,6 +71,8 @@ import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RebaseTodoLine; +import org.eclipse.jgit.lib.RebaseTodoLine.Action; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.RepositoryState; @@ -85,6 +85,8 @@ import org.junit.Before; import org.junit.Test; public class RebaseCommandTest extends RepositoryTestCase { + private static final String GIT_REBASE_TODO = "rebase-merge/git-rebase-todo"; + private static final String FILE1 = "file1"; protected Git git; @@ -1497,15 +1499,18 @@ public class RebaseCommandTest extends RepositoryTestCase { try { String line = br.readLine(); while (line != null) { - String actionToken = line.substring(0, line.indexOf(' ')); - Action action = null; - try { - action = Action.parse(actionToken); - } catch (Exception e) { - // ignore + int firstBlank = line.indexOf(' '); + if (firstBlank != -1) { + String actionToken = line.substring(0, firstBlank); + Action action = null; + try { + action = Action.parse(actionToken); + } catch (Exception e) { + // ignore + } + if (Action.PICK.equals(action)) + count++; } - if (action != null) - count++; line = br.readLine(); } return count; @@ -1625,11 +1630,61 @@ public class RebaseCommandTest extends RepositoryTestCase { + "# Comment line at end\n"; write(getTodoFile(), todo); - RebaseCommand rebaseCommand = git.rebase(); - List<Step> steps = rebaseCommand.loadSteps(); + List<RebaseTodoLine> steps = db.readRebaseTodo(GIT_REBASE_TODO, false); assertEquals(2, steps.size()); - assertEquals("1111111", steps.get(0).commit.name()); - assertEquals("2222222", steps.get(1).commit.name()); + assertEquals("1111111", steps.get(0).getCommit().name()); + assertEquals("2222222", steps.get(1).getCommit().name()); + } + + @Test + public void testRebaseShouldNotFailIfUserAddCommentLinesInPrepareSteps() + throws Exception { + commitFile(FILE1, FILE1, "master"); + RevCommit c2 = commitFile("file2", "file2", "master"); + + // update files on master + commitFile(FILE1, "blah", "master"); + RevCommit c4 = commitFile("file2", "more change", "master"); + + RebaseResult res = git.rebase().setUpstream("HEAD~2") + .runInteractively(new InteractiveHandler() { + public void prepareSteps(List<RebaseTodoLine> steps) { + steps.add(0, new RebaseTodoLine( + "# Comment that should not be processed")); + } + + public String modifyCommitMessage(String commit) { + fail("modifyCommitMessage() was not expected to be called"); + return commit; + } + }).call(); + + assertEquals(RebaseResult.Status.FAST_FORWARD, res.getStatus()); + + RebaseResult res2 = git.rebase().setUpstream("HEAD~2") + .runInteractively(new InteractiveHandler() { + public void prepareSteps(List<RebaseTodoLine> steps) { + steps.get(0).setAction(Action.COMMENT); // delete + // RevCommit c4 + } + + public String modifyCommitMessage(String commit) { + fail("modifyCommitMessage() was not expected to be called"); + return commit; + } + }).call(); + + assertEquals(RebaseResult.Status.OK, res2.getStatus()); + + ObjectId headId = db.resolve(Constants.HEAD); + RevWalk rw = new RevWalk(db); + RevCommit rc = rw.parseCommit(headId); + + ObjectId head1Id = db.resolve(Constants.HEAD + "~1"); + RevCommit rc1 = rw.parseCommit(head1Id); + + assertEquals(rc.getFullMessage(), c4.getFullMessage()); + assertEquals(rc1.getFullMessage(), c2.getFullMessage()); } @Test @@ -1638,13 +1693,56 @@ public class RebaseCommandTest extends RepositoryTestCase { + "reword 2222222 Commit 2\n"; write(getTodoFile(), todo); - RebaseCommand rebaseCommand = git.rebase(); - List<Step> steps = rebaseCommand.loadSteps(); + List<RebaseTodoLine> steps = db.readRebaseTodo(GIT_REBASE_TODO, false); + + assertEquals(2, steps.size()); + assertEquals("1111111", steps.get(0).getCommit().name()); + assertEquals("2222222", steps.get(1).getCommit().name()); + assertEquals(Action.REWORD, steps.get(1).getAction()); + } + + @Test + public void testEmptyRebaseTodo() throws Exception { + write(getTodoFile(), ""); + assertEquals(0, db.readRebaseTodo(GIT_REBASE_TODO, true).size()); + assertEquals(0, db.readRebaseTodo(GIT_REBASE_TODO, false).size()); + } + + @Test + public void testOnlyCommentRebaseTodo() throws Exception { + write(getTodoFile(), "# a b c d e\n# e f"); + assertEquals(0, db.readRebaseTodo(GIT_REBASE_TODO, false).size()); + List<RebaseTodoLine> lines = db.readRebaseTodo(GIT_REBASE_TODO, true); + assertEquals(2, lines.size()); + for (RebaseTodoLine line : lines) + assertEquals(Action.COMMENT, line.getAction()); + write(getTodoFile(), "# a b c d e\n# e f\n"); + assertEquals(0, db.readRebaseTodo(GIT_REBASE_TODO, false).size()); + lines = db.readRebaseTodo(GIT_REBASE_TODO, true); + assertEquals(2, lines.size()); + for (RebaseTodoLine line : lines) + assertEquals(Action.COMMENT, line.getAction()); + write(getTodoFile(), " \r\n# a b c d e\r\n# e f\r\n#"); + assertEquals(0, db.readRebaseTodo(GIT_REBASE_TODO, false).size()); + lines = db.readRebaseTodo(GIT_REBASE_TODO, true); + assertEquals(4, lines.size()); + for (RebaseTodoLine line : lines) + assertEquals(Action.COMMENT, line.getAction()); + } + + @Test + public void testLeadingSpacesRebaseTodo() throws Exception { + String todo = " \t\t pick 1111111 Commit 1\n" + + "\t\n" + + "\treword 2222222 Commit 2\n"; + write(getTodoFile(), todo); + + List<RebaseTodoLine> steps = db.readRebaseTodo(GIT_REBASE_TODO, false); 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); + assertEquals("1111111", steps.get(0).getCommit().name()); + assertEquals("2222222", steps.get(1).getCommit().name()); + assertEquals(Action.REWORD, steps.get(1).getAction()); } @Test @@ -1672,8 +1770,8 @@ public class RebaseCommandTest extends RepositoryTestCase { RebaseResult res = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { - public void prepareSteps(List<Step> steps) { - steps.get(0).action = Action.REWORD; + public void prepareSteps(List<RebaseTodoLine> steps) { + steps.get(0).setAction(Action.REWORD); } public String modifyCommitMessage(String commit) { return "rewritten commit message"; @@ -1713,8 +1811,8 @@ public class RebaseCommandTest extends RepositoryTestCase { RebaseResult res = git.rebase().setUpstream("HEAD~2") .runInteractively(new InteractiveHandler() { - public void prepareSteps(List<Step> steps) { - steps.get(0).action = Action.EDIT; + public void prepareSteps(List<RebaseTodoLine> steps) { + steps.get(0).setAction(Action.EDIT); } public String modifyCommitMessage(String commit) { @@ -1741,8 +1839,7 @@ public class RebaseCommandTest extends RepositoryTestCase { } private File getTodoFile() { - File todoFile = new File(db.getDirectory(), - "rebase-merge/git-rebase-todo"); + File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO); return todoFile; } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 706dce7ce1..5cf7af81d2 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -13,6 +13,7 @@ anSSHSessionHasBeenAlreadyCreated=An SSH session has been already created applyingCommit=Applying {0} archiveFormatAlreadyAbsent=Archive format already absent: {0} archiveFormatAlreadyRegistered=Archive format already registered: {0} +argumentIsNotAValidCommentString=Invalid comment: {0} atLeastOnePathIsRequired=At least one path is required. atLeastOnePatternIsRequired=At least one pattern is required. atLeastTwoFiltersNeeded=At least two filters needed. @@ -38,6 +39,8 @@ cachedPacksPreventsIndexCreation=Using cached packs prevents index creation cachedPacksPreventsListingObjects=Using cached packs prevents listing objects cannotBeCombined=Cannot be combined. cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included. +cannotChangeActionOnComment=Cannot change action on comment line in git-rebase-todo file, old action: {0}, new action: {1}. +cannotChangeToComment=Cannot change a non-comment line to a comment line. cannotCombineSquashWithNoff=Cannot combine --squash with --no-ff. cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RevFilter {1}. cannotCommitOnARepoWithState=Cannot commit on a repo with state: {0} 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 a3f80c0f09..b8cbb9f048 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -42,21 +42,17 @@ */ package org.eclipse.jgit.api; -import java.io.BufferedReader; -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; -import java.io.OutputStreamWriter; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -75,7 +71,6 @@ import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -83,6 +78,8 @@ 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.RebaseTodoLine; +import org.eclipse.jgit.lib.RebaseTodoLine.Action; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; @@ -267,34 +264,18 @@ public class RebaseCommand extends GitCommand<RebaseResult> { ObjectReader or = repo.newObjectReader(); - List<Step> steps = loadSteps(); + List<RebaseTodoLine> steps = repo.readRebaseTodo( + rebaseState.getPath(GIT_REBASE_TODO), false); if (isInteractive()) { interactiveHandler.prepareSteps(steps); - BufferedWriter fw = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream( - rebaseState.getFile(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(" "); //$NON-NLS-1$ - sb.append(step.commit.name()); - sb.append(" "); //$NON-NLS-1$ - sb.append(RawParseUtils.decode(step.shortMessage) - .trim()); - fw.write(sb.toString()); - fw.newLine(); - } - } finally { - fw.close(); - } + repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO), + steps, false); } - for (Step step : steps) { + for (RebaseTodoLine step : steps) { popSteps(1); - Collection<ObjectId> ids = or.resolve(step.commit); + if (Action.COMMENT.equals(step.getAction())) + continue; + Collection<ObjectId> ids = or.resolve(step.getCommit()); if (ids.size() != 1) throw new JGitInternalException( "Could not resolve uniquely the abbreviated object ID"); @@ -334,7 +315,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { newHead = cherryPickResult.getNewHead(); } } - switch (step.action) { + switch (step.getAction()) { case PICK: continue; // continue rebase process on pick command case REWORD: @@ -347,6 +328,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { case EDIT: rebaseState.createFile(AMEND, commitToPick.name()); return stop(commitToPick); + case COMMENT: + break; } } finally { monitor.endTask(); @@ -541,67 +524,23 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private void popSteps(int numSteps) throws IOException { if (numSteps == 0) return; - List<String> todoLines = new ArrayList<String>(); - List<String> poppedLines = new ArrayList<String>(); - File todoFile = rebaseState.getFile(GIT_REBASE_TODO); - File doneFile = rebaseState.getFile(DONE); - BufferedReader br = new BufferedReader(new InputStreamReader( - new FileInputStream(todoFile), Constants.CHARACTER_ENCODING)); - try { - // check if the line starts with a action tag (pick, skip...) - while (poppedLines.size() < numSteps) { - String popCandidate = br.readLine(); - if (popCandidate == null) - break; - if (popCandidate.length() == 0) - continue; - if (popCandidate.charAt(0) == '#') - continue; - int spaceIndex = popCandidate.indexOf(' '); - boolean pop = false; - if (spaceIndex >= 0) { - String actionToken = popCandidate.substring(0, spaceIndex); - pop = Action.parse(actionToken) != null; - } - if (pop) - poppedLines.add(popCandidate); - else - todoLines.add(popCandidate); - } - String readLine = br.readLine(); - while (readLine != null) { - todoLines.add(readLine); - readLine = br.readLine(); - } - } finally { - br.close(); - } - - BufferedWriter todoWriter = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(todoFile), Constants.CHARACTER_ENCODING)); - try { - for (String writeLine : todoLines) { - todoWriter.write(writeLine); - todoWriter.newLine(); - } - } finally { - todoWriter.close(); + List<RebaseTodoLine> todoLines = new LinkedList<RebaseTodoLine>(); + List<RebaseTodoLine> poppedLines = new LinkedList<RebaseTodoLine>(); + + for (RebaseTodoLine line : repo.readRebaseTodo( + rebaseState.getPath(GIT_REBASE_TODO), true)) { + if (poppedLines.size() >= numSteps + || RebaseTodoLine.Action.COMMENT.equals(line.getAction())) + todoLines.add(line); + else + poppedLines.add(line); } + repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO), + todoLines, false); if (poppedLines.size() > 0) { - // append here - BufferedWriter doneWriter = new BufferedWriter( - new OutputStreamWriter( - new FileOutputStream(doneFile, true), - Constants.CHARACTER_ENCODING)); - try { - for (String writeLine : poppedLines) { - doneWriter.write(writeLine); - doneWriter.newLine(); - } - } finally { - doneWriter.close(); - } + repo.writeRebaseTodoFile(rebaseState.getPath(DONE), poppedLines, + true); } } @@ -666,28 +605,16 @@ public class RebaseCommand extends GitCommand<RebaseResult> { rebaseState.createFile(ONTO, upstreamCommit.name()); rebaseState.createFile(ONTO_NAME, upstreamCommitName); rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$ - BufferedWriter fw = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(rebaseState.getFile(GIT_REBASE_TODO)), - Constants.CHARACTER_ENCODING)); - fw.write("# Created by EGit: rebasing " + headId.name() + " onto " - + upstreamCommit.name()); - fw.newLine(); - try { - StringBuilder sb = new StringBuilder(); - ObjectReader reader = walk.getObjectReader(); - for (RevCommit commit : cherryPickList) { - sb.setLength(0); - sb.append(Action.PICK.toToken()); - sb.append(" "); //$NON-NLS-1$ - sb.append(reader.abbreviate(commit).name()); - sb.append(" "); //$NON-NLS-1$ - sb.append(commit.getShortMessage()); - fw.write(sb.toString()); - fw.newLine(); - } - } finally { - fw.close(); - } + + ArrayList<RebaseTodoLine> toDoSteps = new ArrayList<RebaseTodoLine>(); + toDoSteps.add(new RebaseTodoLine("# Created by EGit: rebasing " + headId.name() //$NON-NLS-1$ + + " onto " + upstreamCommit.name())); //$NON-NLS-1$ + ObjectReader reader = walk.getObjectReader(); + for (RevCommit commit : cherryPickList) + toDoSteps.add(new RebaseTodoLine(Action.PICK, reader + .abbreviate(commit), commit.getShortMessage())); + repo.writeRebaseTodoFile(rebaseState.getPath(GIT_REBASE_TODO), + toDoSteps, false); monitor.endTask(); @@ -907,57 +834,6 @@ public class RebaseCommand extends GitCommand<RebaseResult> { return true; } - List<Step> loadSteps() throws IOException { - byte[] buf = IO.readFully(rebaseState.getFile(GIT_REBASE_TODO)); - int ptr = 0; - int tokenBegin = 0; - ArrayList<Step> r = new ArrayList<Step>(); - while (ptr < buf.length) { - tokenBegin = ptr; - ptr = RawParseUtils.nextLF(buf, ptr); - int nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); - int tokenCount = 0; - Step current = null; - while (tokenCount < 3 && nextSpace < ptr) { - switch (tokenCount) { - case 0: - String actionToken = new String(buf, tokenBegin, nextSpace - - tokenBegin - 1); - tokenBegin = nextSpace; - if (actionToken.charAt(0) == '#') { - tokenCount = 3; - break; - } - Action action = Action.parse(actionToken); - if (action != null) - current = new Step(Action.parse(actionToken)); - break; - case 1: - if (current == null) - break; - nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); - String commitToken = new String(buf, tokenBegin, nextSpace - - tokenBegin - 1); - tokenBegin = nextSpace; - current.commit = AbbreviatedObjectId - .fromString(commitToken); - break; - case 2: - if (current == null) - break; - nextSpace = ptr; - int length = ptr - tokenBegin; - current.shortMessage = new byte[length]; - System.arraycopy(buf, tokenBegin, current.shortMessage, 0, - length); - r.add(current); - break; - } - tokenCount++; - } - } - return r; - } /** * @param upstream @@ -1066,7 +942,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { * @param steps * initial configuration of rebase interactive */ - void prepareSteps(List<Step> steps); + void prepareSteps(List<RebaseTodoLine> steps); /** * Used for editing commit message on REWORD @@ -1077,107 +953,6 @@ public class RebaseCommand extends GitCommand<RebaseResult> { String modifyCommitMessage(String commit); } - /** - * Describes rebase actions - */ - public static enum Action { - /** Use commit */ - PICK("pick", "p"), //$NON-NLS-1$ //$NON-NLS-2$ - /** Use commit, but edit the commit message */ - REWORD("reword", "r"), //$NON-NLS-1$ //$NON-NLS-2$ - /** Use commit, but stop for amending */ - EDIT("edit", "e"); // later add SQUASH, FIXUP, etc. //$NON-NLS-1$ //$NON-NLS-2$ - - private final 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; - } - - @SuppressWarnings("nls") - @Override - public String toString() { - return "Action[" + token + "]"; - } - - static Action parse(String token) { - for (Action action : Action.values()) { - if (action.token.equals(token) - || action.shortToken.equals(token)) - return action; - } - throw new JGitInternalException(MessageFormat.format( - JGitText.get().unknownOrUnsupportedCommand, token, - Action.values())); - } - } - - /** - * Describes single rebase step - */ - public static class Step { - Action action; - - AbbreviatedObjectId commit; - - byte[] shortMessage; - - Step(Action action) { - 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; - } - - @SuppressWarnings("nls") - @Override - public String toString() { - return "Step[" - + action - + ", " - + ((commit == null) ? "null" : commit) - + ", " - + ((shortMessage == null) ? "null" : new String( - shortMessage)) + "]"; - } - } PersonIdent parseAuthor(byte[] raw) { if (raw.length == 0) @@ -1257,6 +1032,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> { return new File(getDir(), name); } + public String getPath(String name) { + return (getDir().getName() + "/" + name); //$NON-NLS-1$ + } + private static String readFile(File directory, String fileName) throws IOException { byte[] content = IO.readFully(new File(directory, fileName)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 8ac971ab62..b4f99409bd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -75,6 +75,7 @@ public class JGitText extends TranslationBundle { /***/ public String applyingCommit; /***/ public String archiveFormatAlreadyAbsent; /***/ public String archiveFormatAlreadyRegistered; + /***/ public String argumentIsNotAValidCommentString; /***/ public String atLeastOnePathIsRequired; /***/ public String atLeastOnePatternIsRequired; /***/ public String atLeastTwoFiltersNeeded; @@ -100,6 +101,8 @@ public class JGitText extends TranslationBundle { /***/ public String cachedPacksPreventsListingObjects; /***/ public String cannotBeCombined; /***/ public String cannotBeRecursiveWhenTreesAreIncluded; + /***/ public String cannotChangeActionOnComment; + /***/ public String cannotChangeToComment; /***/ public String cannotCombineSquashWithNoff; /***/ public String cannotCombineTreeFilterWithRevFilter; /***/ public String cannotCommitOnARepoWithState; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java new file mode 100644 index 0000000000..a386fff0e1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2013, Christian Halstrick <christian.halstrick@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.lib; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Offers methods to read and write files formatted like the git-rebase-todo + * file + */ +public class RebaseTodoFile { + private Repository repo; + + /** + * @param repo + */ + public RebaseTodoFile(Repository repo) { + this.repo = repo; + } + + /** + * Read a file formatted like the git-rebase-todo file. The "done" file is + * also formatted like the git-rebase-todo file. These files can be found in + * .git/rebase-merge/ or .git/rebase-append/ folders. + * + * @param path + * path to the file relative to the repository's git-dir. E.g. + * "rebase-merge/git-rebase-todo" or "rebase-append/done" + * @param includeComments + * <code>true</code> if also comments should be reported + * @return the list of steps + * @throws IOException + */ + public List<RebaseTodoLine> readRebaseTodo(String path, + boolean includeComments) throws IOException { + byte[] buf = IO.readFully(new File(repo.getDirectory(), path)); + int ptr = 0; + int tokenBegin = 0; + List<RebaseTodoLine> r = new LinkedList<RebaseTodoLine>(); + while (ptr < buf.length) { + RebaseTodoLine.Action action = null; + AbbreviatedObjectId commit = null; + tokenBegin = ptr; + ptr = RawParseUtils.nextLF(buf, ptr); + int lineStart = tokenBegin; + int lineEnd = ptr - 2; + if (lineEnd >= 0 && buf[lineEnd] == '\r') + lineEnd--; + // Handle comments + if (buf[tokenBegin] == '#') { + if (includeComments) + r.add(new RebaseTodoLine(RawParseUtils.decode(buf, + tokenBegin, lineEnd + 1))); + continue; + } + // skip leading spaces, tabs, CR + while (tokenBegin <= lineEnd + && (buf[tokenBegin] == ' ' || buf[tokenBegin] == '\t' || buf[tokenBegin] == '\r')) + tokenBegin++; + // Handle empty lines (maybe empty after skipping leading + // whitespace) + if (tokenBegin > lineEnd) { + if (includeComments) + r.add(new RebaseTodoLine(RawParseUtils.decode(buf, + lineStart, 1 + lineEnd))); + continue; + } + int nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); + int tokenCount = 0; + while (tokenCount < 3 && nextSpace < ptr) { + switch (tokenCount) { + case 0: + String actionToken = new String(buf, tokenBegin, nextSpace + - tokenBegin - 1); + tokenBegin = nextSpace; + action = RebaseTodoLine.Action.parse(actionToken); + break; + case 1: + if (action == null) + break; + nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); + String commitToken = new String(buf, tokenBegin, nextSpace + - tokenBegin - 1); + tokenBegin = nextSpace; + commit = AbbreviatedObjectId.fromString(commitToken); + break; + case 2: + if (action == null) + break; + r.add(new RebaseTodoLine(action, commit, RawParseUtils + .decode(buf, tokenBegin, 1 + lineEnd))); + break; + } + tokenCount++; + } + } + return r; + } + + /** + * Write a file formatted like a git-rebase-todo file. + * + * @param path + * path to the file relative to the repository's git-dir. E.g. + * "rebase-merge/git-rebase-todo" or "rebase-append/done" + * @param steps + * the steps to be written + * @param append + * whether to append to an existing file or to write a new file + * @throws IOException + */ + public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, + boolean append) throws IOException { + BufferedWriter fw = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(new File(repo.getDirectory(), path), + append), Constants.CHARACTER_ENCODING)); + try { + StringBuilder sb = new StringBuilder(); + for (RebaseTodoLine step : steps) { + sb.setLength(0); + if (RebaseTodoLine.Action.COMMENT.equals(step.action)) + sb.append(step.getComment()); + else { + sb.append(step.getAction().toToken()); + sb.append(" "); //$NON-NLS-1$ + sb.append(step.getCommit().name()); + sb.append(" "); //$NON-NLS-1$ + sb.append(step.getShortMessage().trim()); + } + fw.write(sb.toString()); + fw.newLine(); + } + } finally { + fw.close(); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java new file mode 100644 index 0000000000..747c4d3fc4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2013, Christian Halstrick <christian.halstrick@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.lib; + +import java.text.MessageFormat; + +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.internal.JGitText; + +/** + * Describes a single line in a file formatted like the git-rebase-todo file. + */ +public class RebaseTodoLine { + /** + * Describes rebase actions + */ + @SuppressWarnings("nls") + public static enum Action { + /** Use commit */ + PICK("pick", "p"), + + /** Use commit, but edit the commit message */ + REWORD("reword", "r"), + + /** Use commit, but stop for amending */ + EDIT("edit", "e"), + + // TODO: add SQUASH, FIXUP, etc. + + /** + * A comment in the file. Also blank lines (or lines containing only + * whitespaces) are reported as comments + */ + COMMENT("comment", "#"); + + private final 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; + } + + @Override + public String toString() { + return "Action[" + token + "]"; + } + + /** + * @param token + * @return the Action + */ + static public Action parse(String token) { + for (Action action : Action.values()) { + if (action.token.equals(token) + || action.shortToken.equals(token)) + return action; + } + throw new JGitInternalException(MessageFormat.format( + JGitText.get().unknownOrUnsupportedCommand, token, + Action.values())); + } + } + + Action action; + + final AbbreviatedObjectId commit; + + String shortMessage; + + String comment; + + /** + * Create a new comment line + * + * @param newComment + * the new comment + */ + public RebaseTodoLine(String newComment) { + this.action = Action.COMMENT; + setComment(newComment); + this.commit = null; + this.shortMessage = null; + } + + /** + * Create a new non-comment line + * + * @param action + * @param commit + * @param shortMessage + */ + public RebaseTodoLine(Action action, AbbreviatedObjectId commit, + String shortMessage) { + this.action = action; + this.commit = commit; + this.shortMessage = shortMessage; + this.comment = null; + } + + /** + * @return rebase action type + */ + public Action getAction() { + return action; + } + + /** + * Set the action. It's not allowed to set a non-comment action on a line + * which was a comment line before. But you are allowed to set the comment + * action on a non-comment line and afterwards change the action back to + * non-comment. + * + * @param newAction + */ + public void setAction(Action newAction) { + if (!Action.COMMENT.equals(action) && Action.COMMENT.equals(newAction)) { + // transforming from non-comment to comment + if (comment == null) + // no comment was explicitly set. Take the old line as comment + // text + comment = "# " + action.token + " " //$NON-NLS-1$ //$NON-NLS-2$ + + ((commit == null) ? "null" : commit.name()) + " " //$NON-NLS-1$ //$NON-NLS-2$ + + ((shortMessage == null) ? "null" : shortMessage); //$NON-NLS-1$ + } else if (Action.COMMENT.equals(action) && !Action.COMMENT.equals(newAction)) { + // transforming from comment to non-comment + if (commit == null) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().cannotChangeActionOnComment, action, + newAction)); + } + this.action = newAction; + } + + /** + * <p> + * Set a comment for this line that is used if this line's + * {@link RebaseTodoLine#action} is a {@link Action#COMMENT} + * </p> + * It's allowed to unset the comment by calling + * <code>setComment(null)</code> <br> + * A valid comment either starts with a hash (i.e. <code>'#'</code>), is an + * empty string, or consists of only spaces and tabs.<br> + * If the argument <code>newComment</code> doesn't match these requirements + * an Exception is thrown. + * + * @param newComment + * the comment + */ + public void setComment(String newComment) { + if (newComment == null) { + this.comment = null; + return; + } + + if (newComment.contains("\n") || newComment.contains("\r")) //$NON-NLS-1$ //$NON-NLS-2$ + throw createInvalidCommentException(newComment); + + if (newComment.trim().length() == 0 || newComment.startsWith("#")) { //$NON-NLS-1$ + this.comment = newComment; + return; + } + + throw createInvalidCommentException(newComment); + } + + private static JGitInternalException createInvalidCommentException( + String newComment) { + IllegalArgumentException iae = new IllegalArgumentException( + MessageFormat.format( + JGitText.get().argumentIsNotAValidCommentString, newComment)); + return new JGitInternalException(iae.getMessage(), iae); + } + + /** + * @return abbreviated commit SHA-1 of commit that action will be performed + * on + */ + public AbbreviatedObjectId getCommit() { + return commit; + } + + /** + * @return the first line of the commit message of the commit the action + * will be performed on. + */ + public String getShortMessage() { + return shortMessage; + } + + /** + * @param shortMessage + */ + public void setShortMessage(String shortMessage) { + this.shortMessage = shortMessage; + } + + /** + * @return a comment. If the line is a comment line then the comment is + * returned. Lines starting with # or blank lines or lines + * containing only spaces and tabs are considered as comment lines. + * The complete line is returned (e.g. including the '#') + */ + public String getComment() { + return comment; + } + + @SuppressWarnings("nls") + @Override + public String toString() { + return "Step[" + + action + + ", " + + ((commit == null) ? "null" : commit) + + ", " + + ((shortMessage == null) ? "null" : shortMessage) + + ", " + + ((comment == null) ? "" : comment) + "]"; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 79f02c0230..7df538cedd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -1563,4 +1563,40 @@ public abstract class Repository { } } + /** + * Read a file formatted like the git-rebase-todo file. The "done" file is + * also formatted like the git-rebase-todo file. These files can be found in + * .git/rebase-merge/ or .git/rebase-append/ folders. + * + * @param path + * path to the file relative to the repository's git-dir. E.g. + * "rebase-merge/git-rebase-todo" or "rebase-append/done" + * @param includeComments + * <code>true</code> if also comments should be reported + * @return the list of steps + * @throws IOException + */ + public List<RebaseTodoLine> readRebaseTodo(String path, + boolean includeComments) + throws IOException { + return new RebaseTodoFile(this).readRebaseTodo(path, includeComments); + } + + /** + * Write a file formatted like a git-rebase-todo file. + * + * @param path + * path to the file relative to the repository's git-dir. E.g. + * "rebase-merge/git-rebase-todo" or "rebase-append/done" + * @param steps + * the steps to be written + * @param append + * whether to append to an existing file or to write a new file + * @throws IOException + */ + public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps, + boolean append) + throws IOException { + new RebaseTodoFile(this).writeRebaseTodoFile(path, steps, append); + } } |