| /* | |
| * 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.File; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.util.LinkedList; | |
| import java.util.List; | |
| import org.eclipse.jgit.lib.RebaseTodoLine.Action; | |
| import org.eclipse.jgit.util.IO; | |
| import org.eclipse.jgit.util.RawParseUtils; | |
| import org.eclipse.jgit.util.io.SafeBufferedOutputStream; | |
| /** | |
| * Offers methods to read and write files formatted like the git-rebase-todo | |
| * file | |
| * | |
| * @since 3.2 | |
| */ | |
| 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) { | |
| 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) | |
| parseComments(buf, tokenBegin, r, lineEnd); | |
| } else { | |
| // skip leading spaces+tabs+cr | |
| tokenBegin = nextParsableToken(buf, tokenBegin, lineEnd); | |
| // Handle empty lines (maybe empty after skipping leading | |
| // whitespace) | |
| if (tokenBegin == -1) { | |
| if (includeComments) | |
| r.add(new RebaseTodoLine(RawParseUtils.decode(buf, | |
| lineStart, 1 + lineEnd))); | |
| continue; | |
| } | |
| RebaseTodoLine line = parseLine(buf, tokenBegin, lineEnd); | |
| if (line == null) | |
| continue; | |
| r.add(line); | |
| } | |
| } | |
| return r; | |
| } | |
| private static void parseComments(byte[] buf, int tokenBegin, | |
| List<RebaseTodoLine> r, int lineEnd) { | |
| RebaseTodoLine line = null; | |
| String commentString = RawParseUtils.decode(buf, | |
| tokenBegin, lineEnd + 1); | |
| try { | |
| int skip = tokenBegin + 1; // skip '#' | |
| skip = nextParsableToken(buf, skip, lineEnd); | |
| if (skip != -1) { | |
| // try to parse the line as non-comment | |
| line = parseLine(buf, skip, lineEnd); | |
| // successfully parsed as non-comment line | |
| // mark this line as a comment explicitly | |
| line.setAction(Action.COMMENT); | |
| // use the read line as comment string | |
| line.setComment(commentString); | |
| } | |
| } catch (Exception e) { | |
| // parsing as non-comment line failed | |
| line = null; | |
| } finally { | |
| if (line == null) | |
| line = new RebaseTodoLine(commentString); | |
| r.add(line); | |
| } | |
| } | |
| /** | |
| * Skip leading space, tab, CR and LF characters | |
| * | |
| * @param buf | |
| * @param tokenBegin | |
| * @param lineEnd | |
| * @return the token within the range of the given {@code buf} that doesn't | |
| * need to be skipped, {@code -1} if no such token found within the | |
| * range (i.e. empty line) | |
| */ | |
| private static int nextParsableToken(byte[] buf, int tokenBegin, int lineEnd) { | |
| while (tokenBegin <= lineEnd | |
| && (buf[tokenBegin] == ' ' || buf[tokenBegin] == '\t' || buf[tokenBegin] == '\r')) | |
| tokenBegin++; | |
| if (tokenBegin > lineEnd) | |
| return -1; | |
| return tokenBegin; | |
| } | |
| private static RebaseTodoLine parseLine(byte[] buf, int tokenBegin, | |
| int lineEnd) { | |
| RebaseTodoLine.Action action = null; | |
| AbbreviatedObjectId commit = null; | |
| int nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); | |
| int tokenCount = 0; | |
| while (tokenCount < 3 && nextSpace < lineEnd) { | |
| switch (tokenCount) { | |
| case 0: | |
| String actionToken = new String(buf, tokenBegin, nextSpace | |
| - tokenBegin - 1); | |
| tokenBegin = nextSpace; | |
| action = RebaseTodoLine.Action.parse(actionToken); | |
| if (action == null) | |
| return null; // parsing failed | |
| break; | |
| case 1: | |
| nextSpace = RawParseUtils.next(buf, tokenBegin, ' '); | |
| String commitToken = new String(buf, tokenBegin, nextSpace | |
| - tokenBegin - 1); | |
| tokenBegin = nextSpace; | |
| commit = AbbreviatedObjectId.fromString(commitToken); | |
| break; | |
| case 2: | |
| return new RebaseTodoLine(action, commit, RawParseUtils.decode( | |
| buf, tokenBegin, 1 + lineEnd)); | |
| } | |
| tokenCount++; | |
| } | |
| if (tokenCount == 2) | |
| return new RebaseTodoLine(action, commit, ""); //$NON-NLS-1$ | |
| return null; | |
| } | |
| /** | |
| * 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 { | |
| OutputStream fw = new SafeBufferedOutputStream(new FileOutputStream( | |
| new File(repo.getDirectory(), path), append)); | |
| 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()); | |
| } | |
| sb.append('\n'); | |
| fw.write(Constants.encode(sb.toString())); | |
| } | |
| } finally { | |
| fw.close(); | |
| } | |
| } | |
| } |