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

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