New ssh command approve
This command allows for scoring and commenting of patchsets via the Gerrit ssh interface.
This commit is contained in:
@@ -156,7 +156,7 @@ public abstract class BaseCommand implements Command {
|
|||||||
list.add(r.toString());
|
list.add(r.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
final CmdLineParser clp = new CmdLineParser(this);
|
final CmdLineParser clp = newCmdLineParserInstance(this);
|
||||||
try {
|
try {
|
||||||
clp.parseArgument(list.toArray(new String[list.size()]));
|
clp.parseArgument(list.toArray(new String[list.size()]));
|
||||||
} catch (CmdLineException err) {
|
} catch (CmdLineException err) {
|
||||||
@@ -178,6 +178,10 @@ public abstract class BaseCommand implements Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected CmdLineParser newCmdLineParserInstance(Object bean) {
|
||||||
|
return new CmdLineParser(bean);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawn a function into its own thread.
|
* Spawn a function into its own thread.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
// Copyright (C) 2009 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.ssh.commands;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.data.ApprovalType;
|
||||||
|
import com.google.gerrit.client.data.ApprovalTypes;
|
||||||
|
import com.google.gerrit.client.reviewdb.ApprovalCategory;
|
||||||
|
import com.google.gerrit.client.reviewdb.ApprovalCategoryValue;
|
||||||
|
import com.google.gerrit.client.reviewdb.Change;
|
||||||
|
import com.google.gerrit.client.reviewdb.ChangeMessage;
|
||||||
|
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||||
|
import com.google.gerrit.client.reviewdb.PatchSetApproval;
|
||||||
|
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.pgm.CmdLineParser;
|
||||||
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.mail.CommentSender;
|
||||||
|
import com.google.gerrit.server.mail.EmailException;
|
||||||
|
import com.google.gerrit.server.mail.CommentSender.Factory;
|
||||||
|
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||||
|
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
|
import com.google.gerrit.server.ssh.BaseCommand;
|
||||||
|
import com.google.gerrit.server.workflow.FunctionState;
|
||||||
|
import com.google.gwtorm.client.OrmException;
|
||||||
|
import com.google.gwtorm.client.Transaction;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
import org.kohsuke.args4j.Argument;
|
||||||
|
import org.kohsuke.args4j.Option;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ApproveCommand extends BaseCommand {
|
||||||
|
static {
|
||||||
|
CmdLineParser.registerHandler(PatchSet.Id.class, PatchSetIdHandler.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final CmdLineParser newCmdLineParserInstance(final Object bean) {
|
||||||
|
CmdLineParser parser = new CmdLineParser(bean);
|
||||||
|
|
||||||
|
for (CmdOption c : optionList) {
|
||||||
|
parser.addOption(c, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int CMD_ERR = 3;
|
||||||
|
|
||||||
|
@Argument(index = 0, required = true, usage = "Patch set to approve")
|
||||||
|
private PatchSet.Id patchSetId;
|
||||||
|
@Option(name = "--message", aliases = "-m", usage = "Message to put on change/patchset", metaVar = "MESSAGE")
|
||||||
|
private String changeComment;
|
||||||
|
@Inject
|
||||||
|
private ReviewDb db;
|
||||||
|
@Inject
|
||||||
|
private IdentifiedUser currentUser;
|
||||||
|
@Inject
|
||||||
|
private Factory commentSenderFactory;
|
||||||
|
@Inject
|
||||||
|
private PatchSetInfoFactory patchSetInfoFactory;
|
||||||
|
@Inject
|
||||||
|
private ApprovalTypes approvalTypes;
|
||||||
|
@Inject
|
||||||
|
private ChangeControl.Factory changeControlFactory;
|
||||||
|
@Inject
|
||||||
|
private FunctionState.Factory functionStateFactory;
|
||||||
|
|
||||||
|
|
||||||
|
private List<CmdOption> optionList;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void start() throws IOException {
|
||||||
|
startThread(new CommandRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
getApprovalNames();
|
||||||
|
parseCommandLine();
|
||||||
|
|
||||||
|
final Transaction txn = db.beginTransaction();
|
||||||
|
|
||||||
|
final PatchSet ps = db.patchSets().get(patchSetId);
|
||||||
|
|
||||||
|
if (ps == null) {
|
||||||
|
throw new UnloggedFailure(CMD_ERR, "Invalid patchset id");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Change.Id cid = ps.getId().getParentKey();
|
||||||
|
final ChangeControl control = changeControlFactory.validateFor(cid);
|
||||||
|
final Change c = control.getChange();
|
||||||
|
|
||||||
|
if (c.getStatus().isClosed()) {
|
||||||
|
throw new UnloggedFailure(CMD_ERR, "Change is closed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append("Patch Set ");
|
||||||
|
sb.append(patchSetId.get());
|
||||||
|
sb.append(": ");
|
||||||
|
|
||||||
|
for (CmdOption co : optionList) {
|
||||||
|
ApprovalCategory.Id category =
|
||||||
|
new ApprovalCategory.Id(co.approvalKey());
|
||||||
|
PatchSetApproval.Key psaKey =
|
||||||
|
new PatchSetApproval.Key(patchSetId, currentUser
|
||||||
|
.getAccountId(), category);
|
||||||
|
PatchSetApproval psa = db.patchSetApprovals().get(psaKey);
|
||||||
|
|
||||||
|
Short score = co.value();
|
||||||
|
|
||||||
|
if (score != null) {
|
||||||
|
addApproval(psaKey, score, c, co, txn);
|
||||||
|
} else {
|
||||||
|
if (psa == null) {
|
||||||
|
score = 0;
|
||||||
|
addApproval(psaKey, score, c, co, txn);
|
||||||
|
} else {
|
||||||
|
score = psa.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String message =
|
||||||
|
db.approvalCategoryValues().get(
|
||||||
|
new ApprovalCategoryValue.Id(category, score)).getName();
|
||||||
|
sb.append(" " + message + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.deleteCharAt(sb.length() - 1);
|
||||||
|
sb.append("\n\n");
|
||||||
|
|
||||||
|
if (changeComment != null) {
|
||||||
|
sb.append(changeComment);
|
||||||
|
}
|
||||||
|
|
||||||
|
String uuid = ChangeUtil.messageUUID(db);
|
||||||
|
ChangeMessage cm =
|
||||||
|
new ChangeMessage(new ChangeMessage.Key(cid, uuid), currentUser
|
||||||
|
.getAccountId());
|
||||||
|
cm.setMessage(sb.toString());
|
||||||
|
db.changeMessages().insert(Collections.singleton(cm), txn);
|
||||||
|
ChangeUtil.updated(c);
|
||||||
|
db.changes().update(Collections.singleton(c), txn);
|
||||||
|
txn.commit();
|
||||||
|
sendMail(c, c.currentPatchSetId(), cm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMail(final Change c, final PatchSet.Id psid,
|
||||||
|
final ChangeMessage message) throws PatchSetInfoNotAvailableException,
|
||||||
|
EmailException, OrmException {
|
||||||
|
PatchSet ps = db.patchSets().get(psid);
|
||||||
|
final CommentSender cm;
|
||||||
|
cm = commentSenderFactory.create(c);
|
||||||
|
cm.setFrom(currentUser.getAccountId());
|
||||||
|
cm.setPatchSet(ps, patchSetInfoFactory.get(psid));
|
||||||
|
cm.setChangeMessage(message);
|
||||||
|
cm.setReviewDb(db);
|
||||||
|
cm.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addApproval(final PatchSetApproval.Key psaKey,
|
||||||
|
final Short score, final Change c, final CmdOption co,
|
||||||
|
final Transaction txn) throws OrmException,
|
||||||
|
UnloggedFailure {
|
||||||
|
PatchSetApproval psa = db.patchSetApprovals().get(psaKey);
|
||||||
|
boolean insert = false;
|
||||||
|
|
||||||
|
if (psa == null) {
|
||||||
|
insert = true;
|
||||||
|
psa = new PatchSetApproval(psaKey, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PatchSetApproval> approvals = Collections.emptyList();
|
||||||
|
final FunctionState fs =
|
||||||
|
functionStateFactory.create(c, patchSetId, approvals);
|
||||||
|
psa.setValue(score);
|
||||||
|
fs.normalize(
|
||||||
|
approvalTypes.getApprovalType(psa.getCategoryId()), psa);
|
||||||
|
if (score != psa.getValue()) {
|
||||||
|
throw new UnloggedFailure(CMD_ERR, co.name() + "=" + co.value()
|
||||||
|
+ " not permitted");
|
||||||
|
}
|
||||||
|
|
||||||
|
psa.setGranted();
|
||||||
|
|
||||||
|
if (insert) {
|
||||||
|
db.patchSetApprovals().insert(Collections.singleton(psa), txn);
|
||||||
|
} else {
|
||||||
|
db.patchSetApprovals().update(Collections.singleton(psa), txn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getApprovalNames() throws OrmException {
|
||||||
|
optionList = new ArrayList<CmdOption>();
|
||||||
|
|
||||||
|
for (ApprovalType type : approvalTypes.getApprovalTypes()) {
|
||||||
|
String usage = "";
|
||||||
|
final ApprovalCategory category = type.getCategory();
|
||||||
|
usage = "Score for " + category.getName() + "\n";
|
||||||
|
|
||||||
|
for (ApprovalCategoryValue v : type.getValues()) {
|
||||||
|
usage +=
|
||||||
|
String.format("%4d", v.getValue()) + " - " + v.getName() + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
optionList.add(
|
||||||
|
new CmdOption(
|
||||||
|
"--" + category.getName().toLowerCase().replace(' ', '-'), usage,
|
||||||
|
category.getId().get(), type.getMin().getValue(),
|
||||||
|
type.getMax().getValue(), category.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (C) 2009 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.ssh.commands;
|
||||||
|
|
||||||
|
import org.kohsuke.args4j.CmdLineException;
|
||||||
|
import org.kohsuke.args4j.Option;
|
||||||
|
import org.kohsuke.args4j.spi.OptionHandler;
|
||||||
|
import org.kohsuke.args4j.spi.Setter;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
|
class CmdOption implements Option, Setter {
|
||||||
|
private String metaVar;
|
||||||
|
private boolean multiValued;
|
||||||
|
private String name;
|
||||||
|
private boolean required;
|
||||||
|
private String usage;
|
||||||
|
|
||||||
|
private String approvalKey;
|
||||||
|
private Short approvalMax;
|
||||||
|
private Short approvalMin;
|
||||||
|
private String descrName;
|
||||||
|
|
||||||
|
private Short value;
|
||||||
|
|
||||||
|
public CmdOption(final String name, final String usage, final String key,
|
||||||
|
final Short min, final Short max, final String descrName) {
|
||||||
|
this.name = name;
|
||||||
|
this.usage = usage;
|
||||||
|
|
||||||
|
this.metaVar = "";
|
||||||
|
this.multiValued = false;
|
||||||
|
this.required = false;
|
||||||
|
this.value = null;
|
||||||
|
|
||||||
|
this.approvalKey = key;
|
||||||
|
this.approvalMax = max;
|
||||||
|
this.approvalMin = min;
|
||||||
|
this.descrName = descrName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String[] aliases() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Class<? extends OptionHandler> handler() {
|
||||||
|
return OptionHandler.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String metaVar() {
|
||||||
|
return metaVar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean multiValued() {
|
||||||
|
return multiValued;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean required() {
|
||||||
|
return required;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String usage() {
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Short value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String approvalKey() {
|
||||||
|
return approvalKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Short approvalMax() {
|
||||||
|
return approvalMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Short approvalMin() {
|
||||||
|
return approvalMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String descrName() {
|
||||||
|
return descrName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Annotation> annotationType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addValue(final Object value) throws CmdLineException {
|
||||||
|
Short val = (Short) value;
|
||||||
|
if (val < approvalMin || val > approvalMax) {
|
||||||
|
throw new CmdLineException(name() + " valid values are "
|
||||||
|
+ approvalMin.toString() + ".." + approvalMax.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.value = (Short) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class getType() {
|
||||||
|
return Short.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiValued() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ public class DefaultCommandModule extends CommandModule {
|
|||||||
final CommandName gerrit = Commands.named("gerrit");
|
final CommandName gerrit = Commands.named("gerrit");
|
||||||
|
|
||||||
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
|
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
|
||||||
|
command(gerrit, "approve").to(ApproveCommand.class);
|
||||||
command(gerrit, "create-project").to(AdminCreateProject.class);
|
command(gerrit, "create-project").to(AdminCreateProject.class);
|
||||||
command(gerrit, "flush-caches").to(AdminFlushCaches.class);
|
command(gerrit, "flush-caches").to(AdminFlushCaches.class);
|
||||||
command(gerrit, "ls-projects").to(ListProjects.class);
|
command(gerrit, "ls-projects").to(ListProjects.class);
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2009 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.ssh.commands;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||||
|
|
||||||
|
import org.kohsuke.args4j.CmdLineException;
|
||||||
|
import org.kohsuke.args4j.CmdLineParser;
|
||||||
|
import org.kohsuke.args4j.OptionDef;
|
||||||
|
import org.kohsuke.args4j.spi.OptionHandler;
|
||||||
|
import org.kohsuke.args4j.spi.Parameters;
|
||||||
|
import org.kohsuke.args4j.spi.Setter;
|
||||||
|
|
||||||
|
public class PatchSetIdHandler extends OptionHandler<PatchSet.Id> {
|
||||||
|
public PatchSetIdHandler(final CmdLineParser parser, final OptionDef option,
|
||||||
|
final Setter<? super PatchSet.Id> setter) {
|
||||||
|
super(parser, option, setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int parseArguments(final Parameters params)
|
||||||
|
throws CmdLineException {
|
||||||
|
final String idString = params.getParameter(0);
|
||||||
|
final PatchSet.Id id;
|
||||||
|
try {
|
||||||
|
id = PatchSet.Id.parse(idString);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new CmdLineException("Invalid patch set: " + idString);
|
||||||
|
}
|
||||||
|
|
||||||
|
setter.addValue(id);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String getDefaultMetaVariable() {
|
||||||
|
return "CHANGE,PATCHSET";
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user