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.

TreeRevFilter.java 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.revwalk;
  11. import java.io.IOException;
  12. import java.util.List;
  13. import org.eclipse.jgit.diff.DiffConfig;
  14. import org.eclipse.jgit.diff.DiffEntry;
  15. import org.eclipse.jgit.diff.DiffEntry.ChangeType;
  16. import org.eclipse.jgit.diff.RenameDetector;
  17. import org.eclipse.jgit.errors.CorruptObjectException;
  18. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  19. import org.eclipse.jgit.errors.MissingObjectException;
  20. import org.eclipse.jgit.errors.StopWalkException;
  21. import org.eclipse.jgit.lib.ObjectId;
  22. import org.eclipse.jgit.revwalk.filter.RevFilter;
  23. import org.eclipse.jgit.treewalk.TreeWalk;
  24. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  25. /**
  26. * Filter applying a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} against
  27. * changed paths in each commit.
  28. * <p>
  29. * Each commit is differenced concurrently against all of its parents to look
  30. * for tree entries that are interesting to the
  31. * {@link org.eclipse.jgit.treewalk.filter.TreeFilter}.
  32. *
  33. * @since 3.5
  34. */
  35. public class TreeRevFilter extends RevFilter {
  36. private static final int PARSED = RevWalk.PARSED;
  37. private static final int UNINTERESTING = RevWalk.UNINTERESTING;
  38. private final int rewriteFlag;
  39. private final TreeWalk pathFilter;
  40. /**
  41. * Create a {@link org.eclipse.jgit.revwalk.filter.RevFilter} from a
  42. * {@link org.eclipse.jgit.treewalk.filter.TreeFilter}.
  43. *
  44. * @param walker
  45. * walker used for reading trees.
  46. * @param t
  47. * filter to compare against any changed paths in each commit. If
  48. * a {@link org.eclipse.jgit.revwalk.FollowFilter}, will be
  49. * replaced with a new filter following new paths after a rename.
  50. * @since 3.5
  51. */
  52. public TreeRevFilter(RevWalk walker, TreeFilter t) {
  53. this(walker, t, 0);
  54. }
  55. /**
  56. * Create a filter for the first phase of a parent-rewriting limited revision
  57. * walk.
  58. * <p>
  59. * This filter is ANDed to evaluate before all other filters and ties the
  60. * configured {@link TreeFilter} into the revision walking process.
  61. * <p>
  62. * If no interesting tree entries are found the commit is colored with
  63. * {@code rewriteFlag}, allowing a later pass implemented by
  64. * {@link RewriteGenerator} to remove those colored commits from the DAG.
  65. *
  66. * @see RewriteGenerator
  67. *
  68. * @param walker
  69. * walker used for reading trees.
  70. * @param t
  71. * filter to compare against any changed paths in each commit. If a
  72. * {@link FollowFilter}, will be replaced with a new filter
  73. * following new paths after a rename.
  74. * @param rewriteFlag
  75. * flag to color commits to be removed from the simplified DAT.
  76. */
  77. TreeRevFilter(RevWalk walker, TreeFilter t, int rewriteFlag) {
  78. pathFilter = new TreeWalk(walker.reader);
  79. pathFilter.setFilter(t);
  80. pathFilter.setRecursive(t.shouldBeRecursive());
  81. this.rewriteFlag = rewriteFlag;
  82. }
  83. /** {@inheritDoc} */
  84. @Override
  85. public RevFilter clone() {
  86. throw new UnsupportedOperationException();
  87. }
  88. /** {@inheritDoc} */
  89. @Override
  90. public boolean include(RevWalk walker, RevCommit c)
  91. throws StopWalkException, MissingObjectException,
  92. IncorrectObjectTypeException, IOException {
  93. // Reset the tree filter to scan this commit and parents.
  94. //
  95. RevCommit[] pList = c.parents;
  96. int nParents = pList.length;
  97. TreeWalk tw = pathFilter;
  98. ObjectId[] trees = new ObjectId[nParents + 1];
  99. for (int i = 0; i < nParents; i++) {
  100. RevCommit p = c.parents[i];
  101. if ((p.flags & PARSED) == 0) {
  102. p.parseHeaders(walker);
  103. }
  104. trees[i] = p.getTree();
  105. }
  106. trees[nParents] = c.getTree();
  107. tw.reset(trees);
  108. if (nParents == 1) {
  109. // We have exactly one parent. This is a very common case.
  110. //
  111. int chgs = 0, adds = 0;
  112. while (tw.next()) {
  113. chgs++;
  114. if (tw.getRawMode(0) == 0 && tw.getRawMode(1) != 0) {
  115. adds++;
  116. } else {
  117. break; // no point in looking at this further.
  118. }
  119. }
  120. if (chgs == 0) {
  121. // No changes, so our tree is effectively the same as
  122. // our parent tree. We pass the buck to our parent.
  123. //
  124. c.flags |= rewriteFlag;
  125. return false;
  126. }
  127. // We have interesting items, but neither of the special
  128. // cases denoted above.
  129. //
  130. if (adds > 0 && tw.getFilter() instanceof FollowFilter) {
  131. // One of the paths we care about was added in this
  132. // commit. We need to update our filter to its older
  133. // name, if we can discover it. Find out what that is.
  134. //
  135. updateFollowFilter(trees, ((FollowFilter) tw.getFilter()).cfg);
  136. }
  137. return true;
  138. } else if (nParents == 0) {
  139. // We have no parents to compare against. Consider us to be
  140. // REWRITE only if we have no paths matching our filter.
  141. //
  142. if (tw.next()) {
  143. return true;
  144. }
  145. c.flags |= rewriteFlag;
  146. return false;
  147. }
  148. // We are a merge commit. We can only be REWRITE if we are same
  149. // to _all_ parents. We may also be able to eliminate a parent if
  150. // it does not contribute changes to us. Such a parent may be an
  151. // uninteresting side branch.
  152. //
  153. int[] chgs = new int[nParents];
  154. int[] adds = new int[nParents];
  155. while (tw.next()) {
  156. int myMode = tw.getRawMode(nParents);
  157. for (int i = 0; i < nParents; i++) {
  158. int pMode = tw.getRawMode(i);
  159. if (myMode == pMode && tw.idEqual(i, nParents)) {
  160. continue;
  161. }
  162. chgs[i]++;
  163. if (pMode == 0 && myMode != 0) {
  164. adds[i]++;
  165. }
  166. }
  167. }
  168. boolean same = false;
  169. boolean diff = false;
  170. for (int i = 0; i < nParents; i++) {
  171. if (chgs[i] == 0) {
  172. // No changes, so our tree is effectively the same as
  173. // this parent tree. We pass the buck to only this one
  174. // parent commit.
  175. //
  176. RevCommit p = pList[i];
  177. if ((p.flags & UNINTERESTING) != 0) {
  178. // This parent was marked as not interesting by the
  179. // application. We should look for another parent
  180. // that is interesting.
  181. //
  182. same = true;
  183. continue;
  184. }
  185. c.flags |= rewriteFlag;
  186. c.parents = new RevCommit[] { p };
  187. return false;
  188. }
  189. if (chgs[i] == adds[i]) {
  190. // All of the differences from this parent were because we
  191. // added files that they did not have. This parent is our
  192. // "empty tree root" and thus their history is not relevant.
  193. // Cut our grandparents to be an empty list.
  194. //
  195. pList[i].parents = RevCommit.NO_PARENTS;
  196. }
  197. // We have an interesting difference relative to this parent.
  198. //
  199. diff = true;
  200. }
  201. if (diff && !same) {
  202. // We did not abort above, so we are different in at least one
  203. // way from all of our parents. We have to take the blame for
  204. // that difference.
  205. //
  206. return true;
  207. }
  208. // We are the same as all of our parents. We must keep them
  209. // as they are and allow those parents to flow into pending
  210. // for further scanning.
  211. //
  212. c.flags |= rewriteFlag;
  213. return false;
  214. }
  215. /** {@inheritDoc} */
  216. @Override
  217. public boolean requiresCommitBody() {
  218. return false;
  219. }
  220. private void updateFollowFilter(ObjectId[] trees, DiffConfig cfg)
  221. throws MissingObjectException, IncorrectObjectTypeException,
  222. CorruptObjectException, IOException {
  223. TreeWalk tw = pathFilter;
  224. FollowFilter oldFilter = (FollowFilter) tw.getFilter();
  225. tw.setFilter(TreeFilter.ANY_DIFF);
  226. tw.reset(trees);
  227. List<DiffEntry> files = DiffEntry.scan(tw);
  228. RenameDetector rd = new RenameDetector(tw.getObjectReader(), cfg);
  229. rd.addAll(files);
  230. files = rd.compute();
  231. TreeFilter newFilter = oldFilter;
  232. for (DiffEntry ent : files) {
  233. if (isRename(ent) && ent.getNewPath().equals(oldFilter.getPath())) {
  234. newFilter = FollowFilter.create(ent.getOldPath(), cfg);
  235. RenameCallback callback = oldFilter.getRenameCallback();
  236. if (callback != null) {
  237. callback.renamed(ent);
  238. // forward the callback to the new follow filter
  239. ((FollowFilter) newFilter).setRenameCallback(callback);
  240. }
  241. break;
  242. }
  243. }
  244. tw.setFilter(newFilter);
  245. }
  246. private static boolean isRename(DiffEntry ent) {
  247. return ent.getChangeType() == ChangeType.RENAME
  248. || ent.getChangeType() == ChangeType.COPY;
  249. }
  250. }