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.8KB

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