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.

BlameGenerator.java 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. /*
  2. * Copyright (C) 2011, 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.blame;
  44. import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
  45. import static org.eclipse.jgit.lib.FileMode.TYPE_FILE;
  46. import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
  47. import java.io.IOException;
  48. import java.util.Collection;
  49. import java.util.Collections;
  50. import org.eclipse.jgit.annotations.Nullable;
  51. import org.eclipse.jgit.blame.Candidate.BlobCandidate;
  52. import org.eclipse.jgit.blame.Candidate.ReverseCandidate;
  53. import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
  54. import org.eclipse.jgit.diff.DiffAlgorithm;
  55. import org.eclipse.jgit.diff.DiffEntry;
  56. import org.eclipse.jgit.diff.DiffEntry.ChangeType;
  57. import org.eclipse.jgit.diff.EditList;
  58. import org.eclipse.jgit.diff.HistogramDiff;
  59. import org.eclipse.jgit.diff.RawText;
  60. import org.eclipse.jgit.diff.RawTextComparator;
  61. import org.eclipse.jgit.diff.RenameDetector;
  62. import org.eclipse.jgit.internal.JGitText;
  63. import org.eclipse.jgit.lib.AnyObjectId;
  64. import org.eclipse.jgit.lib.MutableObjectId;
  65. import org.eclipse.jgit.lib.ObjectId;
  66. import org.eclipse.jgit.lib.ObjectLoader;
  67. import org.eclipse.jgit.lib.ObjectReader;
  68. import org.eclipse.jgit.lib.PersonIdent;
  69. import org.eclipse.jgit.lib.Repository;
  70. import org.eclipse.jgit.revwalk.RevCommit;
  71. import org.eclipse.jgit.revwalk.RevFlag;
  72. import org.eclipse.jgit.revwalk.RevWalk;
  73. import org.eclipse.jgit.treewalk.TreeWalk;
  74. import org.eclipse.jgit.treewalk.filter.PathFilter;
  75. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  76. /**
  77. * Generate author information for lines based on a provided file.
  78. * <p>
  79. * Applications that want a simple one-shot computation of blame for a file
  80. * should use {@link #computeBlameResult()} to prepare the entire result in one
  81. * method call. This may block for significant time as the history of the
  82. * repository must be traversed until information is gathered for every line.
  83. * <p>
  84. * Applications that want more incremental update behavior may use either the
  85. * raw {@link #next()} streaming approach supported by this class, or construct
  86. * a {@link BlameResult} using {@link BlameResult#create(BlameGenerator)} and
  87. * incrementally construct the result with {@link BlameResult#computeNext()}.
  88. * <p>
  89. * This class is not thread-safe.
  90. * <p>
  91. * An instance of BlameGenerator can only be used once. To blame multiple files
  92. * the application must create a new BlameGenerator.
  93. * <p>
  94. * During blame processing there are two files involved:
  95. * <ul>
  96. * <li>result - The file whose lines are being examined. This is the revision
  97. * the user is trying to view blame/annotation information alongside of.</li>
  98. * <li>source - The file that was blamed with supplying one or more lines of
  99. * data into result. The source may be a different file path (due to copy or
  100. * rename). Source line numbers may differ from result line numbers due to lines
  101. * being added/removed in intermediate revisions.</li>
  102. * </ul>
  103. * <p>
  104. * The blame algorithm is implemented by initially assigning responsibility for
  105. * all lines of the result to the starting commit. A difference against the
  106. * commit's ancestor is computed, and responsibility is passed to the ancestor
  107. * commit for any lines that are common. The starting commit is blamed only for
  108. * the lines that do not appear in the ancestor, if any. The loop repeats using
  109. * the ancestor, until there are no more lines to acquire information on, or the
  110. * file's creation point is discovered in history.
  111. */
  112. public class BlameGenerator implements AutoCloseable {
  113. private final Repository repository;
  114. private final PathFilter resultPath;
  115. private final MutableObjectId idBuf;
  116. /** Revision pool used to acquire commits from. */
  117. private RevWalk revPool;
  118. /** Indicates the commit was put into the queue at least once. */
  119. private RevFlag SEEN;
  120. private ObjectReader reader;
  121. private TreeWalk treeWalk;
  122. private DiffAlgorithm diffAlgorithm = new HistogramDiff();
  123. private RawTextComparator textComparator = RawTextComparator.DEFAULT;
  124. private RenameDetector renameDetector;
  125. /** Potential candidates, sorted by commit time descending. */
  126. private Candidate queue;
  127. /** Number of lines that still need to be discovered. */
  128. private int remaining;
  129. /** Blame is currently assigned to this source. */
  130. private Candidate outCandidate;
  131. private Region outRegion;
  132. /**
  133. * Create a blame generator for the repository and path (relative to
  134. * repository)
  135. *
  136. * @param repository
  137. * repository to access revision data from.
  138. * @param path
  139. * initial path of the file to start scanning (relative to the
  140. * repository).
  141. */
  142. public BlameGenerator(Repository repository, String path) {
  143. this.repository = repository;
  144. this.resultPath = PathFilter.create(path);
  145. idBuf = new MutableObjectId();
  146. setFollowFileRenames(true);
  147. initRevPool(false);
  148. remaining = -1;
  149. }
  150. private void initRevPool(boolean reverse) {
  151. if (queue != null)
  152. throw new IllegalStateException();
  153. if (revPool != null)
  154. revPool.close();
  155. if (reverse)
  156. revPool = new ReverseWalk(getRepository());
  157. else
  158. revPool = new RevWalk(getRepository());
  159. SEEN = revPool.newFlag("SEEN"); //$NON-NLS-1$
  160. reader = revPool.getObjectReader();
  161. treeWalk = new TreeWalk(reader);
  162. treeWalk.setRecursive(true);
  163. }
  164. /** @return repository being scanned for revision history. */
  165. public Repository getRepository() {
  166. return repository;
  167. }
  168. /** @return path file path being processed. */
  169. public String getResultPath() {
  170. return resultPath.getPath();
  171. }
  172. /**
  173. * Difference algorithm to use when comparing revisions.
  174. *
  175. * @param algorithm
  176. * @return {@code this}
  177. */
  178. public BlameGenerator setDiffAlgorithm(DiffAlgorithm algorithm) {
  179. diffAlgorithm = algorithm;
  180. return this;
  181. }
  182. /**
  183. * Text comparator to use when comparing revisions.
  184. *
  185. * @param comparator
  186. * @return {@code this}
  187. */
  188. public BlameGenerator setTextComparator(RawTextComparator comparator) {
  189. textComparator = comparator;
  190. return this;
  191. }
  192. /**
  193. * Enable (or disable) following file renames, on by default.
  194. * <p>
  195. * If true renames are followed using the standard FollowFilter behavior
  196. * used by RevWalk (which matches {@code git log --follow} in the C
  197. * implementation). This is not the same as copy/move detection as
  198. * implemented by the C implementation's of {@code git blame -M -C}.
  199. *
  200. * @param follow
  201. * enable following.
  202. * @return {@code this}
  203. */
  204. public BlameGenerator setFollowFileRenames(boolean follow) {
  205. if (follow)
  206. renameDetector = new RenameDetector(getRepository());
  207. else
  208. renameDetector = null;
  209. return this;
  210. }
  211. /**
  212. * Obtain the RenameDetector, allowing the application to configure its
  213. * settings for rename score and breaking behavior.
  214. *
  215. * @return the rename detector, or {@code null} if
  216. * {@code setFollowFileRenames(false)}.
  217. */
  218. @Nullable
  219. public RenameDetector getRenameDetector() {
  220. return renameDetector;
  221. }
  222. /**
  223. * Push a candidate blob onto the generator's traversal stack.
  224. * <p>
  225. * Candidates should be pushed in history order from oldest-to-newest.
  226. * Applications should push the starting commit first, then the index
  227. * revision (if the index is interesting), and finally the working tree
  228. * copy (if the working tree is interesting).
  229. *
  230. * @param description
  231. * description of the blob revision, such as "Working Tree".
  232. * @param contents
  233. * contents of the file.
  234. * @return {@code this}
  235. * @throws IOException
  236. * the repository cannot be read.
  237. */
  238. public BlameGenerator push(String description, byte[] contents)
  239. throws IOException {
  240. return push(description, new RawText(contents));
  241. }
  242. /**
  243. * Push a candidate blob onto the generator's traversal stack.
  244. * <p>
  245. * Candidates should be pushed in history order from oldest-to-newest.
  246. * Applications should push the starting commit first, then the index
  247. * revision (if the index is interesting), and finally the working tree copy
  248. * (if the working tree is interesting).
  249. *
  250. * @param description
  251. * description of the blob revision, such as "Working Tree".
  252. * @param contents
  253. * contents of the file.
  254. * @return {@code this}
  255. * @throws IOException
  256. * the repository cannot be read.
  257. */
  258. public BlameGenerator push(String description, RawText contents)
  259. throws IOException {
  260. if (description == null)
  261. description = JGitText.get().blameNotCommittedYet;
  262. BlobCandidate c = new BlobCandidate(description, resultPath);
  263. c.sourceText = contents;
  264. c.regionList = new Region(0, 0, contents.size());
  265. remaining = contents.size();
  266. push(c);
  267. return this;
  268. }
  269. /**
  270. * Push a candidate object onto the generator's traversal stack.
  271. * <p>
  272. * Candidates should be pushed in history order from oldest-to-newest.
  273. * Applications should push the starting commit first, then the index
  274. * revision (if the index is interesting), and finally the working tree copy
  275. * (if the working tree is interesting).
  276. *
  277. * @param description
  278. * description of the blob revision, such as "Working Tree".
  279. * @param id
  280. * may be a commit or a blob.
  281. * @return {@code this}
  282. * @throws IOException
  283. * the repository cannot be read.
  284. */
  285. public BlameGenerator push(String description, AnyObjectId id)
  286. throws IOException {
  287. ObjectLoader ldr = reader.open(id);
  288. if (ldr.getType() == OBJ_BLOB) {
  289. if (description == null)
  290. description = JGitText.get().blameNotCommittedYet;
  291. BlobCandidate c = new BlobCandidate(description, resultPath);
  292. c.sourceBlob = id.toObjectId();
  293. c.sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
  294. c.regionList = new Region(0, 0, c.sourceText.size());
  295. remaining = c.sourceText.size();
  296. push(c);
  297. return this;
  298. }
  299. RevCommit commit = revPool.parseCommit(id);
  300. if (!find(commit, resultPath))
  301. return this;
  302. Candidate c = new Candidate(commit, resultPath);
  303. c.sourceBlob = idBuf.toObjectId();
  304. c.loadText(reader);
  305. c.regionList = new Region(0, 0, c.sourceText.size());
  306. remaining = c.sourceText.size();
  307. push(c);
  308. return this;
  309. }
  310. /**
  311. * Configure the generator to compute reverse blame (history of deletes).
  312. * <p>
  313. * This method is expensive as it immediately runs a RevWalk over the
  314. * history spanning the expression {@code start..end} (end being more recent
  315. * than start) and then performs the equivalent operation as
  316. * {@link #push(String, AnyObjectId)} to begin blame traversal from the
  317. * commit named by {@code start} walking forwards through history until
  318. * {@code end} blaming line deletions.
  319. * <p>
  320. * A reverse blame may produce multiple sources for the same result line,
  321. * each of these is a descendant commit that removed the line, typically
  322. * this occurs when the same deletion appears in multiple side branches such
  323. * as due to a cherry-pick. Applications relying on reverse should use
  324. * {@link BlameResult} as it filters these duplicate sources and only
  325. * remembers the first (oldest) deletion.
  326. *
  327. * @param start
  328. * oldest commit to traverse from. The result file will be loaded
  329. * from this commit's tree.
  330. * @param end
  331. * most recent commit to stop traversal at. Usually an active
  332. * branch tip, tag, or HEAD.
  333. * @return {@code this}
  334. * @throws IOException
  335. * the repository cannot be read.
  336. */
  337. public BlameGenerator reverse(AnyObjectId start, AnyObjectId end)
  338. throws IOException {
  339. return reverse(start, Collections.singleton(end.toObjectId()));
  340. }
  341. /**
  342. * Configure the generator to compute reverse blame (history of deletes).
  343. * <p>
  344. * This method is expensive as it immediately runs a RevWalk over the
  345. * history spanning the expression {@code start..end} (end being more recent
  346. * than start) and then performs the equivalent operation as
  347. * {@link #push(String, AnyObjectId)} to begin blame traversal from the
  348. * commit named by {@code start} walking forwards through history until
  349. * {@code end} blaming line deletions.
  350. * <p>
  351. * A reverse blame may produce multiple sources for the same result line,
  352. * each of these is a descendant commit that removed the line, typically
  353. * this occurs when the same deletion appears in multiple side branches such
  354. * as due to a cherry-pick. Applications relying on reverse should use
  355. * {@link BlameResult} as it filters these duplicate sources and only
  356. * remembers the first (oldest) deletion.
  357. *
  358. * @param start
  359. * oldest commit to traverse from. The result file will be loaded
  360. * from this commit's tree.
  361. * @param end
  362. * most recent commits to stop traversal at. Usually an active
  363. * branch tip, tag, or HEAD.
  364. * @return {@code this}
  365. * @throws IOException
  366. * the repository cannot be read.
  367. */
  368. public BlameGenerator reverse(AnyObjectId start,
  369. Collection<? extends ObjectId> end) throws IOException {
  370. initRevPool(true);
  371. ReverseCommit result = (ReverseCommit) revPool.parseCommit(start);
  372. if (!find(result, resultPath))
  373. return this;
  374. revPool.markUninteresting(result);
  375. for (ObjectId id : end)
  376. revPool.markStart(revPool.parseCommit(id));
  377. while (revPool.next() != null) {
  378. // just pump the queue
  379. }
  380. ReverseCandidate c = new ReverseCandidate(result, resultPath);
  381. c.sourceBlob = idBuf.toObjectId();
  382. c.loadText(reader);
  383. c.regionList = new Region(0, 0, c.sourceText.size());
  384. remaining = c.sourceText.size();
  385. push(c);
  386. return this;
  387. }
  388. /**
  389. * Allocate a new RevFlag for use by the caller.
  390. *
  391. * @param name
  392. * unique name of the flag in the blame context.
  393. * @return the newly allocated flag.
  394. * @since 3.4
  395. */
  396. public RevFlag newFlag(String name) {
  397. return revPool.newFlag(name);
  398. }
  399. /**
  400. * Execute the generator in a blocking fashion until all data is ready.
  401. *
  402. * @return the complete result. Null if no file exists for the given path.
  403. * @throws IOException
  404. * the repository cannot be read.
  405. */
  406. public BlameResult computeBlameResult() throws IOException {
  407. try {
  408. BlameResult r = BlameResult.create(this);
  409. if (r != null)
  410. r.computeAll();
  411. return r;
  412. } finally {
  413. close();
  414. }
  415. }
  416. /**
  417. * Step the blame algorithm one iteration.
  418. *
  419. * @return true if the generator has found a region's source. The getSource*
  420. * and {@link #getResultStart()}, {@link #getResultEnd()} methods
  421. * can be used to inspect the region found. False if there are no
  422. * more regions to describe.
  423. * @throws IOException
  424. * repository cannot be read.
  425. */
  426. public boolean next() throws IOException {
  427. // If there is a source still pending, produce the next region.
  428. if (outRegion != null) {
  429. Region r = outRegion;
  430. remaining -= r.length;
  431. if (r.next != null) {
  432. outRegion = r.next;
  433. return true;
  434. }
  435. if (outCandidate.queueNext != null)
  436. return result(outCandidate.queueNext);
  437. outCandidate = null;
  438. outRegion = null;
  439. }
  440. // If there are no lines remaining, the entire result is done,
  441. // even if there are revisions still available for the path.
  442. if (remaining == 0)
  443. return done();
  444. for (;;) {
  445. Candidate n = pop();
  446. if (n == null)
  447. return done();
  448. int pCnt = n.getParentCount();
  449. if (pCnt == 1) {
  450. if (processOne(n))
  451. return true;
  452. } else if (1 < pCnt) {
  453. if (processMerge(n))
  454. return true;
  455. } else if (n instanceof ReverseCandidate) {
  456. // Do not generate a tip of a reverse. The region
  457. // survives and should not appear to be deleted.
  458. } else /* if (pCnt == 0) */{
  459. // Root commit, with at least one surviving region.
  460. // Assign the remaining blame here.
  461. return result(n);
  462. }
  463. }
  464. }
  465. private boolean done() {
  466. close();
  467. return false;
  468. }
  469. private boolean result(Candidate n) throws IOException {
  470. n.beginResult(revPool);
  471. outCandidate = n;
  472. outRegion = n.regionList;
  473. return true;
  474. }
  475. private boolean reverseResult(Candidate parent, Candidate source)
  476. throws IOException {
  477. // On a reverse blame present the application the parent
  478. // (as this is what did the removals), however the region
  479. // list to enumerate is the source's surviving list.
  480. Candidate res = parent.copy(parent.sourceCommit);
  481. res.regionList = source.regionList;
  482. return result(res);
  483. }
  484. private Candidate pop() {
  485. Candidate n = queue;
  486. if (n != null) {
  487. queue = n.queueNext;
  488. n.queueNext = null;
  489. }
  490. return n;
  491. }
  492. private void push(BlobCandidate toInsert) {
  493. Candidate c = queue;
  494. if (c != null) {
  495. c.remove(SEEN); // will be pushed by toInsert
  496. c.regionList = null;
  497. toInsert.parent = c;
  498. }
  499. queue = toInsert;
  500. }
  501. private void push(Candidate toInsert) {
  502. if (toInsert.has(SEEN)) {
  503. // We have already added a Candidate for this commit to the queue,
  504. // this can happen if the commit is a merge base for two or more
  505. // parallel branches that were merged together.
  506. //
  507. // It is likely the candidate was not yet processed. The queue
  508. // sorts descending by commit time and usually descendant commits
  509. // have higher timestamps than the ancestors.
  510. //
  511. // Find the existing candidate and merge the new candidate's
  512. // region list into it.
  513. for (Candidate p = queue; p != null; p = p.queueNext) {
  514. if (p.canMergeRegions(toInsert)) {
  515. p.mergeRegions(toInsert);
  516. return;
  517. }
  518. }
  519. }
  520. toInsert.add(SEEN);
  521. // Insert into the queue using descending commit time, so
  522. // the most recent commit will pop next.
  523. int time = toInsert.getTime();
  524. Candidate n = queue;
  525. if (n == null || time >= n.getTime()) {
  526. toInsert.queueNext = n;
  527. queue = toInsert;
  528. return;
  529. }
  530. for (Candidate p = n;; p = n) {
  531. n = p.queueNext;
  532. if (n == null || time >= n.getTime()) {
  533. toInsert.queueNext = n;
  534. p.queueNext = toInsert;
  535. return;
  536. }
  537. }
  538. }
  539. private boolean processOne(Candidate n) throws IOException {
  540. RevCommit parent = n.getParent(0);
  541. if (parent == null)
  542. return split(n.getNextCandidate(0), n);
  543. revPool.parseHeaders(parent);
  544. if (find(parent, n.sourcePath)) {
  545. if (idBuf.equals(n.sourceBlob))
  546. return blameEntireRegionOnParent(n, parent);
  547. return splitBlameWithParent(n, parent);
  548. }
  549. if (n.sourceCommit == null)
  550. return result(n);
  551. DiffEntry r = findRename(parent, n.sourceCommit, n.sourcePath);
  552. if (r == null)
  553. return result(n);
  554. if (0 == r.getOldId().prefixCompare(n.sourceBlob)) {
  555. // A 100% rename without any content change can also
  556. // skip directly to the parent.
  557. n.sourceCommit = parent;
  558. n.sourcePath = PathFilter.create(r.getOldPath());
  559. push(n);
  560. return false;
  561. }
  562. Candidate next = n.create(parent, PathFilter.create(r.getOldPath()));
  563. next.sourceBlob = r.getOldId().toObjectId();
  564. next.renameScore = r.getScore();
  565. next.loadText(reader);
  566. return split(next, n);
  567. }
  568. private boolean blameEntireRegionOnParent(Candidate n, RevCommit parent) {
  569. // File was not modified, blame parent.
  570. n.sourceCommit = parent;
  571. push(n);
  572. return false;
  573. }
  574. private boolean splitBlameWithParent(Candidate n, RevCommit parent)
  575. throws IOException {
  576. Candidate next = n.create(parent, n.sourcePath);
  577. next.sourceBlob = idBuf.toObjectId();
  578. next.loadText(reader);
  579. return split(next, n);
  580. }
  581. private boolean split(Candidate parent, Candidate source)
  582. throws IOException {
  583. EditList editList = diffAlgorithm.diff(textComparator,
  584. parent.sourceText, source.sourceText);
  585. if (editList.isEmpty()) {
  586. // Ignoring whitespace (or some other special comparator) can
  587. // cause non-identical blobs to have an empty edit list. In
  588. // a case like this push the parent alone.
  589. parent.regionList = source.regionList;
  590. push(parent);
  591. return false;
  592. }
  593. parent.takeBlame(editList, source);
  594. if (parent.regionList != null)
  595. push(parent);
  596. if (source.regionList != null) {
  597. if (source instanceof ReverseCandidate)
  598. return reverseResult(parent, source);
  599. return result(source);
  600. }
  601. return false;
  602. }
  603. private boolean processMerge(Candidate n) throws IOException {
  604. int pCnt = n.getParentCount();
  605. // If any single parent exactly matches the merge, follow only
  606. // that one parent through history.
  607. ObjectId[] ids = null;
  608. for (int pIdx = 0; pIdx < pCnt; pIdx++) {
  609. RevCommit parent = n.getParent(pIdx);
  610. revPool.parseHeaders(parent);
  611. if (!find(parent, n.sourcePath))
  612. continue;
  613. if (!(n instanceof ReverseCandidate) && idBuf.equals(n.sourceBlob))
  614. return blameEntireRegionOnParent(n, parent);
  615. if (ids == null)
  616. ids = new ObjectId[pCnt];
  617. ids[pIdx] = idBuf.toObjectId();
  618. }
  619. // If rename detection is enabled, search for any relevant names.
  620. DiffEntry[] renames = null;
  621. if (renameDetector != null) {
  622. renames = new DiffEntry[pCnt];
  623. for (int pIdx = 0; pIdx < pCnt; pIdx++) {
  624. RevCommit parent = n.getParent(pIdx);
  625. if (ids != null && ids[pIdx] != null)
  626. continue;
  627. DiffEntry r = findRename(parent, n.sourceCommit, n.sourcePath);
  628. if (r == null)
  629. continue;
  630. if (n instanceof ReverseCandidate) {
  631. if (ids == null)
  632. ids = new ObjectId[pCnt];
  633. ids[pCnt] = r.getOldId().toObjectId();
  634. } else if (0 == r.getOldId().prefixCompare(n.sourceBlob)) {
  635. // A 100% rename without any content change can also
  636. // skip directly to the parent. Note this bypasses an
  637. // earlier parent that had the path (above) but did not
  638. // have an exact content match. For performance reasons
  639. // we choose to follow the one parent over trying to do
  640. // possibly both parents.
  641. n.sourcePath = PathFilter.create(r.getOldPath());
  642. return blameEntireRegionOnParent(n, parent);
  643. }
  644. renames[pIdx] = r;
  645. }
  646. }
  647. // Construct the candidate for each parent.
  648. Candidate[] parents = new Candidate[pCnt];
  649. for (int pIdx = 0; pIdx < pCnt; pIdx++) {
  650. RevCommit parent = n.getParent(pIdx);
  651. Candidate p;
  652. if (renames != null && renames[pIdx] != null) {
  653. p = n.create(parent,
  654. PathFilter.create(renames[pIdx].getOldPath()));
  655. p.renameScore = renames[pIdx].getScore();
  656. p.sourceBlob = renames[pIdx].getOldId().toObjectId();
  657. } else if (ids != null && ids[pIdx] != null) {
  658. p = n.create(parent, n.sourcePath);
  659. p.sourceBlob = ids[pIdx];
  660. } else {
  661. continue;
  662. }
  663. EditList editList;
  664. if (n instanceof ReverseCandidate
  665. && p.sourceBlob.equals(n.sourceBlob)) {
  666. // This special case happens on ReverseCandidate forks.
  667. p.sourceText = n.sourceText;
  668. editList = new EditList(0);
  669. } else {
  670. p.loadText(reader);
  671. editList = diffAlgorithm.diff(textComparator,
  672. p.sourceText, n.sourceText);
  673. }
  674. if (editList.isEmpty()) {
  675. // Ignoring whitespace (or some other special comparator) can
  676. // cause non-identical blobs to have an empty edit list. In
  677. // a case like this push the parent alone.
  678. if (n instanceof ReverseCandidate) {
  679. parents[pIdx] = p;
  680. continue;
  681. }
  682. p.regionList = n.regionList;
  683. n.regionList = null;
  684. parents[pIdx] = p;
  685. break;
  686. }
  687. p.takeBlame(editList, n);
  688. // Only remember this parent candidate if there is at least
  689. // one region that was blamed on the parent.
  690. if (p.regionList != null) {
  691. // Reverse blame requires inverting the regions. This puts
  692. // the regions the parent deleted from us into the parent,
  693. // and retains the common regions to look at other parents
  694. // for deletions.
  695. if (n instanceof ReverseCandidate) {
  696. Region r = p.regionList;
  697. p.regionList = n.regionList;
  698. n.regionList = r;
  699. }
  700. parents[pIdx] = p;
  701. }
  702. }
  703. if (n instanceof ReverseCandidate) {
  704. // On a reverse blame report all deletions found in the children,
  705. // and pass on to them a copy of our region list.
  706. Candidate resultHead = null;
  707. Candidate resultTail = null;
  708. for (int pIdx = 0; pIdx < pCnt; pIdx++) {
  709. Candidate p = parents[pIdx];
  710. if (p == null)
  711. continue;
  712. if (p.regionList != null) {
  713. Candidate r = p.copy(p.sourceCommit);
  714. if (resultTail != null) {
  715. resultTail.queueNext = r;
  716. resultTail = r;
  717. } else {
  718. resultHead = r;
  719. resultTail = r;
  720. }
  721. }
  722. if (n.regionList != null) {
  723. p.regionList = n.regionList.deepCopy();
  724. push(p);
  725. }
  726. }
  727. if (resultHead != null)
  728. return result(resultHead);
  729. return false;
  730. }
  731. // Push any parents that are still candidates.
  732. for (int pIdx = 0; pIdx < pCnt; pIdx++) {
  733. if (parents[pIdx] != null)
  734. push(parents[pIdx]);
  735. }
  736. if (n.regionList != null)
  737. return result(n);
  738. return false;
  739. }
  740. /**
  741. * Get the revision blamed for the current region.
  742. * <p>
  743. * The source commit may be null if the line was blamed to an uncommitted
  744. * revision, such as the working tree copy, or during a reverse blame if the
  745. * line survives to the end revision (e.g. the branch tip).
  746. *
  747. * @return current revision being blamed.
  748. */
  749. public RevCommit getSourceCommit() {
  750. return outCandidate.sourceCommit;
  751. }
  752. /** @return current author being blamed. */
  753. public PersonIdent getSourceAuthor() {
  754. return outCandidate.getAuthor();
  755. }
  756. /** @return current committer being blamed. */
  757. public PersonIdent getSourceCommitter() {
  758. RevCommit c = getSourceCommit();
  759. return c != null ? c.getCommitterIdent() : null;
  760. }
  761. /** @return path of the file being blamed. */
  762. public String getSourcePath() {
  763. return outCandidate.sourcePath.getPath();
  764. }
  765. /** @return rename score if a rename occurred in {@link #getSourceCommit}. */
  766. public int getRenameScore() {
  767. return outCandidate.renameScore;
  768. }
  769. /**
  770. * @return first line of the source data that has been blamed for the
  771. * current region. This is line number of where the region was added
  772. * during {@link #getSourceCommit()} in file
  773. * {@link #getSourcePath()}.
  774. */
  775. public int getSourceStart() {
  776. return outRegion.sourceStart;
  777. }
  778. /**
  779. * @return one past the range of the source data that has been blamed for
  780. * the current region. This is line number of where the region was
  781. * added during {@link #getSourceCommit()} in file
  782. * {@link #getSourcePath()}.
  783. */
  784. public int getSourceEnd() {
  785. Region r = outRegion;
  786. return r.sourceStart + r.length;
  787. }
  788. /**
  789. * @return first line of the result that {@link #getSourceCommit()} has been
  790. * blamed for providing. Line numbers use 0 based indexing.
  791. */
  792. public int getResultStart() {
  793. return outRegion.resultStart;
  794. }
  795. /**
  796. * @return one past the range of the result that {@link #getSourceCommit()}
  797. * has been blamed for providing. Line numbers use 0 based indexing.
  798. * Because a source cannot be blamed for an empty region of the
  799. * result, {@link #getResultEnd()} is always at least one larger
  800. * than {@link #getResultStart()}.
  801. */
  802. public int getResultEnd() {
  803. Region r = outRegion;
  804. return r.resultStart + r.length;
  805. }
  806. /**
  807. * @return number of lines in the current region being blamed to
  808. * {@link #getSourceCommit()}. This is always the value of the
  809. * expression {@code getResultEnd() - getResultStart()}, but also
  810. * {@code getSourceEnd() - getSourceStart()}.
  811. */
  812. public int getRegionLength() {
  813. return outRegion.length;
  814. }
  815. /**
  816. * @return complete contents of the source file blamed for the current
  817. * output region. This is the contents of {@link #getSourcePath()}
  818. * within {@link #getSourceCommit()}. The source contents is
  819. * temporarily available as an artifact of the blame algorithm. Most
  820. * applications will want the result contents for display to users.
  821. */
  822. public RawText getSourceContents() {
  823. return outCandidate.sourceText;
  824. }
  825. /**
  826. * @return complete file contents of the result file blame is annotating.
  827. * This value is accessible only after being configured and only
  828. * immediately before the first call to {@link #next()}. Returns
  829. * null if the path does not exist.
  830. * @throws IOException
  831. * repository cannot be read.
  832. * @throws IllegalStateException
  833. * {@link #next()} has already been invoked.
  834. */
  835. public RawText getResultContents() throws IOException {
  836. return queue != null ? queue.sourceText : null;
  837. }
  838. /**
  839. * Release the current blame session.
  840. *
  841. * @since 4.0
  842. */
  843. @Override
  844. public void close() {
  845. revPool.close();
  846. queue = null;
  847. outCandidate = null;
  848. outRegion = null;
  849. }
  850. private boolean find(RevCommit commit, PathFilter path) throws IOException {
  851. treeWalk.setFilter(path);
  852. treeWalk.reset(commit.getTree());
  853. if (treeWalk.next() && isFile(treeWalk.getRawMode(0))) {
  854. treeWalk.getObjectId(idBuf, 0);
  855. return true;
  856. }
  857. return false;
  858. }
  859. private static final boolean isFile(int rawMode) {
  860. return (rawMode & TYPE_MASK) == TYPE_FILE;
  861. }
  862. private DiffEntry findRename(RevCommit parent, RevCommit commit,
  863. PathFilter path) throws IOException {
  864. if (renameDetector == null)
  865. return null;
  866. treeWalk.setFilter(TreeFilter.ANY_DIFF);
  867. treeWalk.reset(parent.getTree(), commit.getTree());
  868. renameDetector.reset();
  869. renameDetector.addAll(DiffEntry.scan(treeWalk));
  870. for (DiffEntry ent : renameDetector.compute()) {
  871. if (isRename(ent) && ent.getNewPath().equals(path.getPath()))
  872. return ent;
  873. }
  874. return null;
  875. }
  876. private static boolean isRename(DiffEntry ent) {
  877. return ent.getChangeType() == ChangeType.RENAME
  878. || ent.getChangeType() == ChangeType.COPY;
  879. }
  880. }