blob: 8cc92ac9ec52317fc99697dcfd2a4bc67a88b3ab [file] [log] [blame]
// 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.googlesource.gerrit.plugins.findowners;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.AccountAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.rules.StoredValues;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.googlecode.prolog_cafe.lang.Prolog;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Check if a change needs owner approval. */
public class Checker {
private static final Logger log = LoggerFactory.getLogger(Checker.class);
// Accept both "Exempt-" and "Exempted-".
private static final String EXEMPT_MESSAGE1 = "Exempt-From-Owner-Approval:";
private static final String EXEMPT_MESSAGE2 = "Exempted-From-Owner-Approval:";
private Repository repository;
private ChangeData changeData;
private int minVoteLevel;
Checker(Repository repository, ChangeData changeData, int v) {
this.repository = repository;
this.changeData = changeData;
minVoteLevel = v;
}
/** Returns a map from reviewer email to vote value. */
static Map<String, Integer> getVotes(ChangeData changeData) throws OrmException {
ReviewDb db = changeData.db();
Map<String, Integer> map = new HashMap<>();
AccountAccess ac = db.accounts();
for (PatchSetApproval p : changeData.currentApprovals()) {
if (p.getValue() != 0) {
Account.Id id = p.getAccountId();
try {
map.put(ac.get(id).getPreferredEmail(), Integer.valueOf(p.getValue()));
} catch (OrmException e) {
log.error("Cannot get email address of account id: " + id.get(), e);
}
}
}
return map;
}
/** Returns true if some owner in owners is "*" or in votes */
boolean findOwnersInVotes(Set<String> owners, Map<String, Integer> votes) {
boolean foundVeto = false;
boolean foundApproval = false;
for (String owner : owners) {
if (votes.containsKey(owner)) {
int v = votes.get(owner);
// TODO: Maybe add a configurable feature in the next version
// to exclude the committer's vote from the "foundApproval".
foundApproval |= (v >= minVoteLevel);
foundVeto |= (v < 0); // an owner's -1 vote is a veto
} else if (owner.equals("*")) {
foundApproval = true; // no specific owner
}
}
return foundApproval && !foundVeto;
}
/** Returns 1 if owner approval is found, -1 if missing, 0 if unneeded. */
int findApproval(OwnersDb db) throws OrmException {
Map<String, Set<String>> file2Owners = db.findOwners(changeData.currentFilePaths());
if (file2Owners.size() == 0) { // do not need owner approval
return 0;
}
Map<String, Integer> votes = getVotes(changeData);
for (Set<String> owners : file2Owners.values()) {
if (!findOwnersInVotes(owners, votes)) {
return -1;
}
}
return 1;
}
/** Returns 1 if owner approval is found, -1 if missing, 0 if unneeded. */
public static int findApproval(Prolog engine, int minVoteLevel) {
try {
ChangeData changeData = StoredValues.CHANGE_DATA.get(engine);
Repository repository = StoredValues.REPOSITORY.get(engine);
return new Checker(repository, changeData, minVoteLevel).findApproval();
} catch (OrmException e) {
log.error("Exception", e);
return 0; // owner approval may or may not be required.
}
}
/** Returns true if exempt from owner approval. */
static boolean isExemptFromOwnerApproval(ChangeData changeData) throws OrmException {
try {
String message = changeData.commitMessage();
if (message.contains(EXEMPT_MESSAGE1) || message.contains(EXEMPT_MESSAGE2)) {
return true;
}
} catch (IOException | OrmException e) {
log.error("Cannot get commit message", e);
return true; // exempt from owner approval due to lack of data
}
// Abandoned and merged changes do not need approval again.
Status status = changeData.change().getStatus();
return (status == Status.ABANDONED || status == Status.MERGED);
}
int findApproval() throws OrmException {
if (isExemptFromOwnerApproval(changeData)) {
return 0;
}
// One update to a Gerrit change can call submit_rule or submit_filter
// many times. So this function should use cached values.
OwnersDb db = Cache.getInstance().get(repository, changeData);
if (db.getNumOwners() <= 0) {
return 0;
}
if (minVoteLevel <= 0) {
minVoteLevel = Config.getMinOwnerVoteLevel(changeData);
}
log.trace("findApproval db key = " + db.key);
return findApproval(db);
}
}