Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.gerrit.server.change.ChangeKindCacheImpl Maven / Gradle / Ivy
package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
import com.google.common.cache.Weigher;
import com.google.common.collect.FluentIterable;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.proto.Protos;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.gerrit.server.cache.serialize.EnumCacheSerializer;
import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.InMemoryInserter;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.name.Named;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
public class ChangeKindCacheImpl implements ChangeKindCache {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final String ID_CACHE = "change_kind" ;
public static Module module () {
return new CacheModule() {
@Override
protected void configure () {
bind(ChangeKindCache.class).to(ChangeKindCacheImpl.class);
persist(ID_CACHE, Key.class, ChangeKind.class)
.maximumWeight(2 << 20 )
.weigher(ChangeKindWeigher.class)
.version(1 )
.keySerializer(new Key.Serializer())
.valueSerializer(new EnumCacheSerializer<>(ChangeKind.class));
}
};
}
public static class NoCache implements ChangeKindCache {
private final boolean useRecursiveMerge;
private final ChangeData.Factory changeDataFactory;
private final GitRepositoryManager repoManager;
@Inject
NoCache(
@GerritServerConfig Config serverConfig,
ChangeData.Factory changeDataFactory,
GitRepositoryManager repoManager) {
this .useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
this .changeDataFactory = changeDataFactory;
this .repoManager = repoManager;
}
@Override
public ChangeKind getChangeKind (
Project.NameKey project,
@Nullable RevWalk rw,
@Nullable Config repoConfig,
ObjectId prior,
ObjectId next) {
try {
Key key = Key.create(prior, next, useRecursiveMerge);
return new Loader(key, repoManager, project, rw, repoConfig).call();
} catch (IOException e) {
logger.atWarning().withCause(e).log(
"Cannot check trivial rebase of new patch set %s in %s" , next.name(), project);
return ChangeKind.REWORK;
}
}
@Override
public ChangeKind getChangeKind (Change change, PatchSet patch) {
return getChangeKindInternal(this , change, patch, changeDataFactory, repoManager);
}
@Override
public ChangeKind getChangeKind (
@Nullable RevWalk rw, @Nullable Config repoConfig, ChangeData cd, PatchSet patch) {
return getChangeKindInternal(this , rw, repoConfig, cd, patch);
}
}
@AutoValue
public abstract static class Key {
public static Key create (AnyObjectId prior, AnyObjectId next, String strategyName) {
return new AutoValue_ChangeKindCacheImpl_Key(prior.copy(), next.copy(), strategyName);
}
private static Key create (AnyObjectId prior, AnyObjectId next, boolean useRecursiveMerge) {
return create(prior, next, MergeUtil.mergeStrategyName(true , useRecursiveMerge));
}
public abstract ObjectId prior () ;
public abstract ObjectId next () ;
public abstract String strategyName () ;
@VisibleForTesting
static class Serializer implements CacheSerializer {
@Override
public byte [] serialize(Key object) {
ObjectIdConverter idConverter = ObjectIdConverter.create();
return Protos.toByteArray(
ChangeKindKeyProto.newBuilder()
.setPrior(idConverter.toByteString(object.prior()))
.setNext(idConverter.toByteString(object.next()))
.setStrategyName(object.strategyName())
.build());
}
@Override
public Key deserialize (byte [] in) {
ChangeKindKeyProto proto = Protos.parseUnchecked(ChangeKindKeyProto.parser(), in);
ObjectIdConverter idConverter = ObjectIdConverter.create();
return create(
idConverter.fromByteString(proto.getPrior()),
idConverter.fromByteString(proto.getNext()),
proto.getStrategyName());
}
}
}
private static class Loader implements Callable {
private final Key key;
private final GitRepositoryManager repoManager;
private final Project.NameKey projectName;
private final RevWalk alreadyOpenRw;
private final Config repoConfig;
private Loader (
Key key,
GitRepositoryManager repoManager,
Project.NameKey projectName,
@Nullable RevWalk rw,
@Nullable Config repoConfig) {
checkArgument(
(rw == null && repoConfig == null ) || (rw != null && repoConfig != null ),
"must either provide both revwalk/config, or neither; got %s/%s" ,
rw,
repoConfig);
this .key = key;
this .repoManager = repoManager;
this .projectName = projectName;
this .alreadyOpenRw = rw;
this .repoConfig = repoConfig;
}
@SuppressWarnings ("resource" )
@Override
public ChangeKind call () throws IOException {
if (Objects.equals(key.prior(), key.next())) {
return ChangeKind.NO_CODE_CHANGE;
}
RevWalk rw = alreadyOpenRw;
Config config = repoConfig;
Repository repo = null ;
if (alreadyOpenRw == null ) {
repo = repoManager.openRepository(projectName);
rw = new RevWalk(repo);
config = repo.getConfig();
}
try {
RevCommit prior = rw.parseCommit(key.prior());
rw.parseBody(prior);
RevCommit next = rw.parseCommit(key.next());
rw.parseBody(next);
if (!next.getFullMessage().equals(prior.getFullMessage())) {
if (isSameDeltaAndTree(rw, prior, next)) {
return ChangeKind.NO_CODE_CHANGE;
}
return ChangeKind.REWORK;
}
if (isSameDeltaAndTree(rw, prior, next)) {
return ChangeKind.NO_CHANGE;
}
if (prior.getParentCount() == 0 || next.getParentCount() == 0 ) {
return ChangeKind.REWORK;
}
if ((prior.getParentCount() > 1 || next.getParentCount() > 1 )
&& !onlyFirstParentChanged(prior, next)) {
return ChangeKind.REWORK;
}
try (ObjectInserter ins = new InMemoryInserter(rw.getObjectReader())) {
ThreeWayMerger merger = MergeUtil.newThreeWayMerger(ins, config, key.strategyName());
merger.setBase(prior.getParent(0 ));
if (merger.merge(next.getParent(0 ), prior)
&& merger.getResultTreeId().equals(next.getTree())) {
if (prior.getParentCount() == 1 ) {
return ChangeKind.TRIVIAL_REBASE;
}
return ChangeKind.MERGE_FIRST_PARENT_UPDATE;
}
} catch (LargeObjectException e) {
}
return ChangeKind.REWORK;
} finally {
if (repo != null ) {
rw.close();
repo.close();
}
}
}
public static boolean onlyFirstParentChanged (RevCommit prior, RevCommit next) {
return !sameFirstParents(prior, next) && sameRestOfParents(prior, next);
}
private static boolean sameFirstParents (RevCommit prior, RevCommit next) {
if (prior.getParentCount() == 0 ) {
return next.getParentCount() == 0 ;
}
return prior.getParent(0 ).equals(next.getParent(0 ));
}
private static boolean sameRestOfParents (RevCommit prior, RevCommit next) {
Set priorRestParents = allExceptFirstParent(prior.getParents());
Set nextRestParents = allExceptFirstParent(next.getParents());
return priorRestParents.equals(nextRestParents);
}
private static Set allExceptFirstParent (RevCommit[] parents) {
return FluentIterable.from(Arrays.asList(parents)).skip(1 ).toSet();
}
private static boolean isSameDeltaAndTree (RevWalk rw, RevCommit prior, RevCommit next)
throws IOException {
if (!Objects.equals(next.getTree(), prior.getTree())) {
return false ;
}
if (prior.getParentCount() != next.getParentCount()) {
return false ;
} else if (prior.getParentCount() == 0 ) {
return true ;
}
for (int i = 0 ; i < prior.getParentCount(); i++) {
rw.parseCommit(prior.getParent(i));
rw.parseCommit(next.getParent(i));
if (!Objects.equals(next.getParent(i).getTree(), prior.getParent(i).getTree())) {
return false ;
}
}
return true ;
}
}
public static class ChangeKindWeigher implements Weigher {
@Override
public int weigh (Key key, ChangeKind changeKind) {
return 16
+ 2 * 36
+ 2 * key.strategyName().length()
+ 2 * changeKind.name().length();
}
}
private final Cache cache;
private final boolean useRecursiveMerge;
private final ChangeData.Factory changeDataFactory;
private final GitRepositoryManager repoManager;
@Inject
ChangeKindCacheImpl(
@GerritServerConfig Config serverConfig,
@Named (ID_CACHE) Cache cache,
ChangeData.Factory changeDataFactory,
GitRepositoryManager repoManager) {
this .cache = cache;
this .useRecursiveMerge = MergeUtil.useRecursiveMerge(serverConfig);
this .changeDataFactory = changeDataFactory;
this .repoManager = repoManager;
}
@Override
public ChangeKind getChangeKind (
Project.NameKey project,
@Nullable RevWalk rw,
@Nullable Config repoConfig,
ObjectId prior,
ObjectId next) {
try {
Key key = Key.create(prior, next, useRecursiveMerge);
ChangeKind kind = cache.get(key, new Loader(key, repoManager, project, rw, repoConfig));
logger.atFine().log("Change kind of new patch set %s in %s: %s" , next.name(), project, kind);
return kind;
} catch (ExecutionException e) {
logger.atWarning().withCause(e).log(
"Cannot check change kind of new patch set %s in %s" , next.name(), project);
return ChangeKind.REWORK;
}
}
@Override
public ChangeKind getChangeKind (Change change, PatchSet patch) {
return getChangeKindInternal(this , change, patch, changeDataFactory, repoManager);
}
@Override
public ChangeKind getChangeKind (
@Nullable RevWalk rw, @Nullable Config repoConfig, ChangeData cd, PatchSet patch) {
return getChangeKindInternal(this , rw, repoConfig, cd, patch);
}
private static ChangeKind getChangeKindInternal (
ChangeKindCache cache,
@Nullable RevWalk rw,
@Nullable Config repoConfig,
ChangeData change,
PatchSet patch) {
ChangeKind kind = ChangeKind.REWORK;
if (patch.id().get() > 1 ) {
try {
Collection patchSetCollection = change.patchSets();
PatchSet priorPs = patch;
for (PatchSet ps : patchSetCollection) {
if (ps.id().get() < patch.id().get()
&& (ps.id().get() > priorPs.id().get() || priorPs == patch)) {
priorPs = ps;
}
}
if (priorPs != patch) {
kind =
cache.getChangeKind(
change.project(), rw, repoConfig, priorPs.commitId(), patch.commitId());
}
} catch (StorageException e) {
logger.atWarning().withCause(e).log(
"Unable to get change kind for patchSet %s of change %s" ,
patch.number(), change.getId());
}
}
logger.atFine().log(
"Change kind for patchSet %s of change %s: %s" , patch.number(), change.getId(), kind);
return kind;
}
private static ChangeKind getChangeKindInternal (
ChangeKindCache cache,
Change change,
PatchSet patch,
ChangeData.Factory changeDataFactory,
GitRepositoryManager repoManager) {
ChangeKind kind = ChangeKind.REWORK;
if (patch.id().get() > 1 ) {
try (Repository repo = repoManager.openRepository(change.getProject());
RevWalk rw = new RevWalk(repo)) {
kind =
getChangeKindInternal(
cache, rw, repo.getConfig(), changeDataFactory.create(change), patch);
} catch (IOException e) {
logger.atWarning().withCause(e).log(
"Unable to get change kind for patchSet %s of change %s" ,
patch.number(), change.getChangeId());
}
}
logger.atFine().log(
"Change kind for patchSet %s of change %s: %s" , patch.number(), change.getChangeId(), kind);
return kind;
}
}