blob: b1cea9064181fb32ab902594b67e549332abfe68 [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;
20
Dave Borowitz227ce702013-06-10 10:49:27 -070021import static org.eclipse.jgit.lib.Constants.R_HEADS;
22import static org.eclipse.jgit.lib.Constants.R_TAGS;
23
Dave Borowitza55017e2013-06-19 10:53:26 -070024import com.google.common.base.Function;
25import com.google.common.base.Objects;
26import 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;
47import java.util.concurrent.Callable;
48import java.util.concurrent.ExecutionException;
49import java.util.concurrent.TimeUnit;
Dave Borowitz9de65952012-08-13 16:09:45 -070050
51/** Cache of per-user object visibility. */
52public class VisibilityCache {
53 private static class Key {
54 private final Object user;
55 private final String repositoryName;
56 private final ObjectId objectId;
57
58 private Key(Object user, String repositoryName, ObjectId objectId) {
59 this.user = checkNotNull(user, "user");
60 this.repositoryName = checkNotNull(repositoryName, "repositoryName");
Shawn Pearce13e5cc62013-02-06 11:32:20 -080061 this.objectId = checkNotNull(objectId, "objectId").copy();
Dave Borowitz9de65952012-08-13 16:09:45 -070062 }
63
64 @Override
65 public boolean equals(Object o) {
66 if (o instanceof Key) {
67 Key k = (Key) o;
68 return Objects.equal(user, k.user)
69 && Objects.equal(repositoryName, k.repositoryName)
70 && Objects.equal(objectId, k.objectId);
71 }
72 return false;
73 }
74
75 @Override
76 public int hashCode() {
77 return Objects.hashCode(user, repositoryName, objectId);
78 }
79
80 @Override
81 public String toString() {
82 return Objects.toStringHelper(this)
83 .add("user", user)
84 .add("repositoryName", repositoryName)
85 .add("objectId", objectId)
86 .toString();
87 }
88 }
89
90 private final Cache<Key, Boolean> cache;
91 private final boolean topoSort;
92
93 public static CacheBuilder<Object, Object> newBuilder() {
94 return CacheBuilder.newBuilder()
95 .maximumSize(1 << 10)
96 .expireAfterWrite(30, TimeUnit.MINUTES);
97 }
98
99 public VisibilityCache(boolean topoSort) {
100 this(topoSort, newBuilder());
101 }
102
103 public VisibilityCache(boolean topoSort, CacheBuilder<Object, Object> builder) {
104 this.cache = builder.build();
105 this.topoSort = topoSort;
106 }
107
108 public Cache<?, Boolean> getCache() {
109 return cache;
110 }
111
112 boolean isVisible(final Repository repo, final RevWalk walk, GitilesAccess access,
Dave Borowitzdf363d22013-06-10 11:18:04 -0700113 final ObjectId id, final ObjectId... knownReachable) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700114 try {
115 return cache.get(
116 new Key(access.getUserKey(), access.getRepositoryName(), id),
117 new Callable<Boolean>() {
118 @Override
119 public Boolean call() throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700120 return isVisible(repo, walk, id, Arrays.asList(knownReachable));
Dave Borowitz9de65952012-08-13 16:09:45 -0700121 }
122 });
123 } catch (ExecutionException e) {
124 Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
125 throw new IOException(e);
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700126 } catch (ExecutionError e) {
127 // markUninteresting may overflow on pathological repos with very long
128 // merge chains. Play it safe and return false rather than letting the
129 // error propagate.
130 if (e.getCause() instanceof StackOverflowError) {
131 return false;
132 }
133 throw e;
Dave Borowitz9de65952012-08-13 16:09:45 -0700134 }
135 }
136
Dave Borowitzdf363d22013-06-10 11:18:04 -0700137 private boolean isVisible(Repository repo, RevWalk walk, ObjectId id,
138 Collection<ObjectId> knownReachable) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 RevCommit commit;
140 try {
141 commit = walk.parseCommit(id);
142 } catch (IncorrectObjectTypeException e) {
143 return false;
144 }
145
146 // If any reference directly points at the requested object, permit display.
147 // Common for displays of pending patch sets in Gerrit Code Review, or
148 // bookmarks to the commit a tag points at.
149 Collection<Ref> allRefs = repo.getRefDatabase().getRefs(RefDatabase.ALL).values();
150 for (Ref ref : allRefs) {
151 ref = repo.getRefDatabase().peel(ref);
152 if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) {
153 return true;
154 }
155 }
156
157 // Check heads first under the assumption that most requests are for refs
158 // close to a head. Tags tend to be much further back in history and just
159 // clutter up the priority queue in the common case.
Dave Borowitzdf363d22013-06-10 11:18:04 -0700160 return isReachableFrom(walk, commit, knownReachable)
161 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_HEADS)))
162 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_TAGS)))
163 || isReachableFromRefs(walk, commit, filter(allRefs, otherRefs()));
Dave Borowitz9de65952012-08-13 16:09:45 -0700164 }
165
166 private static Predicate<Ref> refStartsWith(final String prefix) {
167 return new Predicate<Ref>() {
168 @Override
169 public boolean apply(Ref ref) {
170 return ref.getName().startsWith(prefix);
171 }
172 };
173 }
174
Dave Borowitz227ce702013-06-10 10:49:27 -0700175 @SuppressWarnings("unchecked")
176 private static Predicate<Ref> otherRefs() {
Dave Borowitza55017e2013-06-19 10:53:26 -0700177 return not(Predicates.<Ref> or(
178 refStartsWith(R_HEADS), refStartsWith(R_TAGS), refStartsWith("refs/changes/")));
Dave Borowitz227ce702013-06-10 10:49:27 -0700179 }
180
Dave Borowitzdf363d22013-06-10 11:18:04 -0700181 private boolean isReachableFromRefs(RevWalk walk, RevCommit commit,
182 Collection<Ref> refs) throws IOException {
183 return isReachableFrom(walk, commit,
184 Collections2.transform(refs, new Function<Ref, ObjectId>() {
185 @Override
186 public ObjectId apply(Ref ref) {
187 if (ref.getPeeledObjectId() != null) {
188 return ref.getPeeledObjectId();
189 } else {
190 return ref.getObjectId();
191 }
192 }
193 }));
194 }
195
196 private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<ObjectId> ids)
Dave Borowitz9de65952012-08-13 16:09:45 -0700197 throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700198 if (ids.isEmpty()) {
Dave Borowitz227ce702013-06-10 10:49:27 -0700199 return false;
200 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700201 walk.reset();
202 if (topoSort) {
203 walk.sort(RevSort.TOPO);
204 }
205 walk.markStart(commit);
Dave Borowitzdf363d22013-06-10 11:18:04 -0700206 for (ObjectId id : ids) {
207 markUninteresting(walk, id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700208 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700209 // If the commit is reachable from any given tip, it will appear to be
Dave Borowitz9de65952012-08-13 16:09:45 -0700210 // uninteresting to the RevWalk and no output will be produced.
211 return walk.next() == null;
212 }
213
214 private static void markUninteresting(RevWalk walk, ObjectId id) throws IOException {
215 if (id == null) {
216 return;
217 }
218 try {
219 walk.markUninteresting(walk.parseCommit(id));
220 } catch (IncorrectObjectTypeException e) {
221 // Do nothing, doesn't affect reachability.
222 } catch (MissingObjectException e) {
223 // Do nothing, doesn't affect reachability.
224 }
225 }
226}