blob: fbb3a45ea5b41811c7575228d25301673de37079 [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 Borowitzc6f1ab82016-10-04 09:58:57 -040017import static com.google.common.base.MoreObjects.firstNonNull;
Dave Borowitzc410f962014-09-23 10:49:26 -070018import static com.google.common.base.MoreObjects.toStringHelper;
Dave Borowitz9de65952012-08-13 16:09:45 -070019import static com.google.common.base.Preconditions.checkNotNull;
Dave Borowitzc410f962014-09-23 10:49:26 -070020import static java.util.Objects.hash;
Dave Borowitzc6f1ab82016-10-04 09:58:57 -040021import static java.util.stream.Collectors.toList;
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.Throwables;
26import com.google.common.cache.Cache;
27import com.google.common.cache.CacheBuilder;
Dave Borowitza55017e2013-06-19 10:53:26 -070028import com.google.common.util.concurrent.ExecutionError;
Dave Borowitz3b744b12016-08-19 16:11:10 -040029import java.io.IOException;
30import java.util.Arrays;
31import java.util.Collection;
32import java.util.Objects;
Dave Borowitz3b744b12016-08-19 16:11:10 -040033import java.util.concurrent.ExecutionException;
34import java.util.concurrent.TimeUnit;
Dave Borowitzc6f1ab82016-10-04 09:58:57 -040035import java.util.stream.Stream;
Dave Borowitz9de65952012-08-13 16:09:45 -070036import org.eclipse.jgit.errors.IncorrectObjectTypeException;
37import org.eclipse.jgit.errors.MissingObjectException;
Dave Borowitz9de65952012-08-13 16:09:45 -070038import org.eclipse.jgit.lib.ObjectId;
39import org.eclipse.jgit.lib.Ref;
Ivan Frade2ce0c642019-04-08 15:51:03 -070040import org.eclipse.jgit.lib.RefDatabase;
Dave Borowitz9de65952012-08-13 16:09:45 -070041import org.eclipse.jgit.lib.Repository;
42import org.eclipse.jgit.revwalk.RevCommit;
43import org.eclipse.jgit.revwalk.RevSort;
44import org.eclipse.jgit.revwalk.RevWalk;
45
Dave Borowitz9de65952012-08-13 16:09:45 -070046/** Cache of per-user object visibility. */
47public class VisibilityCache {
48 private static class Key {
49 private final Object user;
50 private final String repositoryName;
51 private final ObjectId objectId;
52
53 private Key(Object user, String repositoryName, ObjectId objectId) {
54 this.user = checkNotNull(user, "user");
55 this.repositoryName = checkNotNull(repositoryName, "repositoryName");
Shawn Pearce13e5cc62013-02-06 11:32:20 -080056 this.objectId = checkNotNull(objectId, "objectId").copy();
Dave Borowitz9de65952012-08-13 16:09:45 -070057 }
58
59 @Override
60 public boolean equals(Object o) {
61 if (o instanceof Key) {
62 Key k = (Key) o;
Dave Borowitzc410f962014-09-23 10:49:26 -070063 return Objects.equals(user, k.user)
64 && Objects.equals(repositoryName, k.repositoryName)
65 && Objects.equals(objectId, k.objectId);
Dave Borowitz9de65952012-08-13 16:09:45 -070066 }
67 return false;
68 }
69
70 @Override
71 public int hashCode() {
Dave Borowitzc410f962014-09-23 10:49:26 -070072 return hash(user, repositoryName, objectId);
Dave Borowitz9de65952012-08-13 16:09:45 -070073 }
74
75 @Override
76 public String toString() {
Dave Borowitzc410f962014-09-23 10:49:26 -070077 return toStringHelper(this)
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020078 .add("user", user)
79 .add("repositoryName", repositoryName)
80 .add("objectId", objectId)
81 .toString();
Dave Borowitz9de65952012-08-13 16:09:45 -070082 }
83 }
84
85 private final Cache<Key, Boolean> cache;
86 private final boolean topoSort;
87
Dave Borowitzd71f3762014-04-18 11:05:20 -070088 public static CacheBuilder<Object, Object> defaultBuilder() {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020089 return CacheBuilder.newBuilder().maximumSize(1 << 10).expireAfterWrite(30, TimeUnit.MINUTES);
Dave Borowitz9de65952012-08-13 16:09:45 -070090 }
91
92 public VisibilityCache(boolean topoSort) {
Dave Borowitzd71f3762014-04-18 11:05:20 -070093 this(topoSort, defaultBuilder());
Dave Borowitz9de65952012-08-13 16:09:45 -070094 }
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
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200105 boolean isVisible(
106 final Repository repo,
107 final RevWalk walk,
108 GitilesAccess access,
109 final ObjectId id,
110 final ObjectId... knownReachable)
111 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700112 try {
113 return cache.get(
114 new Key(access.getUserKey(), access.getRepositoryName(), id),
Dave Borowitz2b159702016-10-04 10:06:58 -0400115 () -> isVisible(repo, walk, id, Arrays.asList(knownReachable)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700116 } catch (ExecutionException e) {
David Pursehouse33b67e92017-03-13 21:19:21 +0900117 Throwables.throwIfInstanceOf(e.getCause(), IOException.class);
Dave Borowitz9de65952012-08-13 16:09:45 -0700118 throw new IOException(e);
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700119 } catch (ExecutionError e) {
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400120 // markUninteresting may overflow on pathological repos with very long merge chains. Play it
121 // safe and return false rather than letting the error propagate.
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700122 if (e.getCause() instanceof StackOverflowError) {
123 return false;
124 }
125 throw e;
Dave Borowitz9de65952012-08-13 16:09:45 -0700126 }
127 }
128
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200129 private boolean isVisible(
130 Repository repo, RevWalk walk, ObjectId id, Collection<ObjectId> knownReachable)
131 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700132 RevCommit commit;
133 try {
134 commit = walk.parseCommit(id);
135 } catch (IncorrectObjectTypeException e) {
136 return false;
137 }
138
Ivan Frade2ce0c642019-04-08 15:51:03 -0700139 RefDatabase refDb = repo.getRefDatabase();
140
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400141 // If any reference directly points at the requested object, permit display. Common for displays
142 // of pending patch sets in Gerrit Code Review, or bookmarks to the commit a tag points at.
Ivan Frade2ce0c642019-04-08 15:51:03 -0700143 for (Ref ref : repo.getRefDatabase().getRefs()) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700144 ref = repo.getRefDatabase().peel(ref);
145 if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) {
146 return true;
147 }
148 }
149
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400150 // Check heads first under the assumption that most requests are for refs close to a head. Tags
151 // tend to be much further back in history and just clutter up the priority queue in the common
152 // case.
Dave Borowitzdf363d22013-06-10 11:18:04 -0700153 return isReachableFrom(walk, commit, knownReachable)
Ivan Frade2ce0c642019-04-08 15:51:03 -0700154 || isReachableFromRefs(walk, commit, refDb.getRefsByPrefix(R_HEADS).stream())
155 || isReachableFromRefs(walk, commit, refDb.getRefsByPrefix(R_TAGS).stream())
156 || isReachableFromRefs(walk, commit, refDb.getRefs().stream().filter(r -> otherRefs(r)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700157 }
158
David Pursehouse14293fe2016-10-04 15:55:22 +0900159 private static boolean refStartsWith(Ref ref, String prefix) {
160 return ref.getName().startsWith(prefix);
Dave Borowitz9de65952012-08-13 16:09:45 -0700161 }
162
David Pursehouse14293fe2016-10-04 15:55:22 +0900163 private static boolean otherRefs(Ref r) {
164 return !(refStartsWith(r, R_HEADS)
165 || refStartsWith(r, R_TAGS)
166 || refStartsWith(r, "refs/changes/"));
Dave Borowitz227ce702013-06-10 10:49:27 -0700167 }
168
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400169 private boolean isReachableFromRefs(RevWalk walk, RevCommit commit, Stream<Ref> refs)
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200170 throws IOException {
171 return isReachableFrom(
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400172 walk, commit, refs.map(r -> firstNonNull(r.getPeeledObjectId(), r.getObjectId())));
173 }
174
175 private boolean isReachableFrom(RevWalk walk, RevCommit commit, Stream<ObjectId> ids)
176 throws IOException {
177 return isReachableFrom(walk, commit, ids.collect(toList()));
Dave Borowitzdf363d22013-06-10 11:18:04 -0700178 }
179
180 private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<ObjectId> ids)
Dave Borowitz9de65952012-08-13 16:09:45 -0700181 throws IOException {
Dave Borowitzdf363d22013-06-10 11:18:04 -0700182 if (ids.isEmpty()) {
Dave Borowitz227ce702013-06-10 10:49:27 -0700183 return false;
184 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700185 walk.reset();
186 if (topoSort) {
187 walk.sort(RevSort.TOPO);
188 }
189 walk.markStart(commit);
Dave Borowitzdf363d22013-06-10 11:18:04 -0700190 for (ObjectId id : ids) {
191 markUninteresting(walk, id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700192 }
Dave Borowitzdf363d22013-06-10 11:18:04 -0700193 // If the commit is reachable from any given tip, it will appear to be
Dave Borowitz9de65952012-08-13 16:09:45 -0700194 // uninteresting to the RevWalk and no output will be produced.
195 return walk.next() == null;
196 }
197
198 private static void markUninteresting(RevWalk walk, ObjectId id) throws IOException {
199 if (id == null) {
200 return;
201 }
202 try {
203 walk.markUninteresting(walk.parseCommit(id));
Dave Borowitz27058932014-12-03 15:44:46 -0800204 } catch (IncorrectObjectTypeException | MissingObjectException e) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700205 // Do nothing, doesn't affect reachability.
206 }
207 }
208}