Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

RevCommit.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /*
  2. * Copyright (C) 2008-2009, Google Inc.
  3. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  4. * and other copyright owners as documented in the project's IP log.
  5. *
  6. * This program and the accompanying materials are made available
  7. * under the terms of the Eclipse Distribution License v1.0 which
  8. * accompanies this distribution, is reproduced below, and is
  9. * available at http://www.eclipse.org/org/documents/edl-v10.php
  10. *
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or
  14. * without modification, are permitted provided that the following
  15. * conditions are met:
  16. *
  17. * - Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * - Redistributions in binary form must reproduce the above
  21. * copyright notice, this list of conditions and the following
  22. * disclaimer in the documentation and/or other materials provided
  23. * with the distribution.
  24. *
  25. * - Neither the name of the Eclipse Foundation, Inc. nor the
  26. * names of its contributors may be used to endorse or promote
  27. * products derived from this software without specific prior
  28. * written permission.
  29. *
  30. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. */
  44. package org.eclipse.jgit.revwalk;
  45. import java.io.IOException;
  46. import java.nio.charset.Charset;
  47. import java.util.ArrayList;
  48. import java.util.Collections;
  49. import java.util.List;
  50. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  51. import org.eclipse.jgit.errors.MissingObjectException;
  52. import org.eclipse.jgit.lib.AnyObjectId;
  53. import org.eclipse.jgit.lib.Commit;
  54. import org.eclipse.jgit.lib.Constants;
  55. import org.eclipse.jgit.lib.MutableObjectId;
  56. import org.eclipse.jgit.lib.PersonIdent;
  57. import org.eclipse.jgit.util.RawParseUtils;
  58. /** A commit reference to a commit in the DAG. */
  59. public class RevCommit extends RevObject {
  60. static final RevCommit[] NO_PARENTS = {};
  61. private RevTree tree;
  62. RevCommit[] parents;
  63. int commitTime; // An int here for performance, overflows in 2038
  64. int inDegree;
  65. private byte[] buffer;
  66. /**
  67. * Create a new commit reference.
  68. *
  69. * @param id
  70. * object name for the commit.
  71. */
  72. protected RevCommit(final AnyObjectId id) {
  73. super(id);
  74. }
  75. @Override
  76. void parseHeaders(final RevWalk walk) throws MissingObjectException,
  77. IncorrectObjectTypeException, IOException {
  78. parseCanonical(walk, loadCanonical(walk));
  79. }
  80. @Override
  81. void parseBody(final RevWalk walk) throws MissingObjectException,
  82. IncorrectObjectTypeException, IOException {
  83. if (buffer == null) {
  84. buffer = loadCanonical(walk);
  85. if ((flags & PARSED) == 0)
  86. parseCanonical(walk, buffer);
  87. }
  88. }
  89. void parseCanonical(final RevWalk walk, final byte[] raw) {
  90. final MutableObjectId idBuffer = walk.idBuffer;
  91. idBuffer.fromString(raw, 5);
  92. tree = walk.lookupTree(idBuffer);
  93. int ptr = 46;
  94. if (parents == null) {
  95. RevCommit[] pList = new RevCommit[1];
  96. int nParents = 0;
  97. for (;;) {
  98. if (raw[ptr] != 'p')
  99. break;
  100. idBuffer.fromString(raw, ptr + 7);
  101. final RevCommit p = walk.lookupCommit(idBuffer);
  102. if (nParents == 0)
  103. pList[nParents++] = p;
  104. else if (nParents == 1) {
  105. pList = new RevCommit[] { pList[0], p };
  106. nParents = 2;
  107. } else {
  108. if (pList.length <= nParents) {
  109. RevCommit[] old = pList;
  110. pList = new RevCommit[pList.length + 32];
  111. System.arraycopy(old, 0, pList, 0, nParents);
  112. }
  113. pList[nParents++] = p;
  114. }
  115. ptr += 48;
  116. }
  117. if (nParents != pList.length) {
  118. RevCommit[] old = pList;
  119. pList = new RevCommit[nParents];
  120. System.arraycopy(old, 0, pList, 0, nParents);
  121. }
  122. parents = pList;
  123. }
  124. // extract time from "committer "
  125. ptr = RawParseUtils.committer(raw, ptr);
  126. if (ptr > 0) {
  127. ptr = RawParseUtils.nextLF(raw, ptr, '>');
  128. // In 2038 commitTime will overflow unless it is changed to long.
  129. commitTime = RawParseUtils.parseBase10(raw, ptr, null);
  130. }
  131. if (walk.isRetainBody())
  132. buffer = raw;
  133. flags |= PARSED;
  134. }
  135. @Override
  136. public final int getType() {
  137. return Constants.OBJ_COMMIT;
  138. }
  139. static void carryFlags(RevCommit c, final int carry) {
  140. for (;;) {
  141. final RevCommit[] pList = c.parents;
  142. if (pList == null)
  143. return;
  144. final int n = pList.length;
  145. if (n == 0)
  146. return;
  147. for (int i = 1; i < n; i++) {
  148. final RevCommit p = pList[i];
  149. if ((p.flags & carry) == carry)
  150. continue;
  151. p.flags |= carry;
  152. carryFlags(p, carry);
  153. }
  154. c = pList[0];
  155. if ((c.flags & carry) == carry)
  156. return;
  157. c.flags |= carry;
  158. }
  159. }
  160. /**
  161. * Carry a RevFlag set on this commit to its parents.
  162. * <p>
  163. * If this commit is parsed, has parents, and has the supplied flag set on
  164. * it we automatically add it to the parents, grand-parents, and so on until
  165. * an unparsed commit or a commit with no parents is discovered. This
  166. * permits applications to force a flag through the history chain when
  167. * necessary.
  168. *
  169. * @param flag
  170. * the single flag value to carry back onto parents.
  171. */
  172. public void carry(final RevFlag flag) {
  173. final int carry = flags & flag.mask;
  174. if (carry != 0)
  175. carryFlags(this, carry);
  176. }
  177. /**
  178. * Time from the "committer " line of the buffer.
  179. *
  180. * @return time, expressed as seconds since the epoch.
  181. */
  182. public final int getCommitTime() {
  183. return commitTime;
  184. }
  185. /**
  186. * Parse this commit buffer for display.
  187. *
  188. * @param walk
  189. * revision walker owning this reference.
  190. * @return parsed commit.
  191. */
  192. public final Commit asCommit(final RevWalk walk) {
  193. return new Commit(walk.db, this, buffer);
  194. }
  195. /**
  196. * Get a reference to this commit's tree.
  197. *
  198. * @return tree of this commit.
  199. */
  200. public final RevTree getTree() {
  201. return tree;
  202. }
  203. /**
  204. * Get the number of parent commits listed in this commit.
  205. *
  206. * @return number of parents; always a positive value but can be 0.
  207. */
  208. public final int getParentCount() {
  209. return parents.length;
  210. }
  211. /**
  212. * Get the nth parent from this commit's parent list.
  213. *
  214. * @param nth
  215. * parent index to obtain. Must be in the range 0 through
  216. * {@link #getParentCount()}-1.
  217. * @return the specified parent.
  218. * @throws ArrayIndexOutOfBoundsException
  219. * an invalid parent index was specified.
  220. */
  221. public final RevCommit getParent(final int nth) {
  222. return parents[nth];
  223. }
  224. /**
  225. * Obtain an array of all parents (<b>NOTE - THIS IS NOT A COPY</b>).
  226. * <p>
  227. * This method is exposed only to provide very fast, efficient access to
  228. * this commit's parent list. Applications relying on this list should be
  229. * very careful to ensure they do not modify its contents during their use
  230. * of it.
  231. *
  232. * @return the array of parents.
  233. */
  234. public final RevCommit[] getParents() {
  235. return parents;
  236. }
  237. /**
  238. * Obtain the raw unparsed commit body (<b>NOTE - THIS IS NOT A COPY</b>).
  239. * <p>
  240. * This method is exposed only to provide very fast, efficient access to
  241. * this commit's message buffer within a RevFilter. Applications relying on
  242. * this buffer should be very careful to ensure they do not modify its
  243. * contents during their use of it.
  244. *
  245. * @return the raw unparsed commit body. This is <b>NOT A COPY</b>.
  246. * Altering the contents of this buffer may alter the walker's
  247. * knowledge of this commit, and the results it produces.
  248. */
  249. public final byte[] getRawBuffer() {
  250. return buffer;
  251. }
  252. /**
  253. * Parse the author identity from the raw buffer.
  254. * <p>
  255. * This method parses and returns the content of the author line, after
  256. * taking the commit's character set into account and decoding the author
  257. * name and email address. This method is fairly expensive and produces a
  258. * new PersonIdent instance on each invocation. Callers should invoke this
  259. * method only if they are certain they will be outputting the result, and
  260. * should cache the return value for as long as necessary to use all
  261. * information from it.
  262. * <p>
  263. * RevFilter implementations should try to use {@link RawParseUtils} to scan
  264. * the {@link #getRawBuffer()} instead, as this will allow faster evaluation
  265. * of commits.
  266. *
  267. * @return identity of the author (name, email) and the time the commit was
  268. * made by the author; null if no author line was found.
  269. */
  270. public final PersonIdent getAuthorIdent() {
  271. final byte[] raw = buffer;
  272. final int nameB = RawParseUtils.author(raw, 0);
  273. if (nameB < 0)
  274. return null;
  275. return RawParseUtils.parsePersonIdent(raw, nameB);
  276. }
  277. /**
  278. * Parse the committer identity from the raw buffer.
  279. * <p>
  280. * This method parses and returns the content of the committer line, after
  281. * taking the commit's character set into account and decoding the committer
  282. * name and email address. This method is fairly expensive and produces a
  283. * new PersonIdent instance on each invocation. Callers should invoke this
  284. * method only if they are certain they will be outputting the result, and
  285. * should cache the return value for as long as necessary to use all
  286. * information from it.
  287. * <p>
  288. * RevFilter implementations should try to use {@link RawParseUtils} to scan
  289. * the {@link #getRawBuffer()} instead, as this will allow faster evaluation
  290. * of commits.
  291. *
  292. * @return identity of the committer (name, email) and the time the commit
  293. * was made by the committer; null if no committer line was found.
  294. */
  295. public final PersonIdent getCommitterIdent() {
  296. final byte[] raw = buffer;
  297. final int nameB = RawParseUtils.committer(raw, 0);
  298. if (nameB < 0)
  299. return null;
  300. return RawParseUtils.parsePersonIdent(raw, nameB);
  301. }
  302. /**
  303. * Parse the complete commit message and decode it to a string.
  304. * <p>
  305. * This method parses and returns the message portion of the commit buffer,
  306. * after taking the commit's character set into account and decoding the
  307. * buffer using that character set. This method is a fairly expensive
  308. * operation and produces a new string on each invocation.
  309. *
  310. * @return decoded commit message as a string. Never null.
  311. */
  312. public final String getFullMessage() {
  313. final byte[] raw = buffer;
  314. final int msgB = RawParseUtils.commitMessage(raw, 0);
  315. if (msgB < 0)
  316. return "";
  317. final Charset enc = RawParseUtils.parseEncoding(raw);
  318. return RawParseUtils.decode(enc, raw, msgB, raw.length);
  319. }
  320. /**
  321. * Parse the commit message and return the first "line" of it.
  322. * <p>
  323. * The first line is everything up to the first pair of LFs. This is the
  324. * "oneline" format, suitable for output in a single line display.
  325. * <p>
  326. * This method parses and returns the message portion of the commit buffer,
  327. * after taking the commit's character set into account and decoding the
  328. * buffer using that character set. This method is a fairly expensive
  329. * operation and produces a new string on each invocation.
  330. *
  331. * @return decoded commit message as a string. Never null. The returned
  332. * string does not contain any LFs, even if the first paragraph
  333. * spanned multiple lines. Embedded LFs are converted to spaces.
  334. */
  335. public final String getShortMessage() {
  336. final byte[] raw = buffer;
  337. final int msgB = RawParseUtils.commitMessage(raw, 0);
  338. if (msgB < 0)
  339. return "";
  340. final Charset enc = RawParseUtils.parseEncoding(raw);
  341. final int msgE = RawParseUtils.endOfParagraph(raw, msgB);
  342. String str = RawParseUtils.decode(enc, raw, msgB, msgE);
  343. if (hasLF(raw, msgB, msgE))
  344. str = str.replace('\n', ' ');
  345. return str;
  346. }
  347. static boolean hasLF(final byte[] r, int b, final int e) {
  348. while (b < e)
  349. if (r[b++] == '\n')
  350. return true;
  351. return false;
  352. }
  353. /**
  354. * Determine the encoding of the commit message buffer.
  355. * <p>
  356. * Locates the "encoding" header (if present) and then returns the proper
  357. * character set to apply to this buffer to evaluate its contents as
  358. * character data.
  359. * <p>
  360. * If no encoding header is present, {@link Constants#CHARSET} is assumed.
  361. *
  362. * @return the preferred encoding of {@link #getRawBuffer()}.
  363. */
  364. public final Charset getEncoding() {
  365. return RawParseUtils.parseEncoding(buffer);
  366. }
  367. /**
  368. * Parse the footer lines (e.g. "Signed-off-by") for machine processing.
  369. * <p>
  370. * This method splits all of the footer lines out of the last paragraph of
  371. * the commit message, providing each line as a key-value pair, ordered by
  372. * the order of the line's appearance in the commit message itself.
  373. * <p>
  374. * A footer line's key must match the pattern {@code ^[A-Za-z0-9-]+:}, while
  375. * the value is free-form, but must not contain an LF. Very common keys seen
  376. * in the wild are:
  377. * <ul>
  378. * <li>{@code Signed-off-by} (agrees to Developer Certificate of Origin)
  379. * <li>{@code Acked-by} (thinks change looks sane in context)
  380. * <li>{@code Reported-by} (originally found the issue this change fixes)
  381. * <li>{@code Tested-by} (validated change fixes the issue for them)
  382. * <li>{@code CC}, {@code Cc} (copy on all email related to this change)
  383. * <li>{@code Bug} (link to project's bug tracking system)
  384. * </ul>
  385. *
  386. * @return ordered list of footer lines; empty list if no footers found.
  387. */
  388. public final List<FooterLine> getFooterLines() {
  389. final byte[] raw = buffer;
  390. int ptr = raw.length - 1;
  391. while (raw[ptr] == '\n') // trim any trailing LFs, not interesting
  392. ptr--;
  393. final int msgB = RawParseUtils.commitMessage(raw, 0);
  394. final ArrayList<FooterLine> r = new ArrayList<FooterLine>(4);
  395. final Charset enc = getEncoding();
  396. for (;;) {
  397. ptr = RawParseUtils.prevLF(raw, ptr);
  398. if (ptr <= msgB)
  399. break; // Don't parse commit headers as footer lines.
  400. final int keyStart = ptr + 2;
  401. if (raw[keyStart] == '\n')
  402. break; // Stop at first paragraph break, no footers above it.
  403. final int keyEnd = RawParseUtils.endOfFooterLineKey(raw, keyStart);
  404. if (keyEnd < 0)
  405. continue; // Not a well formed footer line, skip it.
  406. // Skip over the ': *' at the end of the key before the value.
  407. //
  408. int valStart = keyEnd + 1;
  409. while (valStart < raw.length && raw[valStart] == ' ')
  410. valStart++;
  411. // Value ends at the LF, and does not include it.
  412. //
  413. int valEnd = RawParseUtils.nextLF(raw, valStart);
  414. if (raw[valEnd - 1] == '\n')
  415. valEnd--;
  416. r.add(new FooterLine(raw, enc, keyStart, keyEnd, valStart, valEnd));
  417. }
  418. Collections.reverse(r);
  419. return r;
  420. }
  421. /**
  422. * Get the values of all footer lines with the given key.
  423. *
  424. * @param keyName
  425. * footer key to find values of, case insensitive.
  426. * @return values of footers with key of {@code keyName}, ordered by their
  427. * order of appearance. Duplicates may be returned if the same
  428. * footer appeared more than once. Empty list if no footers appear
  429. * with the specified key, or there are no footers at all.
  430. * @see #getFooterLines()
  431. */
  432. public final List<String> getFooterLines(final String keyName) {
  433. return getFooterLines(new FooterKey(keyName));
  434. }
  435. /**
  436. * Get the values of all footer lines with the given key.
  437. *
  438. * @param keyName
  439. * footer key to find values of, case insensitive.
  440. * @return values of footers with key of {@code keyName}, ordered by their
  441. * order of appearance. Duplicates may be returned if the same
  442. * footer appeared more than once. Empty list if no footers appear
  443. * with the specified key, or there are no footers at all.
  444. * @see #getFooterLines()
  445. */
  446. public final List<String> getFooterLines(final FooterKey keyName) {
  447. final List<FooterLine> src = getFooterLines();
  448. if (src.isEmpty())
  449. return Collections.emptyList();
  450. final ArrayList<String> r = new ArrayList<String>(src.size());
  451. for (final FooterLine f : src) {
  452. if (f.matches(keyName))
  453. r.add(f.getValue());
  454. }
  455. return r;
  456. }
  457. /**
  458. * Reset this commit to allow another RevWalk with the same instances.
  459. * <p>
  460. * Subclasses <b>must</b> call <code>super.reset()</code> to ensure the
  461. * basic information can be correctly cleared out.
  462. */
  463. public void reset() {
  464. inDegree = 0;
  465. }
  466. final void disposeBody() {
  467. buffer = null;
  468. }
  469. @Override
  470. public String toString() {
  471. final StringBuilder s = new StringBuilder();
  472. s.append(Constants.typeString(getType()));
  473. s.append(' ');
  474. s.append(name());
  475. s.append(' ');
  476. s.append(commitTime);
  477. s.append(' ');
  478. appendCoreFlags(s);
  479. return s.toString();
  480. }
  481. }