blob: 22969d19b1633789eed0e30dd841efb85e57fc76 [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
21import com.google.common.base.Objects;
22import com.google.common.base.Predicate;
23import com.google.common.base.Throwables;
24import com.google.common.cache.Cache;
25import com.google.common.cache.CacheBuilder;
26
27import org.eclipse.jgit.errors.IncorrectObjectTypeException;
28import org.eclipse.jgit.errors.MissingObjectException;
29import org.eclipse.jgit.lib.Constants;
30import org.eclipse.jgit.lib.ObjectId;
31import org.eclipse.jgit.lib.Ref;
32import org.eclipse.jgit.lib.RefDatabase;
33import org.eclipse.jgit.lib.Repository;
34import org.eclipse.jgit.revwalk.RevCommit;
35import org.eclipse.jgit.revwalk.RevSort;
36import org.eclipse.jgit.revwalk.RevWalk;
37
38import java.io.IOException;
39import java.util.Collection;
40import java.util.concurrent.Callable;
41import java.util.concurrent.ExecutionException;
42import java.util.concurrent.TimeUnit;
43
44/** Cache of per-user object visibility. */
45public class VisibilityCache {
46 private static class Key {
47 private final Object user;
48 private final String repositoryName;
49 private final ObjectId objectId;
50
51 private Key(Object user, String repositoryName, ObjectId objectId) {
52 this.user = checkNotNull(user, "user");
53 this.repositoryName = checkNotNull(repositoryName, "repositoryName");
54 this.objectId = checkNotNull(objectId, "objectId");
55 }
56
57 @Override
58 public boolean equals(Object o) {
59 if (o instanceof Key) {
60 Key k = (Key) o;
61 return Objects.equal(user, k.user)
62 && Objects.equal(repositoryName, k.repositoryName)
63 && Objects.equal(objectId, k.objectId);
64 }
65 return false;
66 }
67
68 @Override
69 public int hashCode() {
70 return Objects.hashCode(user, repositoryName, objectId);
71 }
72
73 @Override
74 public String toString() {
75 return Objects.toStringHelper(this)
76 .add("user", user)
77 .add("repositoryName", repositoryName)
78 .add("objectId", objectId)
79 .toString();
80 }
81 }
82
83 private final Cache<Key, Boolean> cache;
84 private final boolean topoSort;
85
86 public static CacheBuilder<Object, Object> newBuilder() {
87 return CacheBuilder.newBuilder()
88 .maximumSize(1 << 10)
89 .expireAfterWrite(30, TimeUnit.MINUTES);
90 }
91
92 public VisibilityCache(boolean topoSort) {
93 this(topoSort, newBuilder());
94 }
95
96 public VisibilityCache(boolean topoSort, CacheBuilder<Object, Object> builder) {
97 this.cache = builder.build();
98 this.topoSort = topoSort;
99 }
100
101 public Cache<?, Boolean> getCache() {
102 return cache;
103 }
104
105 boolean isVisible(final Repository repo, final RevWalk walk, GitilesAccess access,
106 final ObjectId id) throws IOException {
107 try {
108 return cache.get(
109 new Key(access.getUserKey(), access.getRepositoryName(), id),
110 new Callable<Boolean>() {
111 @Override
112 public Boolean call() throws IOException {
113 return isVisible(repo, walk, id);
114 }
115 });
116 } catch (ExecutionException e) {
117 Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
118 throw new IOException(e);
119 }
120 }
121
122 private boolean isVisible(Repository repo, RevWalk walk, ObjectId id) throws IOException {
123 RevCommit commit;
124 try {
125 commit = walk.parseCommit(id);
126 } catch (IncorrectObjectTypeException e) {
127 return false;
128 }
129
130 // If any reference directly points at the requested object, permit display.
131 // Common for displays of pending patch sets in Gerrit Code Review, or
132 // bookmarks to the commit a tag points at.
133 Collection<Ref> allRefs = repo.getRefDatabase().getRefs(RefDatabase.ALL).values();
134 for (Ref ref : allRefs) {
135 ref = repo.getRefDatabase().peel(ref);
136 if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) {
137 return true;
138 }
139 }
140
141 // Check heads first under the assumption that most requests are for refs
142 // close to a head. Tags tend to be much further back in history and just
143 // clutter up the priority queue in the common case.
144 return isReachableFrom(walk, commit, filter(allRefs, refStartsWith(Constants.R_HEADS)))
145 || isReachableFrom(walk, commit, filter(allRefs, refStartsWith(Constants.R_TAGS)))
146 || isReachableFrom(walk, commit, filter(allRefs, not(refStartsWith("refs/changes/"))));
147 }
148
149 private static Predicate<Ref> refStartsWith(final String prefix) {
150 return new Predicate<Ref>() {
151 @Override
152 public boolean apply(Ref ref) {
153 return ref.getName().startsWith(prefix);
154 }
155 };
156 }
157
158 private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<Ref> refs)
159 throws IOException {
160 walk.reset();
161 if (topoSort) {
162 walk.sort(RevSort.TOPO);
163 }
164 walk.markStart(commit);
165 for (Ref ref : refs) {
166 if (ref.getPeeledObjectId() != null) {
167 markUninteresting(walk, ref.getPeeledObjectId());
168 } else {
169 markUninteresting(walk, ref.getObjectId());
170 }
171 }
172 // If the commit is reachable from any branch head, it will appear to be
173 // uninteresting to the RevWalk and no output will be produced.
174 return walk.next() == null;
175 }
176
177 private static void markUninteresting(RevWalk walk, ObjectId id) throws IOException {
178 if (id == null) {
179 return;
180 }
181 try {
182 walk.markUninteresting(walk.parseCommit(id));
183 } catch (IncorrectObjectTypeException e) {
184 // Do nothing, doesn't affect reachability.
185 } catch (MissingObjectException e) {
186 // Do nothing, doesn't affect reachability.
187 }
188 }
189}