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.

IndexDiffFilter.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /*
  2. * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  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.treewalk.filter;
  44. import java.io.IOException;
  45. import java.util.HashSet;
  46. import java.util.LinkedList;
  47. import java.util.List;
  48. import java.util.Set;
  49. import org.eclipse.jgit.dircache.DirCacheEntry;
  50. import org.eclipse.jgit.dircache.DirCacheIterator;
  51. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  52. import org.eclipse.jgit.errors.MissingObjectException;
  53. import org.eclipse.jgit.lib.FileMode;
  54. import org.eclipse.jgit.treewalk.TreeWalk;
  55. import org.eclipse.jgit.treewalk.WorkingTreeIterator;
  56. /**
  57. * A performance optimized variant of {@link TreeFilter#ANY_DIFF} which should
  58. * be used when among the walked trees there is a {@link DirCacheIterator} and a
  59. * {@link WorkingTreeIterator}. Please see the documentation of
  60. * {@link TreeFilter#ANY_DIFF} for a basic description of the semantics.
  61. * <p>
  62. * This filter tries to avoid computing content ids of the files in the
  63. * working-tree. In contrast to {@link TreeFilter#ANY_DIFF} this filter takes
  64. * care to first compare the entry from the {@link DirCacheIterator} with the
  65. * entries from all other iterators besides the {@link WorkingTreeIterator}.
  66. * Since all those entries have fast access to content ids that is very fast. If
  67. * a difference is detected in this step this filter decides to include that
  68. * path before even looking at the working-tree entry.
  69. * <p>
  70. * If no difference is found then we have to compare index and working-tree as
  71. * the last step. By making use of
  72. * {@link WorkingTreeIterator#isModified(org.eclipse.jgit.dircache.DirCacheEntry, boolean)}
  73. * we can avoid the computation of the content id if the file is not dirty.
  74. * <p>
  75. * Instances of this filter should not be used for multiple {@link TreeWalk}s.
  76. * Always construct a new instance of this filter for each TreeWalk.
  77. */
  78. public class IndexDiffFilter extends TreeFilter {
  79. private final int dirCache;
  80. private final int workingTree;
  81. private final boolean honorIgnores;
  82. private final Set<String> ignoredPaths = new HashSet<String>();
  83. private final LinkedList<String> untrackedParentFolders = new LinkedList<String>();
  84. private final LinkedList<String> untrackedFolders = new LinkedList<String>();
  85. /**
  86. * Creates a new instance of this filter. Do not use an instance of this
  87. * filter in multiple treewalks.
  88. *
  89. * @param dirCacheIndex
  90. * the index of the {@link DirCacheIterator} in the associated
  91. * treewalk
  92. * @param workingTreeIndex
  93. * the index of the {@link WorkingTreeIterator} in the associated
  94. * treewalk
  95. */
  96. public IndexDiffFilter(int dirCacheIndex, int workingTreeIndex) {
  97. this(dirCacheIndex, workingTreeIndex, true /* honor ignores */);
  98. }
  99. /**
  100. * Creates a new instance of this filter. Do not use an instance of this
  101. * filter in multiple treewalks.
  102. *
  103. * @param dirCacheIndex
  104. * the index of the {@link DirCacheIterator} in the associated
  105. * treewalk
  106. * @param workingTreeIndex
  107. * the index of the {@link WorkingTreeIterator} in the associated
  108. * treewalk
  109. * @param honorIgnores
  110. * true if the filter should skip working tree files that are
  111. * declared as ignored by the standard exclude mechanisms..
  112. */
  113. public IndexDiffFilter(int dirCacheIndex, int workingTreeIndex,
  114. boolean honorIgnores) {
  115. this.dirCache = dirCacheIndex;
  116. this.workingTree = workingTreeIndex;
  117. this.honorIgnores = honorIgnores;
  118. }
  119. @Override
  120. public boolean include(TreeWalk tw) throws MissingObjectException,
  121. IncorrectObjectTypeException, IOException {
  122. final int cnt = tw.getTreeCount();
  123. final int wm = tw.getRawMode(workingTree);
  124. String path = tw.getPathString();
  125. DirCacheIterator di = tw.getTree(dirCache, DirCacheIterator.class);
  126. if (di != null) {
  127. DirCacheEntry dce = di.getDirCacheEntry();
  128. if (dce != null)
  129. if (dce.isAssumeValid())
  130. return false;
  131. }
  132. if (!tw.isPostOrderTraversal()) {
  133. // detect untracked Folders
  134. // Whenever we enter a folder in the workingtree assume it will
  135. // contain only untracked files and add it to
  136. // untrackedParentFolders. If we later find tracked files we will
  137. // remove it from this list
  138. if (FileMode.TREE.equals(wm)) {
  139. // Clean untrackedParentFolders. This potentially moves entries
  140. // from untrackedParentFolders to untrackedFolders
  141. copyUntrackedFolders(path);
  142. // add the folder we just entered to untrackedParentFolders
  143. untrackedParentFolders.addFirst(path);
  144. }
  145. // detect untracked Folders
  146. // Whenever we see a tracked file we know that all of its parent
  147. // folders do not belong into untrackedParentFolders anymore. Clean
  148. // it.
  149. for (int i = 0; i < cnt; i++) {
  150. int rmode = tw.getRawMode(i);
  151. if (i != workingTree && rmode != FileMode.TYPE_MISSING
  152. && FileMode.TREE.equals(rmode)) {
  153. untrackedParentFolders.clear();
  154. break;
  155. }
  156. }
  157. }
  158. // If the working tree file doesn't exist, it does exist for at least
  159. // one other so include this difference.
  160. if (wm == 0)
  161. return true;
  162. // If the path does not appear in the DirCache and its ignored
  163. // we can avoid returning a result here, but only if its not in any
  164. // other tree.
  165. final int dm = tw.getRawMode(dirCache);
  166. WorkingTreeIterator wi = workingTree(tw);
  167. if (dm == FileMode.TYPE_MISSING) {
  168. if (honorIgnores && wi.isEntryIgnored()) {
  169. ignoredPaths.add(wi.getEntryPathString());
  170. int i = 0;
  171. for (; i < cnt; i++) {
  172. if (i == dirCache || i == workingTree)
  173. continue;
  174. if (tw.getRawMode(i) != FileMode.TYPE_MISSING)
  175. break;
  176. }
  177. // If i is cnt then the path does not appear in any other tree,
  178. // and this working tree entry can be safely ignored.
  179. return i == cnt ? false : true;
  180. } else {
  181. // In working tree and not ignored, and not in DirCache.
  182. return true;
  183. }
  184. }
  185. // Always include subtrees as WorkingTreeIterator cannot provide
  186. // efficient elimination of unmodified subtrees.
  187. if (tw.isSubtree())
  188. return true;
  189. // Try the inexpensive comparisons between index and all real trees
  190. // first. Only if we don't find a diff here we have to bother with
  191. // the working tree
  192. for (int i = 0; i < cnt; i++) {
  193. if (i == dirCache || i == workingTree)
  194. continue;
  195. if (tw.getRawMode(i) != dm || !tw.idEqual(i, dirCache))
  196. return true;
  197. }
  198. // Only one chance left to detect a diff: between index and working
  199. // tree. Make use of the WorkingTreeIterator#isModified() method to
  200. // avoid computing SHA1 on filesystem content if not really needed.
  201. return wi.isModified(di.getDirCacheEntry(), true);
  202. }
  203. /**
  204. * Copy all entries which are still in untrackedParentFolders and which
  205. * belong to a path this treewalk has left into untrackedFolders. It is sure
  206. * that we will not find any tracked files underneath these paths. Therefore
  207. * these paths definitely belong to untracked folders.
  208. *
  209. * @param currentPath
  210. * the current path of the treewalk
  211. */
  212. private void copyUntrackedFolders(String currentPath) {
  213. String pathToBeSaved = null;
  214. while (!untrackedParentFolders.isEmpty()
  215. && !currentPath.startsWith(untrackedParentFolders.getFirst()
  216. + "/")) //$NON-NLS-1$
  217. pathToBeSaved = untrackedParentFolders.removeFirst();
  218. if (pathToBeSaved != null) {
  219. while (!untrackedFolders.isEmpty()
  220. && untrackedFolders.getLast().startsWith(pathToBeSaved))
  221. untrackedFolders.removeLast();
  222. untrackedFolders.addLast(pathToBeSaved);
  223. }
  224. }
  225. private WorkingTreeIterator workingTree(TreeWalk tw) {
  226. return tw.getTree(workingTree, WorkingTreeIterator.class);
  227. }
  228. @Override
  229. public boolean shouldBeRecursive() {
  230. // We cannot compare subtrees in the working tree, so encourage
  231. // use of recursive walks where the subtrees are always dived into.
  232. return true;
  233. }
  234. @Override
  235. public TreeFilter clone() {
  236. return this;
  237. }
  238. @Override
  239. public String toString() {
  240. return "INDEX_DIFF_FILTER"; //$NON-NLS-1$
  241. }
  242. /**
  243. * The method returns the list of ignored files and folders. Only the root
  244. * folder of an ignored folder hierarchy is reported. If a/b/c is listed in
  245. * the .gitignore then you should not expect a/b/c/d/e/f to be reported
  246. * here. Only a/b/c will be reported. Furthermore only ignored files /
  247. * folders are returned that are NOT in the index.
  248. *
  249. * @return ignored paths
  250. */
  251. public Set<String> getIgnoredPaths() {
  252. return ignoredPaths;
  253. }
  254. /**
  255. * @return all paths of folders which contain only untracked files/folders.
  256. * If on the associated treewalk postorder traversal was turned on
  257. * (see {@link TreeWalk#setPostOrderTraversal(boolean)}) then an
  258. * empty list will be returned.
  259. */
  260. public List<String> getUntrackedFolders() {
  261. LinkedList<String> ret = new LinkedList<String>(untrackedFolders);
  262. if (!untrackedParentFolders.isEmpty()) {
  263. String toBeAdded = untrackedParentFolders.getLast();
  264. while (!ret.isEmpty() && ret.getLast().startsWith(toBeAdded))
  265. ret.removeLast();
  266. ret.addLast(toBeAdded);
  267. }
  268. return ret;
  269. }
  270. }