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;
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;
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;
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;
+ "# 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
+ "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
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";
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) {
}
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;
}
}
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.
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}
*/
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;
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;
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;
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");
newHead = cherryPickResult.getNewHead();
}
}
- switch (step.action) {
+ switch (step.getAction()) {
case PICK:
continue; // continue rebase process on pick command
case REWORD:
case EDIT:
rebaseState.createFile(AMEND, commitToPick.name());
return stop(commitToPick);
+ case COMMENT:
+ break;
}
} finally {
monitor.endTask();
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);
}
}
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();
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
* @param steps
* initial configuration of rebase interactive
*/
- void prepareSteps(List<Step> steps);
+ void prepareSteps(List<RebaseTodoLine> steps);
/**
* Used for editing commit message on REWORD
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)
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));
/***/ public String applyingCommit;
/***/ public String archiveFormatAlreadyAbsent;
/***/ public String archiveFormatAlreadyRegistered;
+ /***/ public String argumentIsNotAValidCommentString;
/***/ public String atLeastOnePathIsRequired;
/***/ public String atLeastOnePatternIsRequired;
/***/ public String atLeastTwoFiltersNeeded;
/***/ public String cachedPacksPreventsListingObjects;
/***/ public String cannotBeCombined;
/***/ public String cannotBeRecursiveWhenTreesAreIncluded;
+ /***/ public String cannotChangeActionOnComment;
+ /***/ public String cannotChangeToComment;
/***/ public String cannotCombineSquashWithNoff;
/***/ public String cannotCombineTreeFilterWithRevFilter;
/***/ public String cannotCommitOnARepoWithState;
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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) + "]";
+ }
+}
}
}
+ /**
+ * 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);
+ }
}