2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
3 * and other copyright owners as documented in the project's IP log.
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
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
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.
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
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.
44 package org
.eclipse
.jgit
.revwalk
;
46 import java
.io
.IOException
;
47 import java
.util
.List
;
49 import org
.eclipse
.jgit
.diff
.DiffEntry
;
50 import org
.eclipse
.jgit
.diff
.RenameDetector
;
51 import org
.eclipse
.jgit
.diff
.DiffEntry
.ChangeType
;
52 import org
.eclipse
.jgit
.errors
.CorruptObjectException
;
53 import org
.eclipse
.jgit
.errors
.IncorrectObjectTypeException
;
54 import org
.eclipse
.jgit
.errors
.MissingObjectException
;
55 import org
.eclipse
.jgit
.errors
.StopWalkException
;
56 import org
.eclipse
.jgit
.lib
.ObjectId
;
57 import org
.eclipse
.jgit
.lib
.Repository
;
58 import org
.eclipse
.jgit
.revwalk
.filter
.RevFilter
;
59 import org
.eclipse
.jgit
.treewalk
.TreeWalk
;
60 import org
.eclipse
.jgit
.treewalk
.filter
.TreeFilter
;
63 * First phase of a path limited revision walk.
65 * This filter is ANDed to evaluate after all other filters and ties the
66 * configured {@link TreeFilter} into the revision walking process.
68 * Each commit is differenced concurrently against all of its parents to look
69 * for tree entries that are interesting to the TreeFilter. If none are found
70 * the commit is colored with {@link RevWalk#REWRITE}, allowing a later pass
71 * implemented by {@link RewriteGenerator} to remove those colored commits from
74 * @see RewriteGenerator
76 class RewriteTreeFilter
extends RevFilter
{
77 private static final int PARSED
= RevWalk
.PARSED
;
79 private static final int UNINTERESTING
= RevWalk
.UNINTERESTING
;
81 private static final int REWRITE
= RevWalk
.REWRITE
;
83 private final TreeWalk pathFilter
;
85 private final Repository repository
;
87 RewriteTreeFilter(final RevWalk walker
, final TreeFilter t
) {
88 repository
= walker
.repository
;
89 pathFilter
= new TreeWalk(walker
.reader
);
90 pathFilter
.setFilter(t
);
91 pathFilter
.setRecursive(t
.shouldBeRecursive());
95 public RevFilter
clone() {
96 throw new UnsupportedOperationException();
100 public boolean include(final RevWalk walker
, final RevCommit c
)
101 throws StopWalkException
, MissingObjectException
,
102 IncorrectObjectTypeException
, IOException
{
103 // Reset the tree filter to scan this commit and parents.
105 final RevCommit
[] pList
= c
.parents
;
106 final int nParents
= pList
.length
;
107 final TreeWalk tw
= pathFilter
;
108 final ObjectId
[] trees
= new ObjectId
[nParents
+ 1];
109 for (int i
= 0; i
< nParents
; i
++) {
110 final RevCommit p
= c
.parents
[i
];
111 if ((p
.flags
& PARSED
) == 0)
112 p
.parseHeaders(walker
);
113 trees
[i
] = p
.getTree();
115 trees
[nParents
] = c
.getTree();
119 // We have exactly one parent. This is a very common case.
121 int chgs
= 0, adds
= 0;
124 if (tw
.getRawMode(0) == 0 && tw
.getRawMode(1) != 0)
127 break; // no point in looking at this further.
131 // No changes, so our tree is effectively the same as
132 // our parent tree. We pass the buck to our parent.
137 // We have interesting items, but neither of the special
138 // cases denoted above.
140 if (adds
> 0 && tw
.getFilter() instanceof FollowFilter
) {
141 // One of the paths we care about was added in this
142 // commit. We need to update our filter to its older
143 // name, if we can discover it. Find out what that is.
145 updateFollowFilter(trees
);
149 } else if (nParents
== 0) {
150 // We have no parents to compare against. Consider us to be
151 // REWRITE only if we have no paths matching our filter.
159 // We are a merge commit. We can only be REWRITE if we are same
160 // to _all_ parents. We may also be able to eliminate a parent if
161 // it does not contribute changes to us. Such a parent may be an
162 // uninteresting side branch.
164 final int[] chgs
= new int[nParents
];
165 final int[] adds
= new int[nParents
];
167 final int myMode
= tw
.getRawMode(nParents
);
168 for (int i
= 0; i
< nParents
; i
++) {
169 final int pMode
= tw
.getRawMode(i
);
170 if (myMode
== pMode
&& tw
.idEqual(i
, nParents
))
174 if (pMode
== 0 && myMode
!= 0)
179 boolean same
= false;
180 boolean diff
= false;
181 for (int i
= 0; i
< nParents
; i
++) {
183 // No changes, so our tree is effectively the same as
184 // this parent tree. We pass the buck to only this one
188 final RevCommit p
= pList
[i
];
189 if ((p
.flags
& UNINTERESTING
) != 0) {
190 // This parent was marked as not interesting by the
191 // application. We should look for another parent
192 // that is interesting.
199 c
.parents
= new RevCommit
[] { p
};
203 if (chgs
[i
] == adds
[i
]) {
204 // All of the differences from this parent were because we
205 // added files that they did not have. This parent is our
206 // "empty tree root" and thus their history is not relevant.
207 // Cut our grandparents to be an empty list.
209 pList
[i
].parents
= RevCommit
.NO_PARENTS
;
212 // We have an interesting difference relative to this parent.
218 // We did not abort above, so we are different in at least one
219 // way from all of our parents. We have to take the blame for
225 // We are the same as all of our parents. We must keep them
226 // as they are and allow those parents to flow into pending
227 // for further scanning.
233 private void updateFollowFilter(ObjectId
[] trees
)
234 throws MissingObjectException
, IncorrectObjectTypeException
,
235 CorruptObjectException
, IOException
{
236 TreeWalk tw
= pathFilter
;
237 FollowFilter oldFilter
= (FollowFilter
) tw
.getFilter();
238 tw
.setFilter(TreeFilter
.ANY_DIFF
);
241 List
<DiffEntry
> files
= DiffEntry
.scan(tw
);
242 RenameDetector rd
= new RenameDetector(repository
);
244 files
= rd
.compute();
246 TreeFilter newFilter
= oldFilter
;
247 for (DiffEntry ent
: files
) {
248 if (isRename(ent
) && ent
.getNewPath().equals(oldFilter
.getPath())) {
249 newFilter
= FollowFilter
.create(ent
.getOldPath());
253 tw
.setFilter(newFilter
);
256 private static boolean isRename(DiffEntry ent
) {
257 return ent
.getChangeType() == ChangeType
.RENAME
258 || ent
.getChangeType() == ChangeType
.COPY
;