blob: a11d7b282ba4ec35d06782e4fc764e9cbab500cf [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles;
16
Dave Borowitzc410f962014-09-23 10:49:26 -070017import static com.google.common.base.MoreObjects.toStringHelper;
Dave Borowitz9de65952012-08-13 16:09:45 -070018import static com.google.common.base.Preconditions.checkNotNull;
19import static com.google.common.base.Predicates.not;
20import static com.google.common.collect.Collections2.filter;
Dave Borowitzc410f962014-09-23 10:49:26 -070021import static java.util.Objects.hash;
Dave Borowitz227ce702013-06-10 10:49:27 -070022import static org.eclipse.jgit.lib.Constants.R_HEADS;
23import static org.eclipse.jgit.lib.Constants.R_TAGS;
24
Dave Borowitza55017e2013-06-19 10:53:26 -070025import com.google.common.base.Function;
Dave Borowitza55017e2013-06-19 10:53:26 -070026import com.google.common.base.Predicate;
27import com.google.common.base.Predicates;
28import com.google.common.base.Throwables;
29import com.google.common.cache.Cache;
30import com.google.common.cache.CacheBuilder;
31import com.google.common.collect.Collections2;
32import com.google.common.util.concurrent.ExecutionError;
Dave Borowitz9de65952012-08-13 16:09:45 -070033
34import org.eclipse.jgit.errors.IncorrectObjectTypeException;
35import org.eclipse.jgit.errors.MissingObjectException;
Dave Borowitz9de65952012-08-13 16:09:45 -070036import org.eclipse.jgit.lib.ObjectId;
37import org.eclipse.jgit.lib.Ref;
38import org.eclipse.jgit.lib.RefDatabase;
39import org.eclipse.jgit.lib.Repository;
40import org.eclipse.jgit.revwalk.RevCommit;
41import org.eclipse.jgit.revwalk.RevSort;
42import org.eclipse.jgit.revwalk.RevWalk;
43
Dave Borowitza55017e2013-06-19 10:53:26 -070044import java.io.IOException;
45import java.util.Arrays;
46import java.util.Collection;
Dave Borowitzc410f962014-09-23 10:49:26 -070047import java.util.Objects;
Dave Borowitza55017e2013-06-19 10:53:26 -070048import java.util.concurrent.Callable;
49import java.util.concurrent.ExecutionException;
50import java.util.concurrent.TimeUnit;
Dave Borowitz9de65952012-08-13 16:09:45 -070051
52/** Cache of per-user object visibility. */
53public class VisibilityCache {
54 private static class Key {
55 private final Object user;
56 private final String repositoryName;
57 private final ObjectId objectId;
58
59 private Key(Object user, String repositoryName, ObjectId objectId) {
60 this.user = checkNotNull(user, "user");
61 this.repositoryName = checkNotNull(repositoryName, "repositoryName");
Shawn Pearce13e5cc62013-02-06 11:32:20 -080062 this.objectId = checkNotNull(objectId, "objectId").copy();
Dave Borowitz9de65952012-08-13 16:09:45 -070063 }
64
65 @Override
66 public boolean equals(Object o) {
67 if (o instanceof Key) {
68 Key k = (Key) o;
Dave Borowitzc410f962014-09-23 10:49:26 -070069 return Objects.equals(user, k.user)
70 && Objects.equals(repositoryName, k.repositoryName)
71 && Objects.equals(objectId, k.objectId);
Dave Borowitz9de65952012-08-13 16:09:45 -070072 }
73 return false;
74 }
75
76 @Override
77 public int hashCode() {
Dave Borowitzc410f962014-09-23 10:49:26 -070078 return hash(user, repositoryName, objectId);
Dave Borowitz9de65952012-08-13 16:09:45 -070079 }
80
81 @Override
82 public String toString() {
Dave Borowitzc410f962014-09-23 10:49:26 -070083 return toStringHelper(this)
Dave Borowitz9de65952012-08-13 16:09:45 -070084 .add("user", user)
85 .add("repositoryName", repositoryName)
86 .add("objectId", objectId)
87 .toString();
88 }
89 }
90
91 private final Cache<Key, Boolean> cache;
92 private final boolean topoSort;
93
Dave Borowitzd71f3762014-04-18 11:05:20 -070094 public static CacheBuilder<Object, Object> defaultBuilder() {
Dave Borowitz9de65952012-08-13 16:09:45 -070095 return CacheBuilder.newBuilder()
96 .maximumSize(1 << 10)
97 .expireAfterWrite(30, TimeUnit.MINUTES);
98 }
99
100 public VisibilityCache(boolean topoSort) {
Dave Borowitzd71f3762014-04-18 11:05:20 -0700101 this(topoSort, defaultBuilder());
Dave Borowitz9de65952012-08-13 16:09:45 -0700102 }
103
104 public VisibilityCache(boolean topoSort, CacheBuilder<Object, Object> builder) {
105 this.cache = builder.build();
106 this.topoSort = topoSort;
107 }
108
109 public Cache<?, Boolean> getCache() {
110 return cache;
111 }
112
113 boolean isVisible(final Repository repo, final RevWalk walk, GitilesAccess access,
Dave Borowitzdf363d22013-06-10 11:18:04 -0700114 final ObjectId id, final ObjectId... knownReachable) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700115 try {
116 return cache.get(
117 new Key(access.getUserKey(), access.getRepositoryName(), id),
118 new Callable<Boolean>() {
119 @Override
120 public Boolean call() throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700121 return isVisible(repo, walk, id, Arrays.asList(knownReachable));
Dave Borowitz9de65952012-08-13 16:09:45 -0700122 }
123 });
124 } catch (ExecutionException e) {
125 Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
126 throw new IOException(e);
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700127 } catch (ExecutionError e) {
128 // markUninteresting may overflow on pathological repos with very long
129 // merge chains. Play it safe and return false rather than letting the
130 // error propagate.
131 if (e.getCause() instanceof StackOverflowError) {
132 return false;
133 }
134 throw e;
Dave Borowitz9de65952012-08-13 16:09:45 -0700135 }
136 }
137
Dave Borowitzdf363d22013-06-10 11:18:04 -0700138 private boolean isVisible(Repository repo, RevWalk walk, ObjectId id,
139 Collection<ObjectId> knownReachable) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700140 RevCommit commit;
141 try {
142 commit = walk.parseCommit(id);
143 } catch (IncorrectObjectTypeException e) {
144 return false;
145 }
146
147 // If any reference directly points at the requested object, permit display.
148 // Common for displays of pending patch sets in Gerrit Code Review, or
149 // bookmarks to the commit a tag points at.
150 Collection<Ref> allRefs = repo.getRefDatabase().getRefs(RefDatabase.ALL).values();
151 for (Ref ref : allRefs) {
152 ref = repo.getRefDatabase().peel(ref);
153 if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) {
154 return true;
155 }
156 }
157
158 // Check heads first under the assumption that most requests are for refs
159 // close to a head. Tags tend to be much further back in history and just
160 // clutter up the priority queue in the common case.
Dave Borowitzdf363d22013-06-10 11:18:04 -0700161 return isReachableFrom(walk, commit, knownReachable)
162 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_HEADS)))
163 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_TAGS)))
164 || isReachableFromRefs(walk, commit, filter(allRefs, otherRefs()));
Dave Borowitz9de65952012-08-13 16:09:45 -0700165 }
166
167 private static Predicate<Ref> refStartsWith(final String prefix) {
168 return new Predicate<Ref>() {
169 @Override
170 public boolean apply(Ref ref) {
171 return ref.getName().startsWith(prefix);
172 }
173 };
174 }
175
Dave Borowitz227ce702013-06-10 10:49:27 -0700176 @SuppressWarnings("unchecked")
177 private static Predicate<Ref> otherRefs() {
Dave Borowitza55017e2013-06-19 10:53:26 -0700178 return not(Predicates.<Ref> or(
179 refStartsWith(R_HEADS), refStartsWith(R_TAGS), refStartsWith("refs/changes/")));
Dave Borowitz227ce702013-06-10 10:49:27 -0700180 }
181
Dave Borowitzdf363d22013-06-10 11:18:04 -0700182 private boolean isReachableFromRefs(RevWalk walk, RevCommit commit,
183 Collection<Ref> refs) throws IOException {
184 return isReachableFrom(walk, commit,
185 Collections2.transform(refs, new Function<Ref, ObjectId>() {
186 @Override
187 public ObjectId apply(Ref ref) {
188 if (ref.getPeeledObjectId() != null) {
189 return ref.getPeeledObjectId();
190 } else {
191 return ref.getObjectId();
192 }
193 }
194 }));
195 }
196
197 private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<ObjectId> ids)
Dave Borowitz9de65952012-08-13 16:09:45 -0700198 throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700199 if (ids.isEmpty()) {
Dave Borowitz227ce702013-06-10 10:49:27 -0700200 return false;
201 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700202 walk.reset();
203 if (topoSort) {
204 walk.sort(RevSort.TOPO);
205 }
206 walk.markStart(commit);
Dave Borowitzdf363d22013-06-10 11:18:04 -0700207 for (ObjectId id : ids) {
208 markUninteresting(walk, id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700209 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700210 // If the commit is reachable from any given tip, it will appear to be
Dave Borowitz9de65952012-08-13 16:09:45 -0700211 // uninteresting to the RevWalk and no output will be produced.
212 return walk.next() == null;
213 }
214
215 private static void markUninteresting(RevWalk walk, ObjectId id) throws IOException {
216 if (id == null) {
217 return;
218 }
219 try {
220 walk.markUninteresting(walk.parseCommit(id));
221 } catch (IncorrectObjectTypeException e) {
222 // Do nothing, doesn't affect reachability.
223 } catch (MissingObjectException e) {
224 // Do nothing, doesn't affect reachability.
225 }
226 }
227}