Line data Source code
1 : // Copyright (C) 2014 The Android Open Source Project
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 :
15 : package com.google.gerrit.server.change;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static java.util.Objects.requireNonNull;
19 :
20 : import com.google.common.base.Converter;
21 : import com.google.common.base.Enums;
22 : import com.google.common.base.MoreObjects;
23 : import com.google.common.cache.Cache;
24 : import com.google.common.cache.Weigher;
25 : import com.google.common.flogger.FluentLogger;
26 : import com.google.common.util.concurrent.UncheckedExecutionException;
27 : import com.google.gerrit.entities.BranchNameKey;
28 : import com.google.gerrit.extensions.client.SubmitType;
29 : import com.google.gerrit.proto.Protos;
30 : import com.google.gerrit.server.cache.CacheModule;
31 : import com.google.gerrit.server.cache.proto.Cache.MergeabilityKeyProto;
32 : import com.google.gerrit.server.cache.serialize.BooleanCacheSerializer;
33 : import com.google.gerrit.server.cache.serialize.CacheSerializer;
34 : import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
35 : import com.google.gerrit.server.git.CodeReviewCommit;
36 : import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
37 : import com.google.gerrit.server.submit.SubmitDryRun;
38 : import com.google.inject.Inject;
39 : import com.google.inject.Module;
40 : import com.google.inject.Singleton;
41 : import com.google.inject.name.Named;
42 : import java.util.Arrays;
43 : import java.util.Objects;
44 : import java.util.Set;
45 : import java.util.concurrent.ExecutionException;
46 : import org.eclipse.jgit.lib.ObjectId;
47 : import org.eclipse.jgit.lib.Ref;
48 : import org.eclipse.jgit.lib.Repository;
49 : import org.eclipse.jgit.revwalk.RevCommit;
50 :
51 : @Singleton
52 : public class MergeabilityCacheImpl implements MergeabilityCache {
53 152 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
54 :
55 : private static final String CACHE_NAME = "mergeability";
56 :
57 : public static Module module() {
58 152 : return new CacheModule() {
59 : @Override
60 : protected void configure() {
61 152 : persist(CACHE_NAME, EntryKey.class, Boolean.class)
62 152 : .maximumWeight(1 << 20)
63 152 : .weigher(MergeabilityWeigher.class)
64 152 : .version(1)
65 152 : .keySerializer(EntryKey.Serializer.INSTANCE)
66 152 : .valueSerializer(BooleanCacheSerializer.INSTANCE);
67 152 : bind(MergeabilityCache.class).to(MergeabilityCacheImpl.class);
68 152 : }
69 : };
70 : }
71 :
72 : public static ObjectId toId(Ref ref) {
73 2 : return ref != null && ref.getObjectId() != null ? ref.getObjectId() : ObjectId.zeroId();
74 : }
75 :
76 : public static class EntryKey {
77 : private ObjectId commit;
78 : private ObjectId into;
79 : private SubmitType submitType;
80 : private String mergeStrategy;
81 :
82 22 : public EntryKey(ObjectId commit, ObjectId into, SubmitType submitType, String mergeStrategy) {
83 22 : checkArgument(
84 : submitType != SubmitType.INHERIT,
85 : "Cannot cache %s.%s",
86 22 : SubmitType.class.getSimpleName(),
87 : submitType);
88 22 : this.commit = requireNonNull(commit, "commit");
89 22 : this.into = requireNonNull(into, "into");
90 22 : this.submitType = requireNonNull(submitType, "submitType");
91 22 : this.mergeStrategy = requireNonNull(mergeStrategy, "mergeStrategy");
92 22 : }
93 :
94 : public ObjectId getCommit() {
95 1 : return commit;
96 : }
97 :
98 : public ObjectId getInto() {
99 1 : return into;
100 : }
101 :
102 : public SubmitType getSubmitType() {
103 1 : return submitType;
104 : }
105 :
106 : public String getMergeStrategy() {
107 1 : return mergeStrategy;
108 : }
109 :
110 : @Override
111 : public boolean equals(Object o) {
112 21 : if (o instanceof EntryKey) {
113 21 : EntryKey k = (EntryKey) o;
114 21 : return commit.equals(k.commit)
115 21 : && into.equals(k.into)
116 : && submitType == k.submitType
117 21 : && mergeStrategy.equals(k.mergeStrategy);
118 : }
119 0 : return false;
120 : }
121 :
122 : @Override
123 : public int hashCode() {
124 21 : return Objects.hash(commit, into, submitType, mergeStrategy);
125 : }
126 :
127 : @Override
128 : public String toString() {
129 0 : return MoreObjects.toStringHelper(this)
130 0 : .add("commit", commit.name())
131 0 : .add("into", into.name())
132 0 : .addValue(submitType)
133 0 : .addValue(mergeStrategy)
134 0 : .toString();
135 : }
136 :
137 152 : enum Serializer implements CacheSerializer<EntryKey> {
138 152 : INSTANCE;
139 :
140 152 : private static final Converter<String, SubmitType> SUBMIT_TYPE_CONVERTER =
141 152 : Enums.stringConverter(SubmitType.class);
142 :
143 : @Override
144 : public byte[] serialize(EntryKey object) {
145 1 : ObjectIdConverter idConverter = ObjectIdConverter.create();
146 1 : return Protos.toByteArray(
147 1 : MergeabilityKeyProto.newBuilder()
148 1 : .setCommit(idConverter.toByteString(object.getCommit()))
149 1 : .setInto(idConverter.toByteString(object.getInto()))
150 1 : .setSubmitType(SUBMIT_TYPE_CONVERTER.reverse().convert(object.getSubmitType()))
151 1 : .setMergeStrategy(object.getMergeStrategy())
152 1 : .build());
153 : }
154 :
155 : @Override
156 : public EntryKey deserialize(byte[] in) {
157 1 : MergeabilityKeyProto proto = Protos.parseUnchecked(MergeabilityKeyProto.parser(), in);
158 1 : ObjectIdConverter idConverter = ObjectIdConverter.create();
159 1 : return new EntryKey(
160 1 : idConverter.fromByteString(proto.getCommit()),
161 1 : idConverter.fromByteString(proto.getInto()),
162 1 : SUBMIT_TYPE_CONVERTER.convert(proto.getSubmitType()),
163 1 : proto.getMergeStrategy());
164 : }
165 : }
166 : }
167 :
168 152 : public static class MergeabilityWeigher implements Weigher<EntryKey, Boolean> {
169 : @Override
170 : public int weigh(EntryKey k, Boolean v) {
171 21 : return 16
172 : + 2 * (16 + 20)
173 : + 3 * 8 // Size of EntryKey, 64-bit JVM.
174 : + 8; // Size of Boolean.
175 : }
176 : }
177 :
178 : private final SubmitDryRun submitDryRun;
179 : private final Cache<EntryKey, Boolean> cache;
180 :
181 : @Inject
182 : MergeabilityCacheImpl(
183 146 : SubmitDryRun submitDryRun, @Named(CACHE_NAME) Cache<EntryKey, Boolean> cache) {
184 146 : this.submitDryRun = submitDryRun;
185 146 : this.cache = cache;
186 146 : }
187 :
188 : @Override
189 : public boolean get(
190 : ObjectId commit,
191 : Ref intoRef,
192 : SubmitType submitType,
193 : String mergeStrategy,
194 : BranchNameKey dest,
195 : Repository repo) {
196 21 : ObjectId into = intoRef != null ? intoRef.getObjectId() : ObjectId.zeroId();
197 21 : EntryKey key = new EntryKey(commit, into, submitType, mergeStrategy);
198 : try {
199 21 : return cache.get(
200 : key,
201 : () -> {
202 21 : if (key.into.equals(ObjectId.zeroId())) {
203 10 : return true; // Assume yes on new branch.
204 : }
205 21 : try (CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(repo)) {
206 21 : Set<RevCommit> accepted = SubmitDryRun.getAlreadyAccepted(repo, rw);
207 21 : accepted.add(rw.parseCommit(key.into));
208 21 : accepted.addAll(Arrays.asList(rw.parseCommit(key.commit).getParents()));
209 21 : return submitDryRun.run(
210 : null, key.submitType, repo, rw, dest, key.into, key.commit, accepted);
211 : }
212 : });
213 0 : } catch (ExecutionException | UncheckedExecutionException e) {
214 0 : logger.atSevere().withCause(e.getCause()).log(
215 : "Error checking mergeability of %s into %s (%s)",
216 0 : key.commit.name(), key.into.name(), key.submitType.name());
217 0 : return false;
218 : }
219 : }
220 :
221 : @Override
222 : public Boolean getIfPresent(
223 : ObjectId commit, Ref intoRef, SubmitType submitType, String mergeStrategy) {
224 2 : return cache.getIfPresent(new EntryKey(commit, toId(intoRef), submitType, mergeStrategy));
225 : }
226 : }
|