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.

HunkHeader.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. /*
  2. * Copyright (C) 2008-2009, Google Inc.
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.patch;
  44. import static org.eclipse.jgit.util.RawParseUtils.match;
  45. import static org.eclipse.jgit.util.RawParseUtils.nextLF;
  46. import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
  47. import java.io.IOException;
  48. import java.io.OutputStream;
  49. import java.text.MessageFormat;
  50. import org.eclipse.jgit.JGitText;
  51. import org.eclipse.jgit.diff.Edit;
  52. import org.eclipse.jgit.diff.EditList;
  53. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  54. import org.eclipse.jgit.util.MutableInteger;
  55. /** Hunk header describing the layout of a single block of lines */
  56. public class HunkHeader {
  57. /** Details about an old image of the file. */
  58. public abstract static class OldImage {
  59. /** First line number the hunk starts on in this file. */
  60. int startLine;
  61. /** Total number of lines this hunk covers in this file. */
  62. int lineCount;
  63. /** Number of lines deleted by the post-image from this file. */
  64. int nDeleted;
  65. /** Number of lines added by the post-image not in this file. */
  66. int nAdded;
  67. /** @return first line number the hunk starts on in this file. */
  68. public int getStartLine() {
  69. return startLine;
  70. }
  71. /** @return total number of lines this hunk covers in this file. */
  72. public int getLineCount() {
  73. return lineCount;
  74. }
  75. /** @return number of lines deleted by the post-image from this file. */
  76. public int getLinesDeleted() {
  77. return nDeleted;
  78. }
  79. /** @return number of lines added by the post-image not in this file. */
  80. public int getLinesAdded() {
  81. return nAdded;
  82. }
  83. /** @return object id of the pre-image file. */
  84. public abstract AbbreviatedObjectId getId();
  85. }
  86. final FileHeader file;
  87. /** Offset within {@link #file}.buf to the "@@ -" line. */
  88. final int startOffset;
  89. /** Position 1 past the end of this hunk within {@link #file}'s buf. */
  90. int endOffset;
  91. private final OldImage old;
  92. /** First line number in the post-image file where the hunk starts */
  93. int newStartLine;
  94. /** Total number of post-image lines this hunk covers (context + inserted) */
  95. int newLineCount;
  96. /** Total number of lines of context appearing in this hunk */
  97. int nContext;
  98. private EditList editList;
  99. HunkHeader(final FileHeader fh, final int offset) {
  100. this(fh, offset, new OldImage() {
  101. @Override
  102. public AbbreviatedObjectId getId() {
  103. return fh.getOldId();
  104. }
  105. });
  106. }
  107. HunkHeader(final FileHeader fh, final int offset, final OldImage oi) {
  108. file = fh;
  109. startOffset = offset;
  110. old = oi;
  111. }
  112. HunkHeader(final FileHeader fh, final EditList editList) {
  113. this(fh, fh.buf.length);
  114. this.editList = editList;
  115. endOffset = startOffset;
  116. nContext = 0;
  117. if (editList.isEmpty()) {
  118. newStartLine = 0;
  119. newLineCount = 0;
  120. } else {
  121. newStartLine = editList.get(0).getBeginB();
  122. Edit last = editList.get(editList.size() - 1);
  123. newLineCount = last.getEndB() - newStartLine;
  124. }
  125. }
  126. /** @return header for the file this hunk applies to */
  127. public FileHeader getFileHeader() {
  128. return file;
  129. }
  130. /** @return the byte array holding this hunk's patch script. */
  131. public byte[] getBuffer() {
  132. return file.buf;
  133. }
  134. /** @return offset the start of this hunk in {@link #getBuffer()}. */
  135. public int getStartOffset() {
  136. return startOffset;
  137. }
  138. /** @return offset one past the end of the hunk in {@link #getBuffer()}. */
  139. public int getEndOffset() {
  140. return endOffset;
  141. }
  142. /** @return information about the old image mentioned in this hunk. */
  143. public OldImage getOldImage() {
  144. return old;
  145. }
  146. /** @return first line number in the post-image file where the hunk starts */
  147. public int getNewStartLine() {
  148. return newStartLine;
  149. }
  150. /** @return Total number of post-image lines this hunk covers */
  151. public int getNewLineCount() {
  152. return newLineCount;
  153. }
  154. /** @return total number of lines of context appearing in this hunk */
  155. public int getLinesContext() {
  156. return nContext;
  157. }
  158. /** @return a list describing the content edits performed within the hunk. */
  159. public EditList toEditList() {
  160. if (editList == null) {
  161. editList = new EditList();
  162. final byte[] buf = file.buf;
  163. int c = nextLF(buf, startOffset);
  164. int oLine = old.startLine;
  165. int nLine = newStartLine;
  166. Edit in = null;
  167. SCAN: for (; c < endOffset; c = nextLF(buf, c)) {
  168. switch (buf[c]) {
  169. case ' ':
  170. case '\n':
  171. in = null;
  172. oLine++;
  173. nLine++;
  174. continue;
  175. case '-':
  176. if (in == null) {
  177. in = new Edit(oLine - 1, nLine - 1);
  178. editList.add(in);
  179. }
  180. oLine++;
  181. in.extendA();
  182. continue;
  183. case '+':
  184. if (in == null) {
  185. in = new Edit(oLine - 1, nLine - 1);
  186. editList.add(in);
  187. }
  188. nLine++;
  189. in.extendB();
  190. continue;
  191. case '\\': // Matches "\ No newline at end of file"
  192. continue;
  193. default:
  194. break SCAN;
  195. }
  196. }
  197. }
  198. return editList;
  199. }
  200. void parseHeader() {
  201. // Parse "@@ -236,9 +236,9 @@ protected boolean"
  202. //
  203. final byte[] buf = file.buf;
  204. final MutableInteger ptr = new MutableInteger();
  205. ptr.value = nextLF(buf, startOffset, ' ');
  206. old.startLine = -parseBase10(buf, ptr.value, ptr);
  207. if (buf[ptr.value] == ',')
  208. old.lineCount = parseBase10(buf, ptr.value + 1, ptr);
  209. else
  210. old.lineCount = 1;
  211. newStartLine = parseBase10(buf, ptr.value + 1, ptr);
  212. if (buf[ptr.value] == ',')
  213. newLineCount = parseBase10(buf, ptr.value + 1, ptr);
  214. else
  215. newLineCount = 1;
  216. }
  217. int parseBody(final Patch script, final int end) {
  218. final byte[] buf = file.buf;
  219. int c = nextLF(buf, startOffset), last = c;
  220. old.nDeleted = 0;
  221. old.nAdded = 0;
  222. SCAN: for (; c < end; last = c, c = nextLF(buf, c)) {
  223. switch (buf[c]) {
  224. case ' ':
  225. case '\n':
  226. nContext++;
  227. continue;
  228. case '-':
  229. old.nDeleted++;
  230. continue;
  231. case '+':
  232. old.nAdded++;
  233. continue;
  234. case '\\': // Matches "\ No newline at end of file"
  235. continue;
  236. default:
  237. break SCAN;
  238. }
  239. }
  240. if (last < end && nContext + old.nDeleted - 1 == old.lineCount
  241. && nContext + old.nAdded == newLineCount
  242. && match(buf, last, Patch.SIG_FOOTER) >= 0) {
  243. // This is an extremely common occurrence of "corruption".
  244. // Users add footers with their signatures after this mark,
  245. // and git diff adds the git executable version number.
  246. // Let it slide; the hunk otherwise looked sound.
  247. //
  248. old.nDeleted--;
  249. return last;
  250. }
  251. if (nContext + old.nDeleted < old.lineCount) {
  252. final int missingCount = old.lineCount - (nContext + old.nDeleted);
  253. script.error(buf, startOffset, MessageFormat.format(
  254. JGitText.get().truncatedHunkOldLinesMissing, missingCount));
  255. } else if (nContext + old.nAdded < newLineCount) {
  256. final int missingCount = newLineCount - (nContext + old.nAdded);
  257. script.error(buf, startOffset, MessageFormat.format(
  258. JGitText.get().truncatedHunkNewLinesMissing, missingCount));
  259. } else if (nContext + old.nDeleted > old.lineCount
  260. || nContext + old.nAdded > newLineCount) {
  261. final String oldcnt = old.lineCount + ":" + newLineCount;
  262. final String newcnt = (nContext + old.nDeleted) + ":"
  263. + (nContext + old.nAdded);
  264. script.warn(buf, startOffset, MessageFormat.format(
  265. JGitText.get().hunkHeaderDoesNotMatchBodyLineCountOf, oldcnt, newcnt));
  266. }
  267. return c;
  268. }
  269. void extractFileLines(final OutputStream[] out) throws IOException {
  270. final byte[] buf = file.buf;
  271. int ptr = startOffset;
  272. int eol = nextLF(buf, ptr);
  273. if (endOffset <= eol)
  274. return;
  275. // Treat the hunk header as though it were from the ancestor,
  276. // as it may have a function header appearing after it which
  277. // was copied out of the ancestor file.
  278. //
  279. out[0].write(buf, ptr, eol - ptr);
  280. SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
  281. eol = nextLF(buf, ptr);
  282. switch (buf[ptr]) {
  283. case ' ':
  284. case '\n':
  285. case '\\':
  286. out[0].write(buf, ptr, eol - ptr);
  287. out[1].write(buf, ptr, eol - ptr);
  288. break;
  289. case '-':
  290. out[0].write(buf, ptr, eol - ptr);
  291. break;
  292. case '+':
  293. out[1].write(buf, ptr, eol - ptr);
  294. break;
  295. default:
  296. break SCAN;
  297. }
  298. }
  299. }
  300. void extractFileLines(final StringBuilder sb, final String[] text,
  301. final int[] offsets) {
  302. final byte[] buf = file.buf;
  303. int ptr = startOffset;
  304. int eol = nextLF(buf, ptr);
  305. if (endOffset <= eol)
  306. return;
  307. copyLine(sb, text, offsets, 0);
  308. SCAN: for (ptr = eol; ptr < endOffset; ptr = eol) {
  309. eol = nextLF(buf, ptr);
  310. switch (buf[ptr]) {
  311. case ' ':
  312. case '\n':
  313. case '\\':
  314. copyLine(sb, text, offsets, 0);
  315. skipLine(text, offsets, 1);
  316. break;
  317. case '-':
  318. copyLine(sb, text, offsets, 0);
  319. break;
  320. case '+':
  321. copyLine(sb, text, offsets, 1);
  322. break;
  323. default:
  324. break SCAN;
  325. }
  326. }
  327. }
  328. void copyLine(final StringBuilder sb, final String[] text,
  329. final int[] offsets, final int fileIdx) {
  330. final String s = text[fileIdx];
  331. final int start = offsets[fileIdx];
  332. int end = s.indexOf('\n', start);
  333. if (end < 0)
  334. end = s.length();
  335. else
  336. end++;
  337. sb.append(s, start, end);
  338. offsets[fileIdx] = end;
  339. }
  340. void skipLine(final String[] text, final int[] offsets,
  341. final int fileIdx) {
  342. final String s = text[fileIdx];
  343. final int end = s.indexOf('\n', offsets[fileIdx]);
  344. offsets[fileIdx] = end < 0 ? s.length() : end + 1;
  345. }
  346. }