Most replacements are a trivial one-to-one replacement of the call to checkNotNull with requireNonNull, however there are some calls that make use of checkNotNull's variable argument list and formatting parameters. Replace these with String.format wrapped in a supplier. Using the Java API rather than Guava means we can get rid of the custom implementation, and Edwin's TODO, in AccessSection. Change-Id: I1d4a6b342e3544abdbba4f05ac4308d223b6768b
258 lines
9.2 KiB
Java
258 lines
9.2 KiB
Java
// Copyright (C) 2014 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;
|
|
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static java.util.Objects.requireNonNull;
|
|
|
|
import com.google.common.collect.HashBasedTable;
|
|
import com.google.common.collect.ListMultimap;
|
|
import com.google.common.collect.Table;
|
|
import com.google.gerrit.common.Nullable;
|
|
import com.google.gerrit.common.data.LabelType;
|
|
import com.google.gerrit.extensions.client.ChangeKind;
|
|
import com.google.gerrit.reviewdb.client.Account;
|
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
|
import com.google.gerrit.server.change.ChangeKindCache;
|
|
import com.google.gerrit.server.change.LabelNormalizer;
|
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
|
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
|
|
import com.google.gerrit.server.project.ProjectCache;
|
|
import com.google.gerrit.server.project.ProjectState;
|
|
import com.google.gerrit.server.query.change.ChangeData;
|
|
import com.google.gwtorm.server.OrmException;
|
|
import com.google.inject.Inject;
|
|
import com.google.inject.Singleton;
|
|
import java.io.IOException;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.TreeMap;
|
|
import org.eclipse.jgit.lib.Config;
|
|
import org.eclipse.jgit.lib.ObjectId;
|
|
import org.eclipse.jgit.revwalk.RevWalk;
|
|
|
|
/**
|
|
* Copies approvals between patch sets.
|
|
*
|
|
* <p>The result of a copy may either be stored, as when stamping approvals in the database at
|
|
* submit time, or refreshed on demand, as when reading approvals from the NoteDb.
|
|
*/
|
|
@Singleton
|
|
public class ApprovalCopier {
|
|
private final ProjectCache projectCache;
|
|
private final ChangeKindCache changeKindCache;
|
|
private final LabelNormalizer labelNormalizer;
|
|
private final ChangeData.Factory changeDataFactory;
|
|
private final PatchSetUtil psUtil;
|
|
|
|
@Inject
|
|
ApprovalCopier(
|
|
ProjectCache projectCache,
|
|
ChangeKindCache changeKindCache,
|
|
LabelNormalizer labelNormalizer,
|
|
ChangeData.Factory changeDataFactory,
|
|
PatchSetUtil psUtil) {
|
|
this.projectCache = projectCache;
|
|
this.changeKindCache = changeKindCache;
|
|
this.labelNormalizer = labelNormalizer;
|
|
this.changeDataFactory = changeDataFactory;
|
|
this.psUtil = psUtil;
|
|
}
|
|
|
|
/**
|
|
* Apply approval copy settings from prior PatchSets to a new PatchSet.
|
|
*
|
|
* @param db review database.
|
|
* @param notes change notes for user uploading PatchSet
|
|
* @param ps new PatchSet
|
|
* @param rw open walk that can read the patch set commit; null to open the repo on demand.
|
|
* @param repoConfig repo config used for change kind detection; null to read from repo on demand.
|
|
* @throws OrmException
|
|
*/
|
|
public void copyInReviewDb(
|
|
ReviewDb db,
|
|
ChangeNotes notes,
|
|
PatchSet ps,
|
|
@Nullable RevWalk rw,
|
|
@Nullable Config repoConfig)
|
|
throws OrmException {
|
|
copyInReviewDb(db, notes, ps, rw, repoConfig, Collections.emptyList());
|
|
}
|
|
|
|
/**
|
|
* Apply approval copy settings from prior PatchSets to a new PatchSet.
|
|
*
|
|
* @param db review database.
|
|
* @param notes change notes for user uploading PatchSet
|
|
* @param ps new PatchSet
|
|
* @param rw open walk that can read the patch set commit; null to open the repo on demand.
|
|
* @param repoConfig repo config used for change kind detection; null to read from repo on demand.
|
|
* @param dontCopy PatchSetApprovals indicating which (account, label) pairs should not be copied
|
|
* @throws OrmException
|
|
*/
|
|
public void copyInReviewDb(
|
|
ReviewDb db,
|
|
ChangeNotes notes,
|
|
PatchSet ps,
|
|
@Nullable RevWalk rw,
|
|
@Nullable Config repoConfig,
|
|
Iterable<PatchSetApproval> dontCopy)
|
|
throws OrmException {
|
|
if (PrimaryStorage.of(notes.getChange()) == PrimaryStorage.REVIEW_DB) {
|
|
db.patchSetApprovals().insert(getForPatchSet(db, notes, ps, rw, repoConfig, dontCopy));
|
|
}
|
|
}
|
|
|
|
Iterable<PatchSetApproval> getForPatchSet(
|
|
ReviewDb db,
|
|
ChangeNotes notes,
|
|
PatchSet.Id psId,
|
|
@Nullable RevWalk rw,
|
|
@Nullable Config repoConfig)
|
|
throws OrmException {
|
|
return getForPatchSet(
|
|
db, notes, psId, rw, repoConfig, Collections.<PatchSetApproval>emptyList());
|
|
}
|
|
|
|
Iterable<PatchSetApproval> getForPatchSet(
|
|
ReviewDb db,
|
|
ChangeNotes notes,
|
|
PatchSet.Id psId,
|
|
@Nullable RevWalk rw,
|
|
@Nullable Config repoConfig,
|
|
Iterable<PatchSetApproval> dontCopy)
|
|
throws OrmException {
|
|
PatchSet ps = psUtil.get(db, notes, psId);
|
|
if (ps == null) {
|
|
return Collections.emptyList();
|
|
}
|
|
return getForPatchSet(db, notes, ps, rw, repoConfig, dontCopy);
|
|
}
|
|
|
|
private Iterable<PatchSetApproval> getForPatchSet(
|
|
ReviewDb db,
|
|
ChangeNotes notes,
|
|
PatchSet ps,
|
|
@Nullable RevWalk rw,
|
|
@Nullable Config repoConfig,
|
|
Iterable<PatchSetApproval> dontCopy)
|
|
throws OrmException {
|
|
requireNonNull(ps, "ps should not be null");
|
|
ChangeData cd = changeDataFactory.create(db, notes);
|
|
try {
|
|
ProjectState project = projectCache.checkedGet(cd.change().getDest().getParentKey());
|
|
ListMultimap<PatchSet.Id, PatchSetApproval> all = cd.approvals();
|
|
requireNonNull(all, "all should not be null");
|
|
|
|
Table<String, Account.Id, PatchSetApproval> wontCopy = HashBasedTable.create();
|
|
for (PatchSetApproval psa : dontCopy) {
|
|
wontCopy.put(psa.getLabel(), psa.getAccountId(), psa);
|
|
}
|
|
|
|
Table<String, Account.Id, PatchSetApproval> byUser = HashBasedTable.create();
|
|
for (PatchSetApproval psa : all.get(ps.getId())) {
|
|
if (!wontCopy.contains(psa.getLabel(), psa.getAccountId())) {
|
|
byUser.put(psa.getLabel(), psa.getAccountId(), psa);
|
|
}
|
|
}
|
|
|
|
TreeMap<Integer, PatchSet> patchSets = getPatchSets(cd);
|
|
|
|
// Walk patch sets strictly less than current in descending order.
|
|
Collection<PatchSet> allPrior =
|
|
patchSets.descendingMap().tailMap(ps.getId().get(), false).values();
|
|
for (PatchSet priorPs : allPrior) {
|
|
List<PatchSetApproval> priorApprovals = all.get(priorPs.getId());
|
|
if (priorApprovals.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
ChangeKind kind =
|
|
changeKindCache.getChangeKind(
|
|
project.getNameKey(),
|
|
rw,
|
|
repoConfig,
|
|
ObjectId.fromString(priorPs.getRevision().get()),
|
|
ObjectId.fromString(ps.getRevision().get()));
|
|
|
|
for (PatchSetApproval psa : priorApprovals) {
|
|
if (wontCopy.contains(psa.getLabel(), psa.getAccountId())) {
|
|
continue;
|
|
}
|
|
if (byUser.contains(psa.getLabel(), psa.getAccountId())) {
|
|
continue;
|
|
}
|
|
if (!canCopy(project, psa, ps.getId(), kind)) {
|
|
wontCopy.put(psa.getLabel(), psa.getAccountId(), psa);
|
|
continue;
|
|
}
|
|
byUser.put(psa.getLabel(), psa.getAccountId(), copy(psa, ps.getId()));
|
|
}
|
|
}
|
|
return labelNormalizer.normalize(notes, byUser.values()).getNormalized();
|
|
} catch (IOException e) {
|
|
throw new OrmException(e);
|
|
}
|
|
}
|
|
|
|
private static TreeMap<Integer, PatchSet> getPatchSets(ChangeData cd) throws OrmException {
|
|
Collection<PatchSet> patchSets = cd.patchSets();
|
|
TreeMap<Integer, PatchSet> result = new TreeMap<>();
|
|
for (PatchSet ps : patchSets) {
|
|
result.put(ps.getId().get(), ps);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static boolean canCopy(
|
|
ProjectState project, PatchSetApproval psa, PatchSet.Id psId, ChangeKind kind) {
|
|
int n = psa.getKey().getParentKey().get();
|
|
checkArgument(n != psId.get());
|
|
LabelType type = project.getLabelTypes().byLabel(psa.getLabelId());
|
|
if (type == null) {
|
|
return false;
|
|
} else if ((type.isCopyMinScore() && type.isMaxNegative(psa))
|
|
|| (type.isCopyMaxScore() && type.isMaxPositive(psa))) {
|
|
return true;
|
|
}
|
|
switch (kind) {
|
|
case MERGE_FIRST_PARENT_UPDATE:
|
|
return type.isCopyAllScoresOnMergeFirstParentUpdate();
|
|
case NO_CODE_CHANGE:
|
|
return type.isCopyAllScoresIfNoCodeChange();
|
|
case TRIVIAL_REBASE:
|
|
return type.isCopyAllScoresOnTrivialRebase();
|
|
case NO_CHANGE:
|
|
return type.isCopyAllScoresIfNoChange()
|
|
|| type.isCopyAllScoresOnTrivialRebase()
|
|
|| type.isCopyAllScoresOnMergeFirstParentUpdate()
|
|
|| type.isCopyAllScoresIfNoCodeChange();
|
|
case REWORK:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static PatchSetApproval copy(PatchSetApproval src, PatchSet.Id psId) {
|
|
if (src.getKey().getParentKey().equals(psId)) {
|
|
return src;
|
|
}
|
|
return new PatchSetApproval(psId, src);
|
|
}
|
|
}
|