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 11KB

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