blob: 3886f29ab4e6902df5324bc4f90df09ecf4b07c0 [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
17import static com.google.common.base.Preconditions.checkNotNull;
18import static com.google.common.base.Predicates.not;
19import static com.google.common.collect.Collections2.filter;
Dave Borowitz227ce702013-06-10 10:49:27 -070020import static org.eclipse.jgit.lib.Constants.R_HEADS;
21import static org.eclipse.jgit.lib.Constants.R_TAGS;
22
Dave Borowitza55017e2013-06-19 10:53:26 -070023import com.google.common.base.Function;
24import com.google.common.base.Objects;
25import com.google.common.base.Predicate;
26import com.google.common.base.Predicates;
27import com.google.common.base.Throwables;
28import com.google.common.cache.Cache;
29import com.google.common.cache.CacheBuilder;
30import com.google.common.collect.Collections2;
31import com.google.common.util.concurrent.ExecutionError;
Dave Borowitz9de65952012-08-13 16:09:45 -070032
33import org.eclipse.jgit.errors.IncorrectObjectTypeException;
34import org.eclipse.jgit.errors.MissingObjectException;
Dave Borowitz9de65952012-08-13 16:09:45 -070035import org.eclipse.jgit.lib.ObjectId;
36import org.eclipse.jgit.lib.Ref;
37import org.eclipse.jgit.lib.RefDatabase;
38import org.eclipse.jgit.lib.Repository;
39import org.eclipse.jgit.revwalk.RevCommit;
40import org.eclipse.jgit.revwalk.RevSort;
41import org.eclipse.jgit.revwalk.RevWalk;
42
Dave Borowitza55017e2013-06-19 10:53:26 -070043import java.io.IOException;
44import java.util.Arrays;
45import java.util.Collection;
46import java.util.concurrent.Callable;
47import java.util.concurrent.ExecutionException;
48import java.util.concurrent.TimeUnit;
Dave Borowitz9de65952012-08-13 16:09:45 -070049
50/** Cache of per-user object visibility. */
51public class VisibilityCache {
52 private static class Key {
53 private final Object user;
54 private final String repositoryName;
55 private final ObjectId objectId;
56
57 private Key(Object user, String repositoryName, ObjectId objectId) {
58 this.user = checkNotNull(user, "user");
59 this.repositoryName = checkNotNull(repositoryName, "repositoryName");
Shawn Pearce13e5cc62013-02-06 11:32:20 -080060 this.objectId = checkNotNull(objectId, "objectId").copy();
Dave Borowitz9de65952012-08-13 16:09:45 -070061 }
62
63 @Override
64 public boolean equals(Object o) {
65 if (o instanceof Key) {
66 Key k = (Key) o;
67 return Objects.equal(user, k.user)
68 && Objects.equal(repositoryName, k.repositoryName)
69 && Objects.equal(objectId, k.objectId);
70 }
71 return false;
72 }
73
74 @Override
75 public int hashCode() {
76 return Objects.hashCode(user, repositoryName, objectId);
77 }
78
79 @Override
80 public String toString() {
81 return Objects.toStringHelper(this)
82 .add("user", user)
83 .add("repositoryName", repositoryName)
84 .add("objectId", objectId)
85 .toString();
86 }
87 }
88
89 private final Cache<Key, Boolean> cache;
90 private final boolean topoSort;
91
Dave Borowitzd71f3762014-04-18 11:05:20 -070092 public static CacheBuilder<Object, Object> defaultBuilder() {
Dave Borowitz9de65952012-08-13 16:09:45 -070093 return CacheBuilder.newBuilder()
94 .maximumSize(1 << 10)
95 .expireAfterWrite(30, TimeUnit.MINUTES);
96 }
97
98 public VisibilityCache(boolean topoSort) {
Dave Borowitzd71f3762014-04-18 11:05:20 -070099 this(topoSort, defaultBuilder());
Dave Borowitz9de65952012-08-13 16:09:45 -0700100 }
101
102 public VisibilityCache(boolean topoSort, CacheBuilder<Object, Object> builder) {
103 this.cache = builder.build();
104 this.topoSort = topoSort;
105 }
106
107 public Cache<?, Boolean> getCache() {
108 return cache;
109 }
110
111 boolean isVisible(final Repository repo, final RevWalk walk, GitilesAccess access,
Dave Borowitzdf363d22013-06-10 11:18:04 -0700112 final ObjectId id, final ObjectId... knownReachable) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700113 try {
114 return cache.get(
115 new Key(access.getUserKey(), access.getRepositoryName(), id),
116 new Callable<Boolean>() {
117 @Override
118 public Boolean call() throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700119 return isVisible(repo, walk, id, Arrays.asList(knownReachable));
Dave Borowitz9de65952012-08-13 16:09:45 -0700120 }
121 });
122 } catch (ExecutionException e) {
123 Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
124 throw new IOException(e);
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700125 } catch (ExecutionError e) {
126 // markUninteresting may overflow on pathological repos with very long
127 // merge chains. Play it safe and return false rather than letting the
128 // error propagate.
129 if (e.getCause() instanceof StackOverflowError) {
130 return false;
131 }
132 throw e;
Dave Borowitz9de65952012-08-13 16:09:45 -0700133 }
134 }
135
Dave Borowitzdf363d22013-06-10 11:18:04 -0700136 private boolean isVisible(Repository repo, RevWalk walk, ObjectId id,
137 Collection<ObjectId> knownReachable) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700138 RevCommit commit;
139 try {
140 commit = walk.parseCommit(id);
141 } catch (IncorrectObjectTypeException e) {
142 return false;
143 }
144
145 // If any reference directly points at the requested object, permit display.
146 // Common for displays of pending patch sets in Gerrit Code Review, or
147 // bookmarks to the commit a tag points at.
148 Collection<Ref> allRefs = repo.getRefDatabase().getRefs(RefDatabase.ALL).values();
149 for (Ref ref : allRefs) {
150 ref = repo.getRefDatabase().peel(ref);
151 if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) {
152 return true;
153 }
154 }
155
156 // Check heads first under the assumption that most requests are for refs
157 // close to a head. Tags tend to be much further back in history and just
158 // clutter up the priority queue in the common case.
Dave Borowitzdf363d22013-06-10 11:18:04 -0700159 return isReachableFrom(walk, commit, knownReachable)
160 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_HEADS)))
161 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_TAGS)))
162 || isReachableFromRefs(walk, commit, filter(allRefs, otherRefs()));
Dave Borowitz9de65952012-08-13 16:09:45 -0700163 }
164
165 private static Predicate<Ref> refStartsWith(final String prefix) {
166 return new Predicate<Ref>() {
167 @Override
168 public boolean apply(Ref ref) {
169 return ref.getName().startsWith(prefix);
170 }
171 };
172 }
173
Dave Borowitz227ce702013-06-10 10:49:27 -0700174 @SuppressWarnings("unchecked")
175 private static Predicate<Ref> otherRefs() {
Dave Borowitza55017e2013-06-19 10:53:26 -0700176 return not(Predicates.<Ref> or(
177 refStartsWith(R_HEADS), refStartsWith(R_TAGS), refStartsWith("refs/changes/")));
Dave Borowitz227ce702013-06-10 10:49:27 -0700178 }
179
Dave Borowitzdf363d22013-06-10 11:18:04 -0700180 private boolean isReachableFromRefs(RevWalk walk, RevCommit commit,
181 Collection<Ref> refs) throws IOException {
182 return isReachableFrom(walk, commit,
183 Collections2.transform(refs, new Function<Ref, ObjectId>() {
184 @Override
185 public ObjectId apply(Ref ref) {
186 if (ref.getPeeledObjectId() != null) {
187 return ref.getPeeledObjectId();
188 } else {
189 return ref.getObjectId();
190 }
191 }
192 }));
193 }
194
195 private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<ObjectId> ids)
Dave Borowitz9de65952012-08-13 16:09:45 -0700196 throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700197 if (ids.isEmpty()) {
Dave Borowitz227ce702013-06-10 10:49:27 -0700198 return false;
199 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700200 walk.reset();
201 if (topoSort) {
202 walk.sort(RevSort.TOPO);
203 }
204 walk.markStart(commit);
Dave Borowitzdf363d22013-06-10 11:18:04 -0700205 for (ObjectId id : ids) {
206 markUninteresting(walk, id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700207 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700208 // If the commit is reachable from any given tip, it will appear to be
Dave Borowitz9de65952012-08-13 16:09:45 -0700209 // uninteresting to the RevWalk and no output will be produced.
210 return walk.next() == null;
211 }
212
213 private static void markUninteresting(RevWalk walk, ObjectId id) throws IOException {
214 if (id == null) {
215 return;
216 }
217 try {
218 walk.markUninteresting(walk.parseCommit(id));
219 } catch (IncorrectObjectTypeException e) {
220 // Do nothing, doesn't affect reachability.
221 } catch (MissingObjectException e) {
222 // Do nothing, doesn't affect reachability.
223 }
224 }
225}