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.

Candidate.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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 java.io.IOException;
  45. import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
  46. import org.eclipse.jgit.diff.Edit;
  47. import org.eclipse.jgit.diff.EditList;
  48. import org.eclipse.jgit.diff.RawText;
  49. import org.eclipse.jgit.errors.MissingObjectException;
  50. import org.eclipse.jgit.lib.Constants;
  51. import org.eclipse.jgit.lib.ObjectId;
  52. import org.eclipse.jgit.lib.ObjectLoader;
  53. import org.eclipse.jgit.lib.ObjectReader;
  54. import org.eclipse.jgit.lib.PersonIdent;
  55. import org.eclipse.jgit.revwalk.RevCommit;
  56. import org.eclipse.jgit.revwalk.RevFlag;
  57. import org.eclipse.jgit.revwalk.RevWalk;
  58. import org.eclipse.jgit.treewalk.filter.PathFilter;
  59. /**
  60. * A source that may have supplied some (or all) of the result file.
  61. * <p>
  62. * Candidates are kept in a queue by BlameGenerator, allowing the generator to
  63. * perform a parallel search down the parents of any merges that are discovered
  64. * during the history traversal. Each candidate retains a {@link #regionList}
  65. * describing sections of the result file the candidate has taken responsibility
  66. * for either directly or indirectly through its history. Actual blame from this
  67. * region list will be assigned to the candidate when its ancestor commit(s) are
  68. * themselves converted into Candidate objects and the ancestor's candidate uses
  69. * {@link #takeBlame(EditList, Candidate)} to accept responsibility for sections
  70. * of the result.
  71. */
  72. class Candidate {
  73. /** Next candidate in the candidate queue. */
  74. Candidate queueNext;
  75. /** Commit being considered (or blamed, depending on state). */
  76. RevCommit sourceCommit;
  77. /** Path of the candidate file in {@link #sourceCommit}. */
  78. PathFilter sourcePath;
  79. /** Unique name of the candidate blob in {@link #sourceCommit}. */
  80. ObjectId sourceBlob;
  81. /** Complete contents of the file in {@link #sourceCommit}. */
  82. RawText sourceText;
  83. /**
  84. * Chain of regions this candidate may be blamed for.
  85. * <p>
  86. * This list is always kept sorted by resultStart order, making it simple to
  87. * merge-join with the sorted EditList during blame assignment.
  88. */
  89. Region regionList;
  90. /**
  91. * Score assigned to the rename to this candidate.
  92. * <p>
  93. * Consider the history "A<-B<-C". If the result file S in C was renamed to
  94. * R in B, the rename score for this rename will be held in this field by
  95. * the candidate object for B. By storing the score with B, the application
  96. * can see what the rename score was as it makes the transition from C/S to
  97. * B/R. This may seem backwards since it was C that performed the rename,
  98. * but the application doesn't learn about path R until B.
  99. */
  100. int renameScore;
  101. Candidate(RevCommit commit, PathFilter path) {
  102. sourceCommit = commit;
  103. sourcePath = path;
  104. }
  105. void beginResult(RevWalk rw) throws MissingObjectException, IOException {
  106. rw.parseBody(sourceCommit);
  107. }
  108. int getParentCount() {
  109. return sourceCommit.getParentCount();
  110. }
  111. RevCommit getParent(int idx) {
  112. return sourceCommit.getParent(idx);
  113. }
  114. Candidate getNextCandidate(@SuppressWarnings("unused") int idx) {
  115. return null;
  116. }
  117. boolean has(RevFlag flag) {
  118. return sourceCommit.has(flag);
  119. }
  120. void add(RevFlag flag) {
  121. sourceCommit.add(flag);
  122. }
  123. void remove(RevFlag flag) {
  124. sourceCommit.remove(flag);
  125. }
  126. int getTime() {
  127. return sourceCommit.getCommitTime();
  128. }
  129. PersonIdent getAuthor() {
  130. return sourceCommit.getAuthorIdent();
  131. }
  132. Candidate create(RevCommit commit, PathFilter path) {
  133. return new Candidate(commit, path);
  134. }
  135. Candidate copy(RevCommit commit) {
  136. Candidate r = create(commit, sourcePath);
  137. r.sourceBlob = sourceBlob;
  138. r.sourceText = sourceText;
  139. r.regionList = regionList;
  140. r.renameScore = renameScore;
  141. return r;
  142. }
  143. void loadText(ObjectReader reader) throws IOException {
  144. ObjectLoader ldr = reader.open(sourceBlob, Constants.OBJ_BLOB);
  145. sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
  146. }
  147. void takeBlame(EditList editList, Candidate child) {
  148. blame(editList, this, child);
  149. }
  150. private static void blame(EditList editList, Candidate a, Candidate b) {
  151. Region r = b.clearRegionList();
  152. Region aTail = null;
  153. Region bTail = null;
  154. for (int eIdx = 0; eIdx < editList.size();) {
  155. // If there are no more regions left, neither side has any
  156. // more responsibility for the result. Remaining edits can
  157. // be safely ignored.
  158. if (r == null)
  159. return;
  160. Edit e = editList.get(eIdx);
  161. // Edit ends before the next candidate region. Skip the edit.
  162. if (e.getEndB() <= r.sourceStart) {
  163. eIdx++;
  164. continue;
  165. }
  166. // Next candidate region starts before the edit. Assign some
  167. // of the blame onto A, but possibly split and also on B.
  168. if (r.sourceStart < e.getBeginB()) {
  169. int d = e.getBeginB() - r.sourceStart;
  170. if (r.length <= d) {
  171. // Pass the blame for this region onto A.
  172. Region next = r.next;
  173. r.sourceStart = e.getBeginA() - d;
  174. aTail = add(aTail, a, r);
  175. r = next;
  176. continue;
  177. }
  178. // Split the region and assign some to A, some to B.
  179. aTail = add(aTail, a, r.splitFirst(e.getBeginA() - d, d));
  180. r.slideAndShrink(d);
  181. }
  182. // At this point e.getBeginB() <= r.sourceStart.
  183. // An empty edit on the B side isn't relevant to this split,
  184. // as it does not overlap any candidate region.
  185. if (e.getLengthB() == 0) {
  186. eIdx++;
  187. continue;
  188. }
  189. // If the region ends before the edit, blame on B.
  190. int rEnd = r.sourceStart + r.length;
  191. if (rEnd <= e.getEndB()) {
  192. Region next = r.next;
  193. bTail = add(bTail, b, r);
  194. r = next;
  195. if (rEnd == e.getEndB())
  196. eIdx++;
  197. continue;
  198. }
  199. // This region extends beyond the edit. Blame the first
  200. // half of the region on B, and process the rest after.
  201. int len = e.getEndB() - r.sourceStart;
  202. bTail = add(bTail, b, r.splitFirst(r.sourceStart, len));
  203. r.slideAndShrink(len);
  204. eIdx++;
  205. }
  206. if (r == null)
  207. return;
  208. // For any remaining region, pass the blame onto A after shifting
  209. // the source start to account for the difference between the two.
  210. Edit e = editList.get(editList.size() - 1);
  211. int endB = e.getEndB();
  212. int d = endB - e.getEndA();
  213. if (aTail == null)
  214. a.regionList = r;
  215. else
  216. aTail.next = r;
  217. do {
  218. if (endB <= r.sourceStart)
  219. r.sourceStart -= d;
  220. r = r.next;
  221. } while (r != null);
  222. }
  223. private static Region add(Region aTail, Candidate a, Region n) {
  224. // If there is no region on the list, use only this one.
  225. if (aTail == null) {
  226. a.regionList = n;
  227. n.next = null;
  228. return n;
  229. }
  230. // If the prior region ends exactly where the new region begins
  231. // in both the result and the source, combine these together into
  232. // one contiguous region. This occurs when intermediate commits
  233. // have inserted and deleted lines in the middle of a region. Try
  234. // to report this region as a single region to the application,
  235. // rather than in fragments.
  236. if (aTail.resultStart + aTail.length == n.resultStart
  237. && aTail.sourceStart + aTail.length == n.sourceStart) {
  238. aTail.length += n.length;
  239. return aTail;
  240. }
  241. // Append the region onto the end of the list.
  242. aTail.next = n;
  243. n.next = null;
  244. return n;
  245. }
  246. private Region clearRegionList() {
  247. Region r = regionList;
  248. regionList = null;
  249. return r;
  250. }
  251. boolean canMergeRegions(Candidate other) {
  252. return sourceCommit == other.sourceCommit
  253. && sourcePath.getPath().equals(other.sourcePath.getPath());
  254. }
  255. void mergeRegions(Candidate other) {
  256. // regionList is always sorted by resultStart. Merge join two
  257. // linked lists, preserving the ordering. Combine neighboring
  258. // regions to reduce the number of results seen by callers.
  259. Region a = clearRegionList();
  260. Region b = other.clearRegionList();
  261. Region t = null;
  262. while (a != null && b != null) {
  263. if (a.resultStart < b.resultStart) {
  264. Region n = a.next;
  265. t = add(t, this, a);
  266. a = n;
  267. } else {
  268. Region n = b.next;
  269. t = add(t, this, b);
  270. b = n;
  271. }
  272. }
  273. if (a != null) {
  274. Region n = a.next;
  275. t = add(t, this, a);
  276. t.next = n;
  277. } else /* b != null */{
  278. Region n = b.next;
  279. t = add(t, this, b);
  280. t.next = n;
  281. }
  282. }
  283. @SuppressWarnings("nls")
  284. @Override
  285. public String toString() {
  286. StringBuilder r = new StringBuilder();
  287. r.append("Candidate[");
  288. r.append(sourcePath.getPath());
  289. if (sourceCommit != null)
  290. r.append(" @ ").append(sourceCommit.abbreviate(6).name());
  291. if (regionList != null)
  292. r.append(" regions:").append(regionList);
  293. r.append("]");
  294. return r.toString();
  295. }
  296. /**
  297. * Special candidate type used for reverse blame.
  298. * <p>
  299. * Reverse blame inverts the commit history graph to follow from a commit to
  300. * its descendant children, rather than the normal history direction of
  301. * child to parent. These types require a {@link ReverseCommit} which keeps
  302. * children pointers, allowing reverse navigation of history.
  303. */
  304. static final class ReverseCandidate extends Candidate {
  305. ReverseCandidate(ReverseCommit commit, PathFilter path) {
  306. super(commit, path);
  307. }
  308. @Override
  309. int getParentCount() {
  310. return ((ReverseCommit) sourceCommit).getChildCount();
  311. }
  312. @Override
  313. RevCommit getParent(int idx) {
  314. return ((ReverseCommit) sourceCommit).getChild(idx);
  315. }
  316. @Override
  317. int getTime() {
  318. // Invert the timestamp so newer dates sort older.
  319. return -sourceCommit.getCommitTime();
  320. }
  321. @Override
  322. Candidate create(RevCommit commit, PathFilter path) {
  323. return new ReverseCandidate((ReverseCommit) commit, path);
  324. }
  325. @Override
  326. public String toString() {
  327. return "Reverse" + super.toString(); //$NON-NLS-1$
  328. }
  329. }
  330. /**
  331. * Candidate loaded from a file source, and not a commit.
  332. * <p>
  333. * The {@link Candidate#sourceCommit} field is always null on this type of
  334. * candidate. Instead history traversal follows the single {@link #parent}
  335. * field to discover the next Candidate. Often this is a normal Candidate
  336. * type that has a valid sourceCommit.
  337. */
  338. static final class BlobCandidate extends Candidate {
  339. /**
  340. * Next candidate to pass blame onto.
  341. * <p>
  342. * When computing the differences that this candidate introduced to the
  343. * file content, the parent's sourceText is used as the base.
  344. */
  345. Candidate parent;
  346. /** Author name to refer to this blob with. */
  347. String description;
  348. BlobCandidate(String name, PathFilter path) {
  349. super(null, path);
  350. description = name;
  351. }
  352. @Override
  353. void beginResult(RevWalk rw) {
  354. // Blob candidates have nothing to prepare.
  355. }
  356. @Override
  357. int getParentCount() {
  358. return parent != null ? 1 : 0;
  359. }
  360. @Override
  361. RevCommit getParent(int idx) {
  362. return null;
  363. }
  364. @Override
  365. Candidate getNextCandidate(int idx) {
  366. return parent;
  367. }
  368. @Override
  369. boolean has(RevFlag flag) {
  370. return true; // Pretend flag was added; sourceCommit is null.
  371. }
  372. @Override
  373. void add(RevFlag flag) {
  374. // Do nothing, sourceCommit is null.
  375. }
  376. @Override
  377. void remove(RevFlag flag) {
  378. // Do nothing, sourceCommit is null.
  379. }
  380. @Override
  381. int getTime() {
  382. return Integer.MAX_VALUE;
  383. }
  384. @Override
  385. PersonIdent getAuthor() {
  386. return new PersonIdent(description, ""); //$NON-NLS-1$
  387. }
  388. }
  389. }