blob: fe1c07ebb863d9907ff2ce5abe7603659f6aec63 [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
Ivan Fradeac7d5242019-05-23 15:32:11 -070025import com.google.common.annotations.VisibleForTesting;
Dave Borowitza55017e2013-06-19 10:53:26 -070026import com.google.common.base.Throwables;
27import com.google.common.cache.Cache;
28import com.google.common.cache.CacheBuilder;
Dave Borowitza55017e2013-06-19 10:53:26 -070029import com.google.common.util.concurrent.ExecutionError;
Dave Borowitz3b744b12016-08-19 16:11:10 -040030import java.io.IOException;
31import java.util.Arrays;
32import java.util.Collection;
33import java.util.Objects;
Dave Borowitz3b744b12016-08-19 16:11:10 -040034import java.util.concurrent.ExecutionException;
35import java.util.concurrent.TimeUnit;
Dave Borowitzc6f1ab82016-10-04 09:58:57 -040036import java.util.stream.Stream;
Dave Borowitz9de65952012-08-13 16:09:45 -070037import org.eclipse.jgit.errors.IncorrectObjectTypeException;
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;
Dave Borowitz9de65952012-08-13 16:09:45 -070043import org.eclipse.jgit.revwalk.RevWalk;
44
Dave Borowitz9de65952012-08-13 16:09:45 -070045/** Cache of per-user object visibility. */
46public class VisibilityCache {
Ivan Fradeac7d5242019-05-23 15:32:11 -070047
Dave Borowitz9de65952012-08-13 16:09:45 -070048 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;
Ivan Fradeac7d5242019-05-23 15:32:11 -070086 private final VisibilityChecker checker;
Dave Borowitz9de65952012-08-13 16:09:45 -070087
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) {
Ivan Fradeac7d5242019-05-23 15:32:11 -070097 this(new VisibilityChecker(topoSort), builder);
98 }
99
100 /**
101 * Use the constructors with a boolean parameter (e.g. {@link #VisibilityCache(boolean)}). The
102 * default visibility checker should cover all common use cases.
103 *
104 * <p>This constructor is useful to use a checker with additional logging or metrics collection,
105 * for example.
106 */
107 public VisibilityCache(VisibilityChecker checker) {
108 this(checker, defaultBuilder());
109 }
110
111 /**
112 * Use the constructors with a boolean parameter (e.g. {@link #VisibilityCache(boolean)}). The
113 * default visibility checker should cover all common use cases.
114 *
115 * <p>This constructor is useful to use a checker with additional logging or metrics collection,
116 * for example.
117 */
118 public VisibilityCache(VisibilityChecker checker, CacheBuilder<Object, Object> builder) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700119 this.cache = builder.build();
Ivan Fradeac7d5242019-05-23 15:32:11 -0700120 this.checker = checker;
Dave Borowitz9de65952012-08-13 16:09:45 -0700121 }
122
123 public Cache<?, Boolean> getCache() {
124 return cache;
125 }
126
Ivan Fradeac7d5242019-05-23 15:32:11 -0700127 @VisibleForTesting
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200128 boolean isVisible(
129 final Repository repo,
130 final RevWalk walk,
131 GitilesAccess access,
132 final ObjectId id,
133 final ObjectId... knownReachable)
134 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700135 try {
136 return cache.get(
137 new Key(access.getUserKey(), access.getRepositoryName(), id),
Dave Borowitz2b159702016-10-04 10:06:58 -0400138 () -> isVisible(repo, walk, id, Arrays.asList(knownReachable)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 } catch (ExecutionException e) {
David Pursehouse33b67e92017-03-13 21:19:21 +0900140 Throwables.throwIfInstanceOf(e.getCause(), IOException.class);
Dave Borowitz9de65952012-08-13 16:09:45 -0700141 throw new IOException(e);
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700142 } catch (ExecutionError e) {
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400143 // markUninteresting may overflow on pathological repos with very long merge chains. Play it
144 // safe and return false rather than letting the error propagate.
Dave Borowitz3d67ed52013-06-10 11:06:12 -0700145 if (e.getCause() instanceof StackOverflowError) {
146 return false;
147 }
148 throw e;
Dave Borowitz9de65952012-08-13 16:09:45 -0700149 }
150 }
151
Ivan Fradeac7d5242019-05-23 15:32:11 -0700152 boolean isVisible(Repository repo, RevWalk walk, ObjectId id, Collection<ObjectId> knownReachable)
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200153 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700154 RevCommit commit;
155 try {
156 commit = walk.parseCommit(id);
157 } catch (IncorrectObjectTypeException e) {
158 return false;
159 }
160
Ivan Frade2ce0c642019-04-08 15:51:03 -0700161 RefDatabase refDb = repo.getRefDatabase();
Ivan Fradeac7d5242019-05-23 15:32:11 -0700162 if (checker.isTipOfBranch(refDb, id)) {
163 return true;
Dave Borowitz9de65952012-08-13 16:09:45 -0700164 }
165
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400166 // Check heads first under the assumption that most requests are for refs close to a head. Tags
167 // tend to be much further back in history and just clutter up the priority queue in the common
168 // case.
Ivan Fradeac7d5242019-05-23 15:32:11 -0700169 return checker.isReachableFrom("knownReachable", walk, commit, knownReachable)
170 || isReachableFromRefs("heads", walk, commit, refDb.getRefsByPrefix(R_HEADS).stream())
171 || isReachableFromRefs("tags", walk, commit, refDb.getRefsByPrefix(R_TAGS).stream())
172 || isReachableFromRefs(
173 "other", walk, commit, refDb.getRefs().stream().filter(r -> otherRefs(r)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700174 }
175
David Pursehouse14293fe2016-10-04 15:55:22 +0900176 private static boolean refStartsWith(Ref ref, String prefix) {
177 return ref.getName().startsWith(prefix);
Dave Borowitz9de65952012-08-13 16:09:45 -0700178 }
179
David Pursehouse14293fe2016-10-04 15:55:22 +0900180 private static boolean otherRefs(Ref r) {
181 return !(refStartsWith(r, R_HEADS)
182 || refStartsWith(r, R_TAGS)
183 || refStartsWith(r, "refs/changes/"));
Dave Borowitz227ce702013-06-10 10:49:27 -0700184 }
185
Ivan Fradeac7d5242019-05-23 15:32:11 -0700186 private boolean isReachableFromRefs(String desc, RevWalk walk, RevCommit commit, Stream<Ref> refs)
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200187 throws IOException {
188 return isReachableFrom(
Ivan Fradeac7d5242019-05-23 15:32:11 -0700189 desc, walk, commit, refs.map(r -> firstNonNull(r.getPeeledObjectId(), r.getObjectId())));
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400190 }
191
Ivan Fradeac7d5242019-05-23 15:32:11 -0700192 private boolean isReachableFrom(String desc, RevWalk walk, RevCommit commit, Stream<ObjectId> ids)
Dave Borowitzc6f1ab82016-10-04 09:58:57 -0400193 throws IOException {
Ivan Fradeac7d5242019-05-23 15:32:11 -0700194 return checker.isReachableFrom(desc, walk, commit, ids.collect(toList()));
Dave Borowitz9de65952012-08-13 16:09:45 -0700195 }
196}