Browse Source
Reading and writing files formatted like the git-rebase-todo files was hidden in the RebaseCommand. Certain constructs (like leading tabs and spaces) have not been handled as in native git. Also the upcoming rebase interactive feature in EGit needs reading/writing these files independently from a RebaseCommand. Therefore reading and writing those files has been moved to the Repository class. RebaseCommand gets smaller because of that and doesn't have to deal with reading/writing files. Additional tests for empty todo-list files, or files containing comments have been added. Change-Id: I323f3619952fecdf28ddf50139a88e0bea34f5ba Signed-off-by: Christian Halstrick <christian.halstrick@sap.com> Also-by: Tobias Pfeifer <to.pfeifer@sap.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>stable-3.2
Christian Halstrick
11 years ago
committed by
Matthias Sohn
7 changed files with 663 additions and 289 deletions
@ -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(); |
||||
} |
||||
} |
||||
} |
@ -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) + "]"; |
||||
} |
||||
} |
Loading…
Reference in new issue