Extract an ApprovalCopier from ApprovalsUtil
Rewrite in such a way that we can do an implicit "copy" between arbitrary patch sets without necessarily storing the results. For the notedb, we will use this (with cached ChangeKinds) rather than storing copied labels. This introduces a few minor behavior changes. First, copied approvals are now normalized according to permissions. This may result in different values stored in the database when copying, but these values were already normalized out by ChangeJson and the submit rule evaluator, so this should not result in a semantic change. Second, we no longer pass in a "source" patch set; all prior patch sets are considered. This has the advantage that it does not rely on labels having been properly copied to intermediate patch sets. It also means, for example, if patch set 3 has a code change relative to patch set 2 but not patch set 1, approvals on a copyAllScoresOnNoCodeChange label will be copied from patch set 1, bypassing patch set 2. We think this is a more correct state of affairs: if patch set 1 is good enough to submit, so should patch set 3, regardless of the fact that there is an intermediate patch set. Change-Id: I56ae707c34ac7b978b06a0624c1353e8f28286a7
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
// 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 com.google.gerrit.server.change.ChangeKind.NO_CODE_CHANGE;
|
||||
import static com.google.gerrit.server.change.ChangeKind.TRIVIAL_REBASE;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Table;
|
||||
import com.google.gerrit.common.data.LabelType;
|
||||
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.ChangeKind;
|
||||
import com.google.gerrit.server.change.ChangeKindCache;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.LabelNormalizer;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
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 org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class ApprovalCopier {
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final ProjectCache projectCache;
|
||||
private final ChangeKindCache changeKindCache;
|
||||
private final LabelNormalizer labelNormalizer;
|
||||
private final ChangeData.Factory changeDataFactory;
|
||||
|
||||
@Inject
|
||||
ApprovalCopier(GitRepositoryManager repoManager,
|
||||
ProjectCache projectCache,
|
||||
ChangeKindCache changeKindCache,
|
||||
LabelNormalizer labelNormalizer,
|
||||
ChangeData.Factory changeDataFactory) {
|
||||
this.repoManager = repoManager;
|
||||
this.projectCache = projectCache;
|
||||
this.changeKindCache = changeKindCache;
|
||||
this.labelNormalizer = labelNormalizer;
|
||||
this.changeDataFactory = changeDataFactory;
|
||||
}
|
||||
|
||||
public void copy(ReviewDb db, ChangeControl ctl, PatchSet.Id psId)
|
||||
throws OrmException {
|
||||
db.patchSetApprovals().insert(getForPatchSet(db, ctl, psId));
|
||||
}
|
||||
|
||||
private List<PatchSetApproval> getForPatchSet(ReviewDb db, ChangeControl ctl,
|
||||
PatchSet.Id psId) throws OrmException {
|
||||
ChangeData cd = changeDataFactory.create(db, ctl);
|
||||
try {
|
||||
ProjectState project =
|
||||
projectCache.checkedGet(cd.change().getDest().getParentKey());
|
||||
ListMultimap<PatchSet.Id, PatchSetApproval> all = cd.allApprovalsMap();
|
||||
|
||||
Table<String, Account.Id, PatchSetApproval> byUser =
|
||||
HashBasedTable.create();
|
||||
for (PatchSetApproval psa : all.get(psId)) {
|
||||
byUser.put(psa.getLabel(), psa.getAccountId(), psa);
|
||||
}
|
||||
|
||||
TreeMap<Integer, PatchSet> patchSets = getPatchSets(cd);
|
||||
NavigableSet<Integer> allPsIds = patchSets.navigableKeySet();
|
||||
PatchSet currPs = patchSets.get(psId.get());
|
||||
if (currPs == null) {
|
||||
throw new OrmException("missing patch set " + psId);
|
||||
}
|
||||
|
||||
Repository repo =
|
||||
repoManager.openRepository(project.getProject().getNameKey());
|
||||
try {
|
||||
// Walk patch sets strictly less than psId in descending order.
|
||||
for (PatchSet priorPs
|
||||
: patchSets.descendingMap().tailMap(psId.get(), false).values()) {
|
||||
List<PatchSetApproval> priorApprovals = all.get(priorPs.getId());
|
||||
if (priorApprovals.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ChangeKind kind = changeKindCache.getChangeKind(project, repo,
|
||||
ObjectId.fromString(priorPs.getRevision().get()),
|
||||
ObjectId.fromString(currPs.getRevision().get()));
|
||||
|
||||
for (PatchSetApproval psa : priorApprovals) {
|
||||
if (!byUser.contains(psa.getLabel(), psa.getAccountId())
|
||||
&& canCopy(project, psa, psId, allPsIds, kind)) {
|
||||
byUser.put(psa.getLabel(), psa.getAccountId(), copy(psa, psId));
|
||||
}
|
||||
}
|
||||
}
|
||||
return labelNormalizer.normalize(ctl, byUser.values());
|
||||
} finally {
|
||||
repo.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new OrmException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeMap<Integer, PatchSet> getPatchSets(ChangeData cd)
|
||||
throws OrmException {
|
||||
Collection<PatchSet> patchSets = cd.patches();
|
||||
TreeMap<Integer, PatchSet> result = Maps.newTreeMap();
|
||||
for (PatchSet ps : patchSets) {
|
||||
result.put(ps.getId().get(), ps);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean canCopy(ProjectState project, PatchSetApproval psa,
|
||||
PatchSet.Id psId, NavigableSet<Integer> allPsIds, ChangeKind kind)
|
||||
throws OrmException {
|
||||
int n = psa.getKey().getParentKey().get();
|
||||
checkArgument(n != psId.get());
|
||||
LabelType type = project.getLabelTypes().byLabel(psa.getLabelId());
|
||||
if (type == null) {
|
||||
return false;
|
||||
} else if (Objects.equal(n, previous(allPsIds, psId.get())) && (
|
||||
type.isCopyMinScore() && type.isMaxNegative(psa)
|
||||
|| type.isCopyMaxScore() && type.isMaxPositive(psa))) {
|
||||
// Copy min/max score only from the immediately preceding patch set (which
|
||||
// may not be psId.get() - 1).
|
||||
return true;
|
||||
}
|
||||
return (type.isCopyAllScoresOnTrivialRebase() && kind == TRIVIAL_REBASE)
|
||||
|| (type.isCopyAllScoresIfNoCodeChange() && kind == NO_CODE_CHANGE);
|
||||
}
|
||||
|
||||
private static PatchSetApproval copy(PatchSetApproval src, PatchSet.Id psId) {
|
||||
if (src.getKey().getParentKey().equals(psId)) {
|
||||
return src;
|
||||
}
|
||||
return new PatchSetApproval(psId, src);
|
||||
}
|
||||
|
||||
private static <T> T previous(NavigableSet<T> s, T v) {
|
||||
SortedSet<T> head = s.headSet(v);
|
||||
return !head.isEmpty() ? head.last() : null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user