Extract MergeabilityCache as an interface
Batch or other standalone programs may choose to disable this implementation entirely rather than provide all the bindings necessary to create SubmitStrategyFactory and friends, just like they do for PatchListCache. (This is already done in tests, where it is marginally nicer to bind a non-null, not-implemented implementation.) Provide a no-op implementation called NotImplemented, mirroring some other NotImplemented implementations in the api packages. Change-Id: Ie8eff3189959e3aeb6c92a9726582c7358553de1
This commit is contained in:
@@ -14,309 +14,34 @@
|
||||
|
||||
package com.google.gerrit.server.change;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
|
||||
import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
|
||||
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.cache.Weigher;
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.extensions.common.SubmitType;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||
import com.google.gerrit.server.git.MergeException;
|
||||
import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
|
||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevFlag;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@Singleton
|
||||
public class MergeabilityCache {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(MergeabilityCache.class);
|
||||
|
||||
private static final String CACHE_NAME = "mergeability";
|
||||
|
||||
public static final BiMap<SubmitType, Character> SUBMIT_TYPES = ImmutableBiMap.of(
|
||||
SubmitType.FAST_FORWARD_ONLY, 'F',
|
||||
SubmitType.MERGE_IF_NECESSARY, 'M',
|
||||
SubmitType.REBASE_IF_NECESSARY, 'R',
|
||||
SubmitType.MERGE_ALWAYS, 'A',
|
||||
SubmitType.CHERRY_PICK, 'C');
|
||||
|
||||
static {
|
||||
checkState(SUBMIT_TYPES.size() == SubmitType.values().length,
|
||||
"SubmitType <-> char BiMap needs updating");
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Key bindingKey() {
|
||||
return Key.get(new TypeLiteral<LoadingCache<EntryKey, Boolean>>() {},
|
||||
Names.named(CACHE_NAME));
|
||||
}
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
persist(CACHE_NAME, EntryKey.class, Boolean.class)
|
||||
.maximumWeight(1 << 20)
|
||||
.weigher(MergeabilityWeigher.class)
|
||||
.loader(Loader.class);
|
||||
bind(MergeabilityCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectId toId(Ref ref) {
|
||||
return ref != null && ref.getObjectId() != null
|
||||
? ref.getObjectId()
|
||||
: ObjectId.zeroId();
|
||||
}
|
||||
|
||||
public static class EntryKey implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private ObjectId commit;
|
||||
private ObjectId into;
|
||||
private SubmitType submitType;
|
||||
private String mergeStrategy;
|
||||
|
||||
// Only used for loading, not stored.
|
||||
private transient LoadHelper load;
|
||||
|
||||
public EntryKey(ObjectId commit, ObjectId into, SubmitType submitType,
|
||||
String mergeStrategy) {
|
||||
this.commit = checkNotNull(commit, "commit");
|
||||
this.into = checkNotNull(into, "into");
|
||||
this.submitType = checkNotNull(submitType, "submitType");
|
||||
this.mergeStrategy = checkNotNull(mergeStrategy, "mergeStrategy");
|
||||
}
|
||||
|
||||
private EntryKey(ObjectId commit, ObjectId into, SubmitType submitType,
|
||||
/** Cache for mergeability of commits into destination branches. */
|
||||
public interface MergeabilityCache {
|
||||
public static class NotImplemented implements MergeabilityCache {
|
||||
@Override
|
||||
public boolean get(ObjectId commit, Ref intoRef, SubmitType submitType,
|
||||
String mergeStrategy, Branch.NameKey dest, Repository repo,
|
||||
ReviewDb db) {
|
||||
this(commit, into, submitType, mergeStrategy);
|
||||
load = new LoadHelper(dest, repo, db);
|
||||
}
|
||||
|
||||
public ObjectId getCommit() {
|
||||
return commit;
|
||||
}
|
||||
|
||||
public ObjectId getInto() {
|
||||
return into;
|
||||
}
|
||||
|
||||
public SubmitType getSubmitType() {
|
||||
return submitType;
|
||||
}
|
||||
|
||||
public String getMergeStrategy() {
|
||||
return mergeStrategy;
|
||||
throw new UnsupportedOperationException("Mergeability checking disabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof EntryKey) {
|
||||
EntryKey k = (EntryKey) o;
|
||||
return commit.equals(k.commit)
|
||||
&& into.equals(k.into)
|
||||
&& submitType == k.submitType
|
||||
&& mergeStrategy.equals(k.mergeStrategy);
|
||||
}
|
||||
return false;
|
||||
public boolean getIfPresent(ObjectId commit, Ref intoRef,
|
||||
SubmitType submitType, String mergeStrategy) {
|
||||
throw new UnsupportedOperationException("Mergeability checking disabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(commit, into, submitType, mergeStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("commit", commit.name())
|
||||
.add("into", into.name())
|
||||
.addValue(submitType)
|
||||
.addValue(mergeStrategy)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
writeNotNull(out, commit);
|
||||
writeNotNull(out, into);
|
||||
Character c = SUBMIT_TYPES.get(submitType);
|
||||
if (c == null) {
|
||||
throw new IOException("Invalid submit type: " + submitType);
|
||||
}
|
||||
out.writeChar(c);
|
||||
writeString(out, mergeStrategy);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException {
|
||||
commit = readNotNull(in);
|
||||
into = readNotNull(in);
|
||||
char t = in.readChar();
|
||||
submitType = SUBMIT_TYPES.inverse().get(t);
|
||||
if (submitType == null) {
|
||||
throw new IOException("Invalid submit type code: " + t);
|
||||
}
|
||||
mergeStrategy = readString(in);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LoadHelper {
|
||||
private final Branch.NameKey dest;
|
||||
private final Repository repo;
|
||||
private final ReviewDb db;
|
||||
|
||||
private LoadHelper(Branch.NameKey dest, Repository repo, ReviewDb db) {
|
||||
this.dest = checkNotNull(dest, "dest");
|
||||
this.repo = checkNotNull(repo, "repo");
|
||||
this.db = checkNotNull(db, "db");
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static class Loader extends CacheLoader<EntryKey, Boolean> {
|
||||
private final SubmitStrategyFactory submitStrategyFactory;
|
||||
|
||||
@Inject
|
||||
Loader(SubmitStrategyFactory submitStrategyFactory) {
|
||||
this.submitStrategyFactory = submitStrategyFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean load(EntryKey key)
|
||||
throws NoSuchProjectException, MergeException, IOException {
|
||||
checkArgument(key.load != null, "Key cannot be loaded: %s", key);
|
||||
if (key.into.equals(ObjectId.zeroId())) {
|
||||
return true; // Assume yes on new branch.
|
||||
}
|
||||
try {
|
||||
Map<String, Ref> refs = key.load.repo.getAllRefs();
|
||||
RevWalk rw = CodeReviewCommit.newRevWalk(key.load.repo);
|
||||
try {
|
||||
RevFlag canMerge = rw.newFlag("CAN_MERGE");
|
||||
CodeReviewCommit rev = parse(rw, key.commit);
|
||||
rev.add(canMerge);
|
||||
CodeReviewCommit tip = parse(rw, key.into);
|
||||
Set<RevCommit> accepted = alreadyAccepted(rw, refs.values());
|
||||
accepted.add(tip);
|
||||
accepted.addAll(Arrays.asList(rev.getParents()));
|
||||
return submitStrategyFactory.create(
|
||||
key.submitType,
|
||||
key.load.db,
|
||||
key.load.repo,
|
||||
rw,
|
||||
null /*inserter*/,
|
||||
canMerge,
|
||||
accepted,
|
||||
key.load.dest).dryRun(tip, rev);
|
||||
} finally {
|
||||
rw.release();
|
||||
}
|
||||
} finally {
|
||||
key.load = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<RevCommit> alreadyAccepted(RevWalk rw,
|
||||
Collection<Ref> refs) throws MissingObjectException, IOException {
|
||||
Set<RevCommit> accepted = Sets.newHashSet();
|
||||
for (Ref r : refs) {
|
||||
if (r.getName().startsWith(Constants.R_HEADS)
|
||||
|| r.getName().startsWith(Constants.R_TAGS)) {
|
||||
try {
|
||||
accepted.add(rw.parseCommit(r.getObjectId()));
|
||||
} catch (IncorrectObjectTypeException nonCommit) {
|
||||
// Not a commit? Skip over it.
|
||||
}
|
||||
}
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
private static CodeReviewCommit parse(RevWalk rw, ObjectId id)
|
||||
throws MissingObjectException, IncorrectObjectTypeException,
|
||||
IOException {
|
||||
return (CodeReviewCommit) rw.parseCommit(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MergeabilityWeigher
|
||||
implements Weigher<EntryKey, Boolean> {
|
||||
@Override
|
||||
public int weigh(EntryKey k, Boolean v) {
|
||||
return 16 + 2 * (16 + 20) + 3 * 8 // Size of EntryKey, 64-bit JVM.
|
||||
+ 8; // Size of Boolean.
|
||||
}
|
||||
}
|
||||
|
||||
private final LoadingCache<EntryKey, Boolean> cache;
|
||||
|
||||
@Inject
|
||||
MergeabilityCache(@Named(CACHE_NAME) LoadingCache<EntryKey, Boolean> cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public boolean get(ObjectId commit, Ref intoRef, SubmitType submitType,
|
||||
String mergeStrategy, Branch.NameKey dest, Repository repo, ReviewDb db) {
|
||||
ObjectId into = intoRef != null ? intoRef.getObjectId() : ObjectId.zeroId();
|
||||
EntryKey key =
|
||||
new EntryKey(commit, into, submitType, mergeStrategy, dest, repo, db);
|
||||
try {
|
||||
return cache.get(key);
|
||||
} catch (ExecutionException e) {
|
||||
log.error(String.format("Error checking mergeability of %s into %s (%s)",
|
||||
key.commit.name(), key.into.name(), key.submitType.name()),
|
||||
e.getCause());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
String mergeStrategy, Branch.NameKey dest, Repository repo, ReviewDb db);
|
||||
|
||||
public boolean getIfPresent(ObjectId commit, Ref intoRef,
|
||||
SubmitType submitType, String mergeStrategy) {
|
||||
return cache.getIfPresent(new EntryKey(
|
||||
commit, toId(intoRef), submitType, mergeStrategy, null, null, null));
|
||||
}
|
||||
SubmitType submitType, String mergeStrategy);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user