com.google.gerrit.server.submit.LocalMergeSuperSetComputation Maven / Gradle / Ivy
The newest version!
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.submit;
import static com.google.common.base.Preconditions.checkState;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.SubmitTypeRecord;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeIsVisibleToPredicate;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.submit.MergeOpRepoManager.OpenRepo;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
/**
* Default implementation of MergeSuperSet that does the computation of the merge super set
* sequentially on the local Gerrit instance.
*/
public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
public static final int MAX_SUBMITTABLE_CHANGES_AT_ONCE_DEFAULT = 1024;
public static class LocalMergeSuperSetComputationModule extends AbstractModule {
@Override
protected void configure() {
DynamicItem.bind(binder(), MergeSuperSetComputation.class)
.to(LocalMergeSuperSetComputation.class);
}
}
@AutoValue
abstract static class QueryKey {
private static QueryKey create(BranchNameKey branch, Iterable hashes) {
return new AutoValue_LocalMergeSuperSetComputation_QueryKey(
branch, ImmutableSet.copyOf(hashes));
}
abstract BranchNameKey branch();
abstract ImmutableSet hashes();
}
private final Provider queryProvider;
private final Map> queryCache;
private final Map> heads;
private final ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory;
private final int maxSubmittableChangesAtOnce;
@Inject
LocalMergeSuperSetComputation(
Provider queryProvider,
ChangeIsVisibleToPredicate.Factory changeIsVisibleToPredicateFactory,
@GerritServerConfig Config gerritConfig) {
this.queryProvider = queryProvider;
this.queryCache = new HashMap<>();
this.heads = new HashMap<>();
this.changeIsVisibleToPredicateFactory = changeIsVisibleToPredicateFactory;
this.maxSubmittableChangesAtOnce =
gerritConfig.getInt(
"change", "maxSubmittableAtOnce", MAX_SUBMITTABLE_CHANGES_AT_ONCE_DEFAULT);
}
@Override
public ChangeSet completeWithoutTopic(
MergeOpRepoManager orm, ChangeSet changeSet, CurrentUser user) throws IOException {
List visibleChanges = new ArrayList<>();
List nonVisibleChanges = new ArrayList<>();
// For each target branch we run a separate rev walk to find open changes
// reachable from changes already in the merge super set.
ImmutableSet branches =
byBranch(Iterables.concat(changeSet.changes(), changeSet.nonVisibleChanges())).keySet();
ImmutableListMultimap visibleChangesPerBranch =
byBranch(changeSet.changes());
ImmutableListMultimap nonVisibleChangesPerBranch =
byBranch(changeSet.nonVisibleChanges());
for (BranchNameKey branchNameKey : branches) {
OpenRepo or = getRepo(orm, branchNameKey.project());
List visibleCommits = new ArrayList<>();
List nonVisibleCommits = new ArrayList<>();
for (ChangeData cd : visibleChangesPerBranch.get(branchNameKey)) {
if (submitType(cd) == SubmitType.CHERRY_PICK) {
visibleChanges.add(cd);
} else {
visibleCommits.add(or.rw.parseCommit(cd.currentPatchSet().commitId()));
}
}
for (ChangeData cd : nonVisibleChangesPerBranch.get(branchNameKey)) {
if (submitType(cd) == SubmitType.CHERRY_PICK) {
nonVisibleChanges.add(cd);
} else {
nonVisibleCommits.add(or.rw.parseCommit(cd.currentPatchSet().commitId()));
}
}
Set visibleHashes =
walkChangesByHashes(
visibleCommits,
Collections.emptySet(),
or,
branchNameKey,
maxSubmittableChangesAtOnce);
Set nonVisibleHashes =
walkChangesByHashes(
nonVisibleCommits, visibleHashes, or, branchNameKey, maxSubmittableChangesAtOnce);
ChangeSet partialSet =
byCommitsOnBranchNotMerged(or, branchNameKey, visibleHashes, nonVisibleHashes, user);
Iterables.addAll(visibleChanges, partialSet.changes());
Iterables.addAll(nonVisibleChanges, partialSet.nonVisibleChanges());
}
return new ChangeSet(visibleChanges, nonVisibleChanges);
}
private static ImmutableListMultimap byBranch(
Iterable changes) {
ImmutableListMultimap.Builder builder =
ImmutableListMultimap.builder();
for (ChangeData cd : changes) {
builder.put(cd.change().getDest(), cd);
}
return builder.build();
}
private OpenRepo getRepo(MergeOpRepoManager orm, Project.NameKey project) throws IOException {
try {
OpenRepo or = orm.getRepo(project);
checkState(or.rw.hasRevSort(RevSort.TOPO));
return or;
} catch (NoSuchProjectException e) {
throw new IOException(e);
}
}
private SubmitType submitType(ChangeData cd) {
SubmitTypeRecord str = cd.submitTypeRecord();
if (!str.isOk()) {
logErrorAndThrow("Failed to get submit type for " + cd.getId() + ": " + str.errorMessage);
}
return str.type;
}
@UsedAt(UsedAt.Project.GOOGLE)
public ChangeSet byCommitsOnBranchNotMerged(
OpenRepo or,
BranchNameKey branch,
Set visibleHashes,
Set nonVisibleHashes,
CurrentUser user)
throws IOException {
ImmutableList potentiallyVisibleChanges =
byCommitsOnBranchNotMerged(or, branch, visibleHashes);
List invisibleChanges =
new ArrayList<>(byCommitsOnBranchNotMerged(or, branch, nonVisibleHashes));
List visibleChanges = new ArrayList<>(potentiallyVisibleChanges.size());
ChangeIsVisibleToPredicate changeIsVisibleToPredicate =
changeIsVisibleToPredicateFactory.forUser(user);
for (ChangeData cd : potentiallyVisibleChanges) {
// short circuit permission checks for non-private changes, as we already checked all
// permissions (except for private changes).
if (!cd.change().isPrivate() || changeIsVisibleToPredicate.match(cd)) {
visibleChanges.add(cd);
} else {
invisibleChanges.add(cd);
}
}
return new ChangeSet(visibleChanges, invisibleChanges);
}
private ImmutableList byCommitsOnBranchNotMerged(
OpenRepo or, BranchNameKey branch, Set hashes) throws IOException {
if (hashes.isEmpty()) {
return ImmutableList.of();
}
QueryKey k = QueryKey.create(branch, hashes);
if (queryCache.containsKey(k)) {
return queryCache.get(k);
}
ImmutableList result =
ImmutableList.copyOf(
queryProvider.get().byCommitsOnBranchNotMerged(or.repo, branch, hashes));
queryCache.put(k, result);
return result;
}
@UsedAt(UsedAt.Project.GOOGLE)
public Set walkChangesByHashes(
Collection sourceCommits,
Set ignoreHashes,
OpenRepo or,
BranchNameKey b,
int limit)
throws IOException {
Set destHashes = new HashSet<>();
or.rw.reset();
markHeadUninteresting(or, b);
for (RevCommit c : sourceCommits) {
String name = c.name();
if (ignoreHashes.contains(name)) {
continue;
}
if (destHashes.size() < limit) {
destHashes.add(name);
} else {
break;
}
or.rw.markStart(c);
}
for (RevCommit c : or.rw) {
String name = c.name();
if (ignoreHashes.contains(name)) {
continue;
}
if (destHashes.size() < limit) {
destHashes.add(name);
} else {
break;
}
}
return destHashes;
}
private void markHeadUninteresting(OpenRepo or, BranchNameKey b) throws IOException {
Optional head = heads.get(b);
if (head == null) {
Ref ref = or.repo.getRefDatabase().exactRef(b.branch());
head = ref != null ? Optional.of(or.rw.parseCommit(ref.getObjectId())) : Optional.empty();
heads.put(b, head);
}
if (head.isPresent()) {
or.rw.markUninteresting(head.get());
}
}
private void logErrorAndThrow(String msg) {
logger.atSevere().log("%s", msg);
throw new StorageException(msg);
}
}