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.

FileHeader.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  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.lib.Constants.encodeASCII;
  45. import static org.eclipse.jgit.util.RawParseUtils.decode;
  46. import static org.eclipse.jgit.util.RawParseUtils.decodeNoFallback;
  47. import static org.eclipse.jgit.util.RawParseUtils.extractBinaryString;
  48. import static org.eclipse.jgit.util.RawParseUtils.match;
  49. import static org.eclipse.jgit.util.RawParseUtils.nextLF;
  50. import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
  51. import java.io.IOException;
  52. import java.nio.charset.CharacterCodingException;
  53. import java.nio.charset.Charset;
  54. import java.text.MessageFormat;
  55. import java.util.ArrayList;
  56. import java.util.Collections;
  57. import java.util.List;
  58. import org.eclipse.jgit.diff.DiffEntry;
  59. import org.eclipse.jgit.diff.EditList;
  60. import org.eclipse.jgit.internal.JGitText;
  61. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  62. import org.eclipse.jgit.lib.Constants;
  63. import org.eclipse.jgit.lib.FileMode;
  64. import org.eclipse.jgit.util.QuotedString;
  65. import org.eclipse.jgit.util.RawParseUtils;
  66. import org.eclipse.jgit.util.TemporaryBuffer;
  67. /**
  68. * Patch header describing an action for a single file path.
  69. */
  70. public class FileHeader extends DiffEntry {
  71. private static final byte[] OLD_MODE = encodeASCII("old mode "); //$NON-NLS-1$
  72. private static final byte[] NEW_MODE = encodeASCII("new mode "); //$NON-NLS-1$
  73. static final byte[] DELETED_FILE_MODE = encodeASCII("deleted file mode "); //$NON-NLS-1$
  74. static final byte[] NEW_FILE_MODE = encodeASCII("new file mode "); //$NON-NLS-1$
  75. private static final byte[] COPY_FROM = encodeASCII("copy from "); //$NON-NLS-1$
  76. private static final byte[] COPY_TO = encodeASCII("copy to "); //$NON-NLS-1$
  77. private static final byte[] RENAME_OLD = encodeASCII("rename old "); //$NON-NLS-1$
  78. private static final byte[] RENAME_NEW = encodeASCII("rename new "); //$NON-NLS-1$
  79. private static final byte[] RENAME_FROM = encodeASCII("rename from "); //$NON-NLS-1$
  80. private static final byte[] RENAME_TO = encodeASCII("rename to "); //$NON-NLS-1$
  81. private static final byte[] SIMILARITY_INDEX = encodeASCII("similarity index "); //$NON-NLS-1$
  82. private static final byte[] DISSIMILARITY_INDEX = encodeASCII("dissimilarity index "); //$NON-NLS-1$
  83. static final byte[] INDEX = encodeASCII("index "); //$NON-NLS-1$
  84. static final byte[] OLD_NAME = encodeASCII("--- "); //$NON-NLS-1$
  85. static final byte[] NEW_NAME = encodeASCII("+++ "); //$NON-NLS-1$
  86. /** Type of patch used by this file. */
  87. public static enum PatchType {
  88. /** A traditional unified diff style patch of a text file. */
  89. UNIFIED,
  90. /** An empty patch with a message "Binary files ... differ" */
  91. BINARY,
  92. /** A Git binary patch, holding pre and post image deltas */
  93. GIT_BINARY;
  94. }
  95. /** Buffer holding the patch data for this file. */
  96. final byte[] buf;
  97. /** Offset within {@link #buf} to the "diff ..." line. */
  98. final int startOffset;
  99. /** Position 1 past the end of this file within {@link #buf}. */
  100. int endOffset;
  101. /** Type of patch used to modify this file */
  102. PatchType patchType;
  103. /** The hunks of this file */
  104. private List<HunkHeader> hunks;
  105. /** If {@link #patchType} is {@link PatchType#GIT_BINARY}, the new image */
  106. BinaryHunk forwardBinaryHunk;
  107. /** If {@link #patchType} is {@link PatchType#GIT_BINARY}, the old image */
  108. BinaryHunk reverseBinaryHunk;
  109. /**
  110. * Constructs a new FileHeader
  111. *
  112. * @param headerLines
  113. * buffer holding the diff header for this file
  114. * @param edits
  115. * the edits for this file
  116. * @param type
  117. * the type of patch used to modify this file
  118. */
  119. public FileHeader(final byte[] headerLines, EditList edits, PatchType type) {
  120. this(headerLines, 0);
  121. endOffset = headerLines.length;
  122. int ptr = parseGitFileName(Patch.DIFF_GIT.length, headerLines.length);
  123. parseGitHeaders(ptr, headerLines.length);
  124. this.patchType = type;
  125. addHunk(new HunkHeader(this, edits));
  126. }
  127. FileHeader(final byte[] b, final int offset) {
  128. buf = b;
  129. startOffset = offset;
  130. changeType = ChangeType.MODIFY; // unless otherwise designated
  131. patchType = PatchType.UNIFIED;
  132. }
  133. int getParentCount() {
  134. return 1;
  135. }
  136. /**
  137. * Get the byte array holding this file's patch script.
  138. *
  139. * @return the byte array holding this file's patch script.
  140. */
  141. public byte[] getBuffer() {
  142. return buf;
  143. }
  144. /**
  145. * Get offset of the start of this file's script in {@link #getBuffer()}.
  146. *
  147. * @return offset of the start of this file's script in
  148. * {@link #getBuffer()}.
  149. */
  150. public int getStartOffset() {
  151. return startOffset;
  152. }
  153. /**
  154. * Get offset one past the end of the file script.
  155. *
  156. * @return offset one past the end of the file script.
  157. */
  158. public int getEndOffset() {
  159. return endOffset;
  160. }
  161. /**
  162. * Convert the patch script for this file into a string.
  163. * <p>
  164. * The default character encoding
  165. * ({@link org.eclipse.jgit.lib.Constants#CHARSET}) is assumed for both the
  166. * old and new files.
  167. *
  168. * @return the patch script, as a Unicode string.
  169. */
  170. public String getScriptText() {
  171. return getScriptText(null, null);
  172. }
  173. /**
  174. * Convert the patch script for this file into a string.
  175. *
  176. * @param oldCharset
  177. * hint character set to decode the old lines with.
  178. * @param newCharset
  179. * hint character set to decode the new lines with.
  180. * @return the patch script, as a Unicode string.
  181. */
  182. public String getScriptText(Charset oldCharset, Charset newCharset) {
  183. return getScriptText(new Charset[] { oldCharset, newCharset });
  184. }
  185. String getScriptText(Charset[] charsetGuess) {
  186. if (getHunks().isEmpty()) {
  187. // If we have no hunks then we can safely assume the entire
  188. // patch is a binary style patch, or a meta-data only style
  189. // patch. Either way the encoding of the headers should be
  190. // strictly 7-bit US-ASCII and the body is either 7-bit ASCII
  191. // (due to the base 85 encoding used for a BinaryHunk) or is
  192. // arbitrary noise we have chosen to ignore and not understand
  193. // (e.g. the message "Binary files ... differ").
  194. //
  195. return extractBinaryString(buf, startOffset, endOffset);
  196. }
  197. if (charsetGuess != null && charsetGuess.length != getParentCount() + 1)
  198. throw new IllegalArgumentException(MessageFormat.format(
  199. JGitText.get().expectedCharacterEncodingGuesses,
  200. Integer.valueOf(getParentCount() + 1)));
  201. if (trySimpleConversion(charsetGuess)) {
  202. Charset cs = charsetGuess != null ? charsetGuess[0] : null;
  203. if (cs == null)
  204. cs = Constants.CHARSET;
  205. try {
  206. return decodeNoFallback(cs, buf, startOffset, endOffset);
  207. } catch (CharacterCodingException cee) {
  208. // Try the much slower, more-memory intensive version which
  209. // can handle a character set conversion patch.
  210. }
  211. }
  212. final StringBuilder r = new StringBuilder(endOffset - startOffset);
  213. // Always treat the headers as US-ASCII; Git file names are encoded
  214. // in a C style escape if any character has the high-bit set.
  215. //
  216. final int hdrEnd = getHunks().get(0).getStartOffset();
  217. for (int ptr = startOffset; ptr < hdrEnd;) {
  218. final int eol = Math.min(hdrEnd, nextLF(buf, ptr));
  219. r.append(extractBinaryString(buf, ptr, eol));
  220. ptr = eol;
  221. }
  222. final String[] files = extractFileLines(charsetGuess);
  223. final int[] offsets = new int[files.length];
  224. for (final HunkHeader h : getHunks())
  225. h.extractFileLines(r, files, offsets);
  226. return r.toString();
  227. }
  228. private static boolean trySimpleConversion(final Charset[] charsetGuess) {
  229. if (charsetGuess == null)
  230. return true;
  231. for (int i = 1; i < charsetGuess.length; i++) {
  232. if (charsetGuess[i] != charsetGuess[0])
  233. return false;
  234. }
  235. return true;
  236. }
  237. private String[] extractFileLines(final Charset[] csGuess) {
  238. final TemporaryBuffer[] tmp = new TemporaryBuffer[getParentCount() + 1];
  239. try {
  240. for (int i = 0; i < tmp.length; i++)
  241. tmp[i] = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
  242. for (final HunkHeader h : getHunks())
  243. h.extractFileLines(tmp);
  244. final String[] r = new String[tmp.length];
  245. for (int i = 0; i < tmp.length; i++) {
  246. Charset cs = csGuess != null ? csGuess[i] : null;
  247. if (cs == null)
  248. cs = Constants.CHARSET;
  249. r[i] = RawParseUtils.decode(cs, tmp[i].toByteArray());
  250. }
  251. return r;
  252. } catch (IOException ioe) {
  253. throw new RuntimeException(JGitText.get().cannotConvertScriptToText, ioe);
  254. }
  255. }
  256. /**
  257. * Get style of patch used to modify this file.
  258. *
  259. * @return style of patch used to modify this file.
  260. */
  261. public PatchType getPatchType() {
  262. return patchType;
  263. }
  264. /**
  265. * Whether this patch modifies metadata about a file
  266. *
  267. * @return {@code true} if this patch modifies metadata about a file .
  268. */
  269. public boolean hasMetaDataChanges() {
  270. return changeType != ChangeType.MODIFY || newMode != oldMode;
  271. }
  272. /**
  273. * Get hunks altering this file; in order of appearance in patch
  274. *
  275. * @return hunks altering this file; in order of appearance in patch.
  276. */
  277. public List<? extends HunkHeader> getHunks() {
  278. if (hunks == null)
  279. return Collections.emptyList();
  280. return hunks;
  281. }
  282. void addHunk(final HunkHeader h) {
  283. if (h.getFileHeader() != this)
  284. throw new IllegalArgumentException(JGitText.get().hunkBelongsToAnotherFile);
  285. if (hunks == null)
  286. hunks = new ArrayList<>();
  287. hunks.add(h);
  288. }
  289. HunkHeader newHunkHeader(final int offset) {
  290. return new HunkHeader(this, offset);
  291. }
  292. /**
  293. * Get the new-image delta/literal if this is a
  294. * {@link PatchType#GIT_BINARY}.
  295. *
  296. * @return the new-image delta/literal if this is a
  297. * {@link PatchType#GIT_BINARY}.
  298. */
  299. public BinaryHunk getForwardBinaryHunk() {
  300. return forwardBinaryHunk;
  301. }
  302. /**
  303. * Get the old-image delta/literal if this is a
  304. * {@link PatchType#GIT_BINARY}.
  305. *
  306. * @return the old-image delta/literal if this is a
  307. * {@link PatchType#GIT_BINARY}.
  308. */
  309. public BinaryHunk getReverseBinaryHunk() {
  310. return reverseBinaryHunk;
  311. }
  312. /**
  313. * Convert to a list describing the content edits performed on this file.
  314. *
  315. * @return a list describing the content edits performed on this file.
  316. */
  317. public EditList toEditList() {
  318. final EditList r = new EditList();
  319. for (final HunkHeader hunk : hunks)
  320. r.addAll(hunk.toEditList());
  321. return r;
  322. }
  323. /**
  324. * Parse a "diff --git" or "diff --cc" line.
  325. *
  326. * @param ptr
  327. * first character after the "diff --git " or "diff --cc " part.
  328. * @param end
  329. * one past the last position to parse.
  330. * @return first character after the LF at the end of the line; -1 on error.
  331. */
  332. int parseGitFileName(int ptr, final int end) {
  333. final int eol = nextLF(buf, ptr);
  334. final int bol = ptr;
  335. if (eol >= end) {
  336. return -1;
  337. }
  338. // buffer[ptr..eol] looks like "a/foo b/foo\n". After the first
  339. // A regex to match this is "^[^/]+/(.*?) [^/+]+/\1\n$". There
  340. // is only one way to split the line such that text to the left
  341. // of the space matches the text to the right, excluding the part
  342. // before the first slash.
  343. //
  344. final int aStart = nextLF(buf, ptr, '/');
  345. if (aStart >= eol)
  346. return eol;
  347. while (ptr < eol) {
  348. final int sp = nextLF(buf, ptr, ' ');
  349. if (sp >= eol) {
  350. // We can't split the header, it isn't valid.
  351. // This may be OK if this is a rename patch.
  352. //
  353. return eol;
  354. }
  355. final int bStart = nextLF(buf, sp, '/');
  356. if (bStart >= eol)
  357. return eol;
  358. // If buffer[aStart..sp - 1] = buffer[bStart..eol - 1]
  359. // we have a valid split.
  360. //
  361. if (eq(aStart, sp - 1, bStart, eol - 1)) {
  362. if (buf[bol] == '"') {
  363. // We're a double quoted name. The region better end
  364. // in a double quote too, and we need to decode the
  365. // characters before reading the name.
  366. //
  367. if (buf[sp - 2] != '"') {
  368. return eol;
  369. }
  370. oldPath = QuotedString.GIT_PATH.dequote(buf, bol, sp - 1);
  371. oldPath = p1(oldPath);
  372. } else {
  373. oldPath = decode(Constants.CHARSET, buf, aStart, sp - 1);
  374. }
  375. newPath = oldPath;
  376. return eol;
  377. }
  378. // This split wasn't correct. Move past the space and try
  379. // another split as the space must be part of the file name.
  380. //
  381. ptr = sp;
  382. }
  383. return eol;
  384. }
  385. int parseGitHeaders(int ptr, final int end) {
  386. while (ptr < end) {
  387. final int eol = nextLF(buf, ptr);
  388. if (isHunkHdr(buf, ptr, eol) >= 1) {
  389. // First hunk header; break out and parse them later.
  390. break;
  391. } else if (match(buf, ptr, OLD_NAME) >= 0) {
  392. parseOldName(ptr, eol);
  393. } else if (match(buf, ptr, NEW_NAME) >= 0) {
  394. parseNewName(ptr, eol);
  395. } else if (match(buf, ptr, OLD_MODE) >= 0) {
  396. oldMode = parseFileMode(ptr + OLD_MODE.length, eol);
  397. } else if (match(buf, ptr, NEW_MODE) >= 0) {
  398. newMode = parseFileMode(ptr + NEW_MODE.length, eol);
  399. } else if (match(buf, ptr, DELETED_FILE_MODE) >= 0) {
  400. oldMode = parseFileMode(ptr + DELETED_FILE_MODE.length, eol);
  401. newMode = FileMode.MISSING;
  402. changeType = ChangeType.DELETE;
  403. } else if (match(buf, ptr, NEW_FILE_MODE) >= 0) {
  404. parseNewFileMode(ptr, eol);
  405. } else if (match(buf, ptr, COPY_FROM) >= 0) {
  406. oldPath = parseName(oldPath, ptr + COPY_FROM.length, eol);
  407. changeType = ChangeType.COPY;
  408. } else if (match(buf, ptr, COPY_TO) >= 0) {
  409. newPath = parseName(newPath, ptr + COPY_TO.length, eol);
  410. changeType = ChangeType.COPY;
  411. } else if (match(buf, ptr, RENAME_OLD) >= 0) {
  412. oldPath = parseName(oldPath, ptr + RENAME_OLD.length, eol);
  413. changeType = ChangeType.RENAME;
  414. } else if (match(buf, ptr, RENAME_NEW) >= 0) {
  415. newPath = parseName(newPath, ptr + RENAME_NEW.length, eol);
  416. changeType = ChangeType.RENAME;
  417. } else if (match(buf, ptr, RENAME_FROM) >= 0) {
  418. oldPath = parseName(oldPath, ptr + RENAME_FROM.length, eol);
  419. changeType = ChangeType.RENAME;
  420. } else if (match(buf, ptr, RENAME_TO) >= 0) {
  421. newPath = parseName(newPath, ptr + RENAME_TO.length, eol);
  422. changeType = ChangeType.RENAME;
  423. } else if (match(buf, ptr, SIMILARITY_INDEX) >= 0) {
  424. score = parseBase10(buf, ptr + SIMILARITY_INDEX.length, null);
  425. } else if (match(buf, ptr, DISSIMILARITY_INDEX) >= 0) {
  426. score = parseBase10(buf, ptr + DISSIMILARITY_INDEX.length, null);
  427. } else if (match(buf, ptr, INDEX) >= 0) {
  428. parseIndexLine(ptr + INDEX.length, eol);
  429. } else {
  430. // Probably an empty patch (stat dirty).
  431. break;
  432. }
  433. ptr = eol;
  434. }
  435. return ptr;
  436. }
  437. void parseOldName(int ptr, final int eol) {
  438. oldPath = p1(parseName(oldPath, ptr + OLD_NAME.length, eol));
  439. if (oldPath == DEV_NULL)
  440. changeType = ChangeType.ADD;
  441. }
  442. void parseNewName(int ptr, final int eol) {
  443. newPath = p1(parseName(newPath, ptr + NEW_NAME.length, eol));
  444. if (newPath == DEV_NULL)
  445. changeType = ChangeType.DELETE;
  446. }
  447. void parseNewFileMode(int ptr, final int eol) {
  448. oldMode = FileMode.MISSING;
  449. newMode = parseFileMode(ptr + NEW_FILE_MODE.length, eol);
  450. changeType = ChangeType.ADD;
  451. }
  452. int parseTraditionalHeaders(int ptr, final int end) {
  453. while (ptr < end) {
  454. final int eol = nextLF(buf, ptr);
  455. if (isHunkHdr(buf, ptr, eol) >= 1) {
  456. // First hunk header; break out and parse them later.
  457. break;
  458. } else if (match(buf, ptr, OLD_NAME) >= 0) {
  459. parseOldName(ptr, eol);
  460. } else if (match(buf, ptr, NEW_NAME) >= 0) {
  461. parseNewName(ptr, eol);
  462. } else {
  463. // Possibly an empty patch.
  464. break;
  465. }
  466. ptr = eol;
  467. }
  468. return ptr;
  469. }
  470. private String parseName(final String expect, int ptr, final int end) {
  471. if (ptr == end)
  472. return expect;
  473. String r;
  474. if (buf[ptr] == '"') {
  475. // New style GNU diff format
  476. //
  477. r = QuotedString.GIT_PATH.dequote(buf, ptr, end - 1);
  478. } else {
  479. // Older style GNU diff format, an optional tab ends the name.
  480. //
  481. int tab = end;
  482. while (ptr < tab && buf[tab - 1] != '\t')
  483. tab--;
  484. if (ptr == tab)
  485. tab = end;
  486. r = decode(Constants.CHARSET, buf, ptr, tab - 1);
  487. }
  488. if (r.equals(DEV_NULL))
  489. r = DEV_NULL;
  490. return r;
  491. }
  492. private static String p1(final String r) {
  493. final int s = r.indexOf('/');
  494. return s > 0 ? r.substring(s + 1) : r;
  495. }
  496. FileMode parseFileMode(int ptr, final int end) {
  497. int tmp = 0;
  498. while (ptr < end - 1) {
  499. tmp <<= 3;
  500. tmp += buf[ptr++] - '0';
  501. }
  502. return FileMode.fromBits(tmp);
  503. }
  504. void parseIndexLine(int ptr, final int end) {
  505. // "index $asha1..$bsha1[ $mode]" where $asha1 and $bsha1
  506. // can be unique abbreviations
  507. //
  508. final int dot2 = nextLF(buf, ptr, '.');
  509. final int mode = nextLF(buf, dot2, ' ');
  510. oldId = AbbreviatedObjectId.fromString(buf, ptr, dot2 - 1);
  511. newId = AbbreviatedObjectId.fromString(buf, dot2 + 1, mode - 1);
  512. if (mode < end)
  513. newMode = oldMode = parseFileMode(mode, end);
  514. }
  515. private boolean eq(int aPtr, int aEnd, int bPtr, int bEnd) {
  516. if (aEnd - aPtr != bEnd - bPtr) {
  517. return false;
  518. }
  519. while (aPtr < aEnd) {
  520. if (buf[aPtr++] != buf[bPtr++])
  521. return false;
  522. }
  523. return true;
  524. }
  525. /**
  526. * Determine if this is a patch hunk header.
  527. *
  528. * @param buf
  529. * the buffer to scan
  530. * @param start
  531. * first position in the buffer to evaluate
  532. * @param end
  533. * last position to consider; usually the end of the buffer (
  534. * <code>buf.length</code>) or the first position on the next
  535. * line. This is only used to avoid very long runs of '@' from
  536. * killing the scan loop.
  537. * @return the number of "ancestor revisions" in the hunk header. A
  538. * traditional two-way diff ("@@ -...") returns 1; a combined diff
  539. * for a 3 way-merge returns 3. If this is not a hunk header, 0 is
  540. * returned instead.
  541. */
  542. static int isHunkHdr(final byte[] buf, final int start, final int end) {
  543. int ptr = start;
  544. while (ptr < end && buf[ptr] == '@')
  545. ptr++;
  546. if (ptr - start < 2)
  547. return 0;
  548. if (ptr == end || buf[ptr++] != ' ')
  549. return 0;
  550. if (ptr == end || buf[ptr++] != '-')
  551. return 0;
  552. return (ptr - 3) - start;
  553. }
  554. }