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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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(RevWalk walker, 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(RevWalk walker, TreeFilter t, int rewriteFlag) {
  111. pathFilter = new TreeWalk(walker.reader);
  112. pathFilter.setFilter(t);
  113. pathFilter.setRecursive(t.shouldBeRecursive());
  114. this.rewriteFlag = rewriteFlag;
  115. }
  116. /** {@inheritDoc} */
  117. @Override
  118. public RevFilter clone() {
  119. throw new UnsupportedOperationException();
  120. }
  121. /** {@inheritDoc} */
  122. @Override
  123. public boolean include(RevWalk walker, RevCommit c)
  124. throws StopWalkException, MissingObjectException,
  125. IncorrectObjectTypeException, IOException {
  126. // Reset the tree filter to scan this commit and parents.
  127. //
  128. RevCommit[] pList = c.parents;
  129. int nParents = pList.length;
  130. TreeWalk tw = pathFilter;
  131. ObjectId[] trees = new ObjectId[nParents + 1];
  132. for (int i = 0; i < nParents; i++) {
  133. RevCommit p = c.parents[i];
  134. if ((p.flags & PARSED) == 0) {
  135. p.parseHeaders(walker);
  136. }
  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. }
  153. if (chgs == 0) {
  154. // No changes, so our tree is effectively the same as
  155. // our parent tree. We pass the buck to our parent.
  156. //
  157. c.flags |= rewriteFlag;
  158. return false;
  159. } else {
  160. // We have interesting items, but neither of the special
  161. // cases denoted above.
  162. //
  163. if (adds > 0 && tw.getFilter() instanceof FollowFilter) {
  164. // One of the paths we care about was added in this
  165. // commit. We need to update our filter to its older
  166. // name, if we can discover it. Find out what that is.
  167. //
  168. updateFollowFilter(trees, ((FollowFilter) tw.getFilter()).cfg);
  169. }
  170. return true;
  171. }
  172. } else if (nParents == 0) {
  173. // We have no parents to compare against. Consider us to be
  174. // REWRITE only if we have no paths matching our filter.
  175. //
  176. if (tw.next()) {
  177. return true;
  178. }
  179. c.flags |= rewriteFlag;
  180. return false;
  181. }
  182. // We are a merge commit. We can only be REWRITE if we are same
  183. // to _all_ parents. We may also be able to eliminate a parent if
  184. // it does not contribute changes to us. Such a parent may be an
  185. // uninteresting side branch.
  186. //
  187. int[] chgs = new int[nParents];
  188. int[] adds = new int[nParents];
  189. while (tw.next()) {
  190. int myMode = tw.getRawMode(nParents);
  191. for (int i = 0; i < nParents; i++) {
  192. int pMode = tw.getRawMode(i);
  193. if (myMode == pMode && tw.idEqual(i, nParents)) {
  194. continue;
  195. }
  196. chgs[i]++;
  197. if (pMode == 0 && myMode != 0) {
  198. adds[i]++;
  199. }
  200. }
  201. }
  202. boolean same = false;
  203. boolean diff = false;
  204. for (int i = 0; i < nParents; i++) {
  205. if (chgs[i] == 0) {
  206. // No changes, so our tree is effectively the same as
  207. // this parent tree. We pass the buck to only this one
  208. // parent commit.
  209. //
  210. RevCommit p = pList[i];
  211. if ((p.flags & UNINTERESTING) != 0) {
  212. // This parent was marked as not interesting by the
  213. // application. We should look for another parent
  214. // that is interesting.
  215. //
  216. same = true;
  217. continue;
  218. }
  219. c.flags |= rewriteFlag;
  220. c.parents = new RevCommit[] { p };
  221. return false;
  222. }
  223. if (chgs[i] == adds[i]) {
  224. // All of the differences from this parent were because we
  225. // added files that they did not have. This parent is our
  226. // "empty tree root" and thus their history is not relevant.
  227. // Cut our grandparents to be an empty list.
  228. //
  229. pList[i].parents = RevCommit.NO_PARENTS;
  230. }
  231. // We have an interesting difference relative to this parent.
  232. //
  233. diff = true;
  234. }
  235. if (diff && !same) {
  236. // We did not abort above, so we are different in at least one
  237. // way from all of our parents. We have to take the blame for
  238. // that difference.
  239. //
  240. return true;
  241. }
  242. // We are the same as all of our parents. We must keep them
  243. // as they are and allow those parents to flow into pending
  244. // for further scanning.
  245. //
  246. c.flags |= rewriteFlag;
  247. return false;
  248. }
  249. /** {@inheritDoc} */
  250. @Override
  251. public boolean requiresCommitBody() {
  252. return false;
  253. }
  254. private void updateFollowFilter(ObjectId[] trees, DiffConfig cfg)
  255. throws MissingObjectException, IncorrectObjectTypeException,
  256. CorruptObjectException, IOException {
  257. TreeWalk tw = pathFilter;
  258. FollowFilter oldFilter = (FollowFilter) tw.getFilter();
  259. tw.setFilter(TreeFilter.ANY_DIFF);
  260. tw.reset(trees);
  261. List<DiffEntry> files = DiffEntry.scan(tw);
  262. RenameDetector rd = new RenameDetector(tw.getObjectReader(), cfg);
  263. rd.addAll(files);
  264. files = rd.compute();
  265. TreeFilter newFilter = oldFilter;
  266. for (DiffEntry ent : files) {
  267. if (isRename(ent) && ent.getNewPath().equals(oldFilter.getPath())) {
  268. newFilter = FollowFilter.create(ent.getOldPath(), cfg);
  269. RenameCallback callback = oldFilter.getRenameCallback();
  270. if (callback != null) {
  271. callback.renamed(ent);
  272. // forward the callback to the new follow filter
  273. ((FollowFilter) newFilter).setRenameCallback(callback);
  274. }
  275. break;
  276. }
  277. }
  278. tw.setFilter(newFilter);
  279. }
  280. private static boolean isRename(DiffEntry ent) {
  281. return ent.getChangeType() == ChangeType.RENAME
  282. || ent.getChangeType() == ChangeType.COPY;
  283. }
  284. }