You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RebaseTodoFile.java 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /*
  2. * Copyright (C) 2013, Christian Halstrick <christian.halstrick@sap.com> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.lib;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import java.io.BufferedOutputStream;
  13. import java.io.File;
  14. import java.io.FileOutputStream;
  15. import java.io.IOException;
  16. import java.io.OutputStream;
  17. import java.util.LinkedList;
  18. import java.util.List;
  19. import org.eclipse.jgit.lib.RebaseTodoLine.Action;
  20. import org.eclipse.jgit.util.IO;
  21. import org.eclipse.jgit.util.RawParseUtils;
  22. /**
  23. * Offers methods to read and write files formatted like the git-rebase-todo
  24. * file
  25. *
  26. * @since 3.2
  27. */
  28. public class RebaseTodoFile {
  29. private Repository repo;
  30. /**
  31. * Constructor for RebaseTodoFile.
  32. *
  33. * @param repo
  34. * a {@link org.eclipse.jgit.lib.Repository} object.
  35. */
  36. public RebaseTodoFile(Repository repo) {
  37. this.repo = repo;
  38. }
  39. /**
  40. * Read a file formatted like the git-rebase-todo file. The "done" file is
  41. * also formatted like the git-rebase-todo file. These files can be found in
  42. * .git/rebase-merge/ or .git/rebase-append/ folders.
  43. *
  44. * @param path
  45. * path to the file relative to the repository's git-dir. E.g.
  46. * "rebase-merge/git-rebase-todo" or "rebase-append/done"
  47. * @param includeComments
  48. * <code>true</code> if also comments should be reported
  49. * @return the list of steps
  50. * @throws java.io.IOException
  51. */
  52. public List<RebaseTodoLine> readRebaseTodo(String path,
  53. boolean includeComments) throws IOException {
  54. byte[] buf = IO.readFully(new File(repo.getDirectory(), path));
  55. int ptr = 0;
  56. int tokenBegin = 0;
  57. List<RebaseTodoLine> r = new LinkedList<>();
  58. while (ptr < buf.length) {
  59. tokenBegin = ptr;
  60. ptr = RawParseUtils.nextLF(buf, ptr);
  61. int lineStart = tokenBegin;
  62. int lineEnd = ptr - 2;
  63. if (lineEnd >= 0 && buf[lineEnd] == '\r')
  64. lineEnd--;
  65. // Handle comments
  66. if (buf[tokenBegin] == '#') {
  67. if (includeComments)
  68. parseComments(buf, tokenBegin, r, lineEnd);
  69. } else {
  70. // skip leading spaces+tabs+cr
  71. tokenBegin = nextParsableToken(buf, tokenBegin, lineEnd);
  72. // Handle empty lines (maybe empty after skipping leading
  73. // whitespace)
  74. if (tokenBegin == -1) {
  75. if (includeComments)
  76. r.add(new RebaseTodoLine(RawParseUtils.decode(buf,
  77. lineStart, 1 + lineEnd)));
  78. continue;
  79. }
  80. RebaseTodoLine line = parseLine(buf, tokenBegin, lineEnd);
  81. if (line == null)
  82. continue;
  83. r.add(line);
  84. }
  85. }
  86. return r;
  87. }
  88. private static void parseComments(byte[] buf, int tokenBegin,
  89. List<RebaseTodoLine> r, int lineEnd) {
  90. RebaseTodoLine line = null;
  91. String commentString = RawParseUtils.decode(buf,
  92. tokenBegin, lineEnd + 1);
  93. try {
  94. int skip = tokenBegin + 1; // skip '#'
  95. skip = nextParsableToken(buf, skip, lineEnd);
  96. if (skip != -1) {
  97. // try to parse the line as non-comment
  98. line = parseLine(buf, skip, lineEnd);
  99. if (line != null) {
  100. // successfully parsed as non-comment line
  101. // mark this line as a comment explicitly
  102. line.setAction(Action.COMMENT);
  103. // use the read line as comment string
  104. line.setComment(commentString);
  105. }
  106. }
  107. } catch (Exception e) {
  108. // parsing as non-comment line failed
  109. line = null;
  110. } finally {
  111. if (line == null)
  112. line = new RebaseTodoLine(commentString);
  113. r.add(line);
  114. }
  115. }
  116. /**
  117. * Skip leading space, tab, CR and LF characters
  118. *
  119. * @param buf
  120. * @param tokenBegin
  121. * @param lineEnd
  122. * @return the token within the range of the given {@code buf} that doesn't
  123. * need to be skipped, {@code -1} if no such token found within the
  124. * range (i.e. empty line)
  125. */
  126. private static int nextParsableToken(byte[] buf, int tokenBegin, int lineEnd) {
  127. while (tokenBegin <= lineEnd
  128. && (buf[tokenBegin] == ' ' || buf[tokenBegin] == '\t' || buf[tokenBegin] == '\r'))
  129. tokenBegin++;
  130. if (tokenBegin > lineEnd)
  131. return -1;
  132. return tokenBegin;
  133. }
  134. private static RebaseTodoLine parseLine(byte[] buf, int tokenBegin,
  135. int lineEnd) {
  136. RebaseTodoLine.Action action = null;
  137. AbbreviatedObjectId commit = null;
  138. int nextSpace = RawParseUtils.next(buf, tokenBegin, ' ');
  139. int tokenCount = 0;
  140. while (tokenCount < 3 && nextSpace <= lineEnd) {
  141. switch (tokenCount) {
  142. case 0:
  143. String actionToken = new String(buf, tokenBegin,
  144. nextSpace - tokenBegin - 1, UTF_8);
  145. tokenBegin = nextSpace;
  146. action = RebaseTodoLine.Action.parse(actionToken);
  147. if (action == null)
  148. return null; // parsing failed
  149. break;
  150. case 1:
  151. nextSpace = RawParseUtils.next(buf, tokenBegin, ' ');
  152. String commitToken;
  153. if (nextSpace > lineEnd + 1) {
  154. commitToken = new String(buf, tokenBegin,
  155. lineEnd - tokenBegin + 1, UTF_8);
  156. } else {
  157. commitToken = new String(buf, tokenBegin,
  158. nextSpace - tokenBegin - 1, UTF_8);
  159. }
  160. tokenBegin = nextSpace;
  161. commit = AbbreviatedObjectId.fromString(commitToken);
  162. break;
  163. case 2:
  164. return new RebaseTodoLine(action, commit,
  165. RawParseUtils.decode(buf, tokenBegin, 1 + lineEnd));
  166. }
  167. tokenCount++;
  168. }
  169. if (tokenCount == 2)
  170. return new RebaseTodoLine(action, commit, ""); //$NON-NLS-1$
  171. return null;
  172. }
  173. /**
  174. * Write a file formatted like a git-rebase-todo file.
  175. *
  176. * @param path
  177. * path to the file relative to the repository's git-dir. E.g.
  178. * "rebase-merge/git-rebase-todo" or "rebase-append/done"
  179. * @param steps
  180. * the steps to be written
  181. * @param append
  182. * whether to append to an existing file or to write a new file
  183. * @throws java.io.IOException
  184. */
  185. public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
  186. boolean append) throws IOException {
  187. try (OutputStream fw = new BufferedOutputStream(new FileOutputStream(
  188. new File(repo.getDirectory(), path), append))) {
  189. StringBuilder sb = new StringBuilder();
  190. for (RebaseTodoLine step : steps) {
  191. sb.setLength(0);
  192. if (RebaseTodoLine.Action.COMMENT.equals(step.action))
  193. sb.append(step.getComment());
  194. else {
  195. sb.append(step.getAction().toToken());
  196. sb.append(" "); //$NON-NLS-1$
  197. sb.append(step.getCommit().name());
  198. sb.append(" "); //$NON-NLS-1$
  199. sb.append(step.getShortMessage().trim());
  200. }
  201. sb.append('\n');
  202. fw.write(Constants.encode(sb.toString()));
  203. }
  204. }
  205. }
  206. }