blob: 657bba89962dd501f8d3ed6bded62c6beba2ec18 [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 Borowitz3b744b12016-08-19 16:11:10 -040033import java.io.IOException;
34import java.util.Arrays;
35import java.util.Collection;
36import java.util.Objects;
37import java.util.concurrent.Callable;
38import java.util.concurrent.ExecutionException;
39import java.util.concurrent.TimeUnit;
Dave Borowitz9de65952012-08-13 16:09:45 -070040import org.eclipse.jgit.errors.IncorrectObjectTypeException;
41import org.eclipse.jgit.errors.MissingObjectException;
Dave Borowitz9de65952012-08-13 16:09:45 -070042import org.eclipse.jgit.lib.ObjectId;
43import org.eclipse.jgit.lib.Ref;
44import org.eclipse.jgit.lib.RefDatabase;
45import org.eclipse.jgit.lib.Repository;
46import org.eclipse.jgit.revwalk.RevCommit;
47import org.eclipse.jgit.revwalk.RevSort;
48import org.eclipse.jgit.revwalk.RevWalk;
49
Dave Borowitz9de65952012-08-13 16:09:45 -070050/** 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;
Dave Borowitzc410f962014-09-23 10:49:26 -070067 return Objects.equals(user, k.user)
68 && Objects.equals(repositoryName, k.repositoryName)
69 && Objects.equals(objectId, k.objectId);
Dave Borowitz9de65952012-08-13 16:09:45 -070070 }
71 return false;
72 }
73
74 @Override
75 public int hashCode() {
Dave Borowitzc410f962014-09-23 10:49:26 -070076 return hash(user, repositoryName, objectId);
Dave Borowitz9de65952012-08-13 16:09:45 -070077 }
78
79 @Override
80 public String toString() {
Dave Borowitzc410f962014-09-23 10:49:26 -070081 return toStringHelper(this)
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020082 .add("user", user)
83 .add("repositoryName", repositoryName)
84 .add("objectId", objectId)
85 .toString();
Dave Borowitz9de65952012-08-13 16:09:45 -070086 }
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() {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020093 return CacheBuilder.newBuilder().maximumSize(1 << 10).expireAfterWrite(30, TimeUnit.MINUTES);
Dave Borowitz9de65952012-08-13 16:09:45 -070094 }
95
96 public VisibilityCache(boolean topoSort) {
Dave Borowitzd71f3762014-04-18 11:05:20 -070097 this(topoSort, defaultBuilder());
Dave Borowitz9de65952012-08-13 16:09:45 -070098 }
99
100 public VisibilityCache(boolean topoSort, CacheBuilder<Object, Object> builder) {
101 this.cache = builder.build();
102 this.topoSort = topoSort;
103 }
104
105 public Cache<?, Boolean> getCache() {
106 return cache;
107 }
108
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200109 boolean isVisible(
110 final Repository repo,
111 final RevWalk walk,
112 GitilesAccess access,
113 final ObjectId id,
114 final ObjectId... knownReachable)
115 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700116 try {
117 return cache.get(
118 new Key(access.getUserKey(), access.getRepositoryName(), id),
119 new Callable<Boolean>() {
120 @Override
121 public Boolean call() throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700122 return isVisible(repo, walk, id, Arrays.asList(knownReachable));
Dave Borowitz9de65952012-08-13 16:09:45 -0700123 }
124 });
125 } catch (ExecutionException e) {
126 Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
127 throw new IOException(e);
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700128 } catch (ExecutionError e) {
129 // markUninteresting may overflow on pathological repos with very long
130 // merge chains. Play it safe and return false rather than letting the
131 // error propagate.
132 if (e.getCause() instanceof StackOverflowError) {
133 return false;
134 }
135 throw e;
Dave Borowitz9de65952012-08-13 16:09:45 -0700136 }
137 }
138
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200139 private boolean isVisible(
140 Repository repo, RevWalk walk, ObjectId id, Collection<ObjectId> knownReachable)
141 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700142 RevCommit commit;
143 try {
144 commit = walk.parseCommit(id);
145 } catch (IncorrectObjectTypeException e) {
146 return false;
147 }
148
149 // If any reference directly points at the requested object, permit display.
150 // Common for displays of pending patch sets in Gerrit Code Review, or
151 // bookmarks to the commit a tag points at.
152 Collection<Ref> allRefs = repo.getRefDatabase().getRefs(RefDatabase.ALL).values();
153 for (Ref ref : allRefs) {
154 ref = repo.getRefDatabase().peel(ref);
155 if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) {
156 return true;
157 }
158 }
159
160 // Check heads first under the assumption that most requests are for refs
161 // close to a head. Tags tend to be much further back in history and just
162 // clutter up the priority queue in the common case.
Dave Borowitzdf363d22013-06-10 11:18:04 -0700163 return isReachableFrom(walk, commit, knownReachable)
164 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_HEADS)))
165 || isReachableFromRefs(walk, commit, filter(allRefs, refStartsWith(R_TAGS)))
166 || isReachableFromRefs(walk, commit, filter(allRefs, otherRefs()));
Dave Borowitz9de65952012-08-13 16:09:45 -0700167 }
168
169 private static Predicate<Ref> refStartsWith(final String prefix) {
170 return new Predicate<Ref>() {
171 @Override
172 public boolean apply(Ref ref) {
173 return ref.getName().startsWith(prefix);
174 }
175 };
176 }
177
Dave Borowitz227ce702013-06-10 10:49:27 -0700178 @SuppressWarnings("unchecked")
179 private static Predicate<Ref> otherRefs() {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200180 return not(
181 Predicates.<Ref>or(
182 refStartsWith(R_HEADS), refStartsWith(R_TAGS), refStartsWith("refs/changes/")));
Dave Borowitz227ce702013-06-10 10:49:27 -0700183 }
184
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200185 private boolean isReachableFromRefs(RevWalk walk, RevCommit commit, Collection<Ref> refs)
186 throws IOException {
187 return isReachableFrom(
188 walk,
189 commit,
190 Collections2.transform(
191 refs,
192 new Function<Ref, ObjectId>() {
193 @Override
194 public ObjectId apply(Ref ref) {
195 if (ref.getPeeledObjectId() != null) {
196 return ref.getPeeledObjectId();
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200197 }
David Pursehouseb3b630f2016-06-15 21:51:18 +0900198 return ref.getObjectId();
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200199 }
200 }));
Dave Borowitzdf363d22013-06-10 11:18:04 -0700201 }
202
203 private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<ObjectId> ids)
Dave Borowitz9de65952012-08-13 16:09:45 -0700204 throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700205 if (ids.isEmpty()) {
Dave Borowitz227ce702013-06-10 10:49:27 -0700206 return false;
207 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700208 walk.reset();
209 if (topoSort) {
210 walk.sort(RevSort.TOPO);
211 }
212 walk.markStart(commit);
Dave Borowitzdf363d22013-06-10 11:18:04 -0700213 for (ObjectId id : ids) {
214 markUninteresting(walk, id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700215 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700216 // If the commit is reachable from any given tip, it will appear to be
Dave Borowitz9de65952012-08-13 16:09:45 -0700217 // uninteresting to the RevWalk and no output will be produced.
218 return walk.next() == null;
219 }
220
221 private static void markUninteresting(RevWalk walk, ObjectId id) throws IOException {
222 if (id == null) {
223 return;
224 }
225 try {
226 walk.markUninteresting(walk.parseCommit(id));
Dave Borowitz27058932014-12-03 15:44:46 -0800227 } catch (IncorrectObjectTypeException | MissingObjectException e) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700228 // Do nothing, doesn't affect reachability.
229 }
230 }
231}