/* * 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.BufferedOutputStream; 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; /** * 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 { try (OutputStream fw = new BufferedOutputStream(new FileOutputStream( new File(repo.getDirectory(), path), append))) { 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())); } } } }