Refactor SSH commands with SshCommand base class
The SshCommand base class extends BaseCommand and provides the common pattern of calling startThread with a CommandRunnable that parses argments and then invokes the real logic. The new annotation @RequiresCapability can be declared on any concrete implementation of SshCommand to name a capability the caller must have before they can run the contained command. This is enforced inside of the DispatchCommand, which is what handles creating and delegating to any paticular command implementation. @AdminCommand is replaced by the new @RequriesCapability annotation, which is more explicitly declaring the administrate server dependency. Most existing commands have been ported to this SshCommand base class, cleaning up a lot of the code. A few special cases still exist such as StreamEvents or ScpCommand that are not trivial to port, as their start implementation is well outside of the common pattern. Plugin authors should be encouraged to extend from SshCommand for the foreseeable future. I eventually would like to get away from needing to extend this class, and instead use a simpler interface declaration, but that is a much bigger change to make with how DispatchCommand and BaseCommand are connected together. Change-Id: I4f1de60c6fdeb207197dfccc135b4d532443d5b2
This commit is contained in:
@@ -246,8 +246,8 @@ public abstract class BaseCommand implements Command {
|
||||
protected synchronized void startThread(final CommandRunnable thunk) {
|
||||
final TaskThunk tt = new TaskThunk(thunk);
|
||||
|
||||
if (isAdminCommand() || (isAdminHighPriorityCommand()
|
||||
&& userProvider.get().getCapabilities().canAdministrateServer())) {
|
||||
if (isAdminHighPriorityCommand()
|
||||
&& userProvider.get().getCapabilities().canAdministrateServer()) {
|
||||
// Admin commands should not block the main work threads (there
|
||||
// might be an interactive shell there), nor should they wait
|
||||
// for the main work threads.
|
||||
@@ -258,10 +258,6 @@ public abstract class BaseCommand implements Command {
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean isAdminCommand() {
|
||||
return getClass().getAnnotation(AdminCommand.class) != null;
|
||||
}
|
||||
|
||||
private final boolean isAdminHighPriorityCommand() {
|
||||
return getClass().getAnnotation(AdminHighPriorityCommand.class) != null;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.sshd;
|
||||
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.CapabilityControl;
|
||||
import com.google.gerrit.sshd.args4j.SubcommandHandler;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
@@ -70,13 +71,7 @@ final class DispatchCommand extends BaseCommand {
|
||||
}
|
||||
|
||||
final Command cmd = p.get();
|
||||
|
||||
if (isAdminCommand(cmd)
|
||||
&& !currentUser.get().getCapabilities().canAdministrateServer()) {
|
||||
final String msg = "fatal: Not a Gerrit administrator";
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
checkRequiresCapability(cmd);
|
||||
if (cmd instanceof BaseCommand) {
|
||||
final BaseCommand bc = (BaseCommand) cmd;
|
||||
if (prefix.isEmpty())
|
||||
@@ -107,8 +102,18 @@ final class DispatchCommand extends BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAdminCommand(final Command cmd) {
|
||||
return cmd.getClass().getAnnotation(AdminCommand.class) != null;
|
||||
private void checkRequiresCapability(Command cmd) throws UnloggedFailure {
|
||||
RequiresCapability rc = cmd.getClass().getAnnotation(RequiresCapability.class);
|
||||
if (rc != null) {
|
||||
CurrentUser user = currentUser.get();
|
||||
CapabilityControl ctl = user.getCapabilities();
|
||||
if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"%s\" capability.",
|
||||
user.getUserName(), rc.value());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
// Copyright (C) 2012 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.
|
||||
@@ -21,12 +21,10 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation tagged on a concrete Command that requires administrator access.
|
||||
* <p>
|
||||
* Currently this annotation is only enforced by DispatchCommand after it has
|
||||
* created the command object, but before it populates it or starts execution.
|
||||
* Annotation on {@link SshCommand} declaring a capability must be granted.
|
||||
*/
|
||||
@Target( {ElementType.TYPE})
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RUNTIME)
|
||||
public @interface AdminCommand {
|
||||
public @interface RequiresCapability {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2012 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.sshd;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public abstract class SshCommand extends BaseCommand {
|
||||
protected PrintWriter stdout;
|
||||
protected PrintWriter stderr;
|
||||
|
||||
@Override
|
||||
public void start(Environment env) throws IOException {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine();
|
||||
stdout = toPrintWriter(out);
|
||||
stderr = toPrintWriter(err);
|
||||
try {
|
||||
SshCommand.this.run();
|
||||
} finally {
|
||||
stdout.flush();
|
||||
stderr.flush();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void run() throws UnloggedFailure, Failure, Exception;
|
||||
}
|
||||
@@ -14,16 +14,18 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.sshd.AdminCommand;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.sshd.AdminHighPriorityCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
/** Opens a query processor. */
|
||||
@AdminCommand
|
||||
final class AdminQueryShell extends BaseCommand {
|
||||
@AdminHighPriorityCommand
|
||||
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
|
||||
final class AdminQueryShell extends SshCommand {
|
||||
@Inject
|
||||
private QueryShell.Factory factory;
|
||||
|
||||
@@ -34,19 +36,13 @@ final class AdminQueryShell extends BaseCommand {
|
||||
private String query;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine();
|
||||
final QueryShell shell = factory.create(in, out);
|
||||
shell.setOutputFormat(format);
|
||||
if (query != null) {
|
||||
shell.execute(query);
|
||||
} else {
|
||||
shell.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
protected void run() {
|
||||
final QueryShell shell = factory.create(in, out);
|
||||
shell.setOutputFormat(format);
|
||||
if (query != null) {
|
||||
shell.execute(query);
|
||||
} else {
|
||||
shell.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.config.AllProjectsName;
|
||||
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||
@@ -21,11 +22,10 @@ import com.google.gerrit.server.git.ProjectConfig;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.sshd.AdminCommand;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
@@ -34,14 +34,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@AdminCommand
|
||||
final class AdminSetParent extends BaseCommand {
|
||||
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
|
||||
final class AdminSetParent extends SshCommand {
|
||||
private static final Logger log = LoggerFactory.getLogger(AdminSetParent.class);
|
||||
|
||||
@Option(name = "--parent", aliases = {"-p"}, metaVar = "NAME", usage = "new parent project")
|
||||
@@ -68,26 +67,10 @@ final class AdminSetParent extends BaseCommand {
|
||||
@Inject
|
||||
private AllProjectsName allProjectsName;
|
||||
|
||||
private PrintWriter stdout;
|
||||
private Project.NameKey newParentKey = null;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
stdout = toPrintWriter(out);
|
||||
try {
|
||||
parseCommandLine();
|
||||
updateParents();
|
||||
} finally {
|
||||
stdout.flush();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateParents() throws Failure {
|
||||
protected void run() throws Failure {
|
||||
if (oldParent == null && children.isEmpty()) {
|
||||
throw new UnloggedFailure(1, "fatal: child projects have to be specified as " +
|
||||
"arguments or the --children-of option has to be set");
|
||||
|
||||
@@ -20,10 +20,9 @@ import com.google.gerrit.server.git.BanCommitResult;
|
||||
import com.google.gerrit.server.git.IncompleteUserInfoException;
|
||||
import com.google.gerrit.server.git.MergeException;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
@@ -33,8 +32,7 @@ import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BanCommitCommand extends BaseCommand {
|
||||
|
||||
public class BanCommitCommand extends SshCommand {
|
||||
@Option(name = "--reason", aliases = {"-r"}, metaVar = "REASON", usage = "reason for banning the commit")
|
||||
private String reason;
|
||||
|
||||
@@ -50,45 +48,30 @@ public class BanCommitCommand extends BaseCommand {
|
||||
private BanCommit.Factory banCommitFactory;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) throws IOException {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine();
|
||||
BanCommitCommand.this.display();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void display() throws Failure {
|
||||
protected void run() throws Failure {
|
||||
try {
|
||||
final BanCommitResult result =
|
||||
banCommitFactory.create().ban(projectControl, commitsToBan, reason);
|
||||
|
||||
final PrintWriter stdout = toPrintWriter(out);
|
||||
try {
|
||||
final List<ObjectId> newlyBannedCommits =
|
||||
result.getNewlyBannedCommits();
|
||||
if (!newlyBannedCommits.isEmpty()) {
|
||||
stdout.print("The following commits were banned:\n");
|
||||
printCommits(stdout, newlyBannedCommits);
|
||||
}
|
||||
final List<ObjectId> newlyBannedCommits =
|
||||
result.getNewlyBannedCommits();
|
||||
if (!newlyBannedCommits.isEmpty()) {
|
||||
stdout.print("The following commits were banned:\n");
|
||||
printCommits(stdout, newlyBannedCommits);
|
||||
}
|
||||
|
||||
final List<ObjectId> alreadyBannedCommits =
|
||||
result.getAlreadyBannedCommits();
|
||||
if (!alreadyBannedCommits.isEmpty()) {
|
||||
stdout.print("The following commits were already banned:\n");
|
||||
printCommits(stdout, alreadyBannedCommits);
|
||||
}
|
||||
final List<ObjectId> alreadyBannedCommits =
|
||||
result.getAlreadyBannedCommits();
|
||||
if (!alreadyBannedCommits.isEmpty()) {
|
||||
stdout.print("The following commits were already banned:\n");
|
||||
printCommits(stdout, alreadyBannedCommits);
|
||||
}
|
||||
|
||||
final List<ObjectId> ignoredIds = result.getIgnoredObjectIds();
|
||||
if (!ignoredIds.isEmpty()) {
|
||||
stdout.print("The following ids do not represent commits"
|
||||
+ " and were ignored:\n");
|
||||
printCommits(stdout, ignoredIds);
|
||||
}
|
||||
} finally {
|
||||
stdout.flush();
|
||||
final List<ObjectId> ignoredIds = result.getIgnoredObjectIds();
|
||||
if (!ignoredIds.isEmpty()) {
|
||||
stdout.print("The following ids do not represent commits"
|
||||
+ " and were ignored:\n");
|
||||
printCommits(stdout, ignoredIds);
|
||||
}
|
||||
} catch (PermissionDeniedException e) {
|
||||
throw die(e);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.ehcache.EhcachePoolImpl;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import net.sf.ehcache.CacheManager;
|
||||
@@ -25,7 +25,7 @@ import java.util.Arrays;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
abstract class CacheCommand extends BaseCommand {
|
||||
abstract class CacheCommand extends SshCommand {
|
||||
@Inject
|
||||
protected EhcachePoolImpl cachePool;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.errors.InvalidSshKeyException;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||
@@ -26,12 +27,12 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountByEmailCache;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.ssh.SshKeyCache;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
@@ -45,7 +46,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/** Create a new user account. **/
|
||||
final class CreateAccountCommand extends BaseCommand {
|
||||
@RequiresCapability(GlobalCapability.CREATE_ACCOUNT)
|
||||
final class CreateAccountCommand extends SshCommand {
|
||||
@Option(name = "--group", aliases = {"-g"}, metaVar = "GROUP", usage = "groups to add account to")
|
||||
private List<AccountGroup.Id> groups = new ArrayList<AccountGroup.Id>();
|
||||
|
||||
@@ -77,24 +79,7 @@ final class CreateAccountCommand extends BaseCommand {
|
||||
private AccountByEmailCache byEmailCache;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canCreateAccount()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"Create Account\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
createAccount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createAccount() throws OrmException, IOException,
|
||||
protected void run() throws OrmException, IOException,
|
||||
InvalidSshKeyException, UnloggedFailure {
|
||||
if (!username.matches(Account.USER_NAME_PATTERN)) {
|
||||
throw die("Username '" + username + "'"
|
||||
|
||||
@@ -14,15 +14,17 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.errors.NameAlreadyUsedException;
|
||||
import com.google.gerrit.common.errors.PermissionDeniedException;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.server.account.PerformCreateGroup;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
@@ -34,7 +36,8 @@ import java.util.Set;
|
||||
* <p>
|
||||
* Optionally, puts an initial set of user in the newly created group.
|
||||
*/
|
||||
final class CreateGroupCommand extends BaseCommand {
|
||||
@RequiresCapability(GlobalCapability.CREATE_GROUP)
|
||||
final class CreateGroupCommand extends SshCommand {
|
||||
@Option(name = "--owner", aliases = {"-o"}, metaVar = "GROUP", usage = "owning group, if not specified the group will be self-owning")
|
||||
private AccountGroup.Id ownerGroupId;
|
||||
|
||||
@@ -65,25 +68,19 @@ final class CreateGroupCommand extends BaseCommand {
|
||||
private PerformCreateGroup.Factory performCreateGroupFactory;
|
||||
|
||||
@Override
|
||||
public void start(Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine();
|
||||
try {
|
||||
performCreateGroupFactory.create().createGroup(groupName,
|
||||
groupDescription,
|
||||
visibleToAll,
|
||||
ownerGroupId,
|
||||
initialMembers,
|
||||
initialGroups);
|
||||
} catch (PermissionDeniedException e) {
|
||||
throw die(e);
|
||||
protected void run() throws Failure, OrmException {
|
||||
try {
|
||||
performCreateGroupFactory.create().createGroup(groupName,
|
||||
groupDescription,
|
||||
visibleToAll,
|
||||
ownerGroupId,
|
||||
initialMembers,
|
||||
initialGroups);
|
||||
} catch (PermissionDeniedException e) {
|
||||
throw die(e);
|
||||
|
||||
} catch (NameAlreadyUsedException e) {
|
||||
throw die(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (NameAlreadyUsedException e) {
|
||||
throw die(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,28 +14,28 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.errors.ProjectCreationFailedException;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.Project.SubmitType;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.project.CreateProject;
|
||||
import com.google.gerrit.server.project.CreateProjectArgs;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.project.SuggestParentCandidates;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
/** Create a new project. **/
|
||||
final class CreateProjectCommand extends BaseCommand {
|
||||
@RequiresCapability(GlobalCapability.CREATE_PROJECT)
|
||||
final class CreateProjectCommand extends SshCommand {
|
||||
@Option(name = "--name", aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created (deprecated option)")
|
||||
void setProjectNameFromOption(String name) {
|
||||
if (projectName != null) {
|
||||
@@ -95,9 +95,6 @@ final class CreateProjectCommand extends BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
private IdentifiedUser currentUser;
|
||||
|
||||
@Inject
|
||||
private CreateProject.Factory CreateProjectFactory;
|
||||
|
||||
@@ -105,55 +102,39 @@ final class CreateProjectCommand extends BaseCommand {
|
||||
private SuggestParentCandidates.Factory suggestParentCandidatesFactory;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canCreateProject()) {
|
||||
String msg =
|
||||
String.format(
|
||||
"fatal: %s does not have \"Create Project\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
protected void run() throws Exception {
|
||||
try {
|
||||
if (!suggestParent) {
|
||||
if (projectName == null) {
|
||||
throw new UnloggedFailure(1, "fatal: Project name is required.");
|
||||
}
|
||||
PrintWriter p = toPrintWriter(out);
|
||||
parseCommandLine();
|
||||
try {
|
||||
if (!suggestParent) {
|
||||
if (projectName == null) {
|
||||
throw new UnloggedFailure(1, "fatal: Project name is required.");
|
||||
}
|
||||
final CreateProjectArgs args = new CreateProjectArgs();
|
||||
args.setProjectName(projectName);
|
||||
args.ownerIds = ownerIds;
|
||||
args.newParent = newParent;
|
||||
args.permissionsOnly = permissionsOnly;
|
||||
args.projectDescription = projectDescription;
|
||||
args.submitType = submitType;
|
||||
args.contributorAgreements = contributorAgreements;
|
||||
args.signedOffBy = signedOffBy;
|
||||
args.contentMerge = contentMerge;
|
||||
args.changeIdRequired = requireChangeID;
|
||||
args.branch = branch;
|
||||
args.createEmptyCommit = createEmptyCommit;
|
||||
final CreateProjectArgs args = new CreateProjectArgs();
|
||||
args.setProjectName(projectName);
|
||||
args.ownerIds = ownerIds;
|
||||
args.newParent = newParent;
|
||||
args.permissionsOnly = permissionsOnly;
|
||||
args.projectDescription = projectDescription;
|
||||
args.submitType = submitType;
|
||||
args.contributorAgreements = contributorAgreements;
|
||||
args.signedOffBy = signedOffBy;
|
||||
args.contentMerge = contentMerge;
|
||||
args.changeIdRequired = requireChangeID;
|
||||
args.branch = branch;
|
||||
args.createEmptyCommit = createEmptyCommit;
|
||||
|
||||
final CreateProject createProject =
|
||||
CreateProjectFactory.create(args);
|
||||
createProject.createProject();
|
||||
} else {
|
||||
List<Project.NameKey> parentCandidates =
|
||||
suggestParentCandidatesFactory.create().getNameKeys();
|
||||
final CreateProject createProject =
|
||||
CreateProjectFactory.create(args);
|
||||
createProject.createProject();
|
||||
} else {
|
||||
List<Project.NameKey> parentCandidates =
|
||||
suggestParentCandidatesFactory.create().getNameKeys();
|
||||
|
||||
for (Project.NameKey parent : parentCandidates) {
|
||||
p.print(parent + "\n");
|
||||
}
|
||||
}
|
||||
} catch (ProjectCreationFailedException err) {
|
||||
throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
|
||||
} finally {
|
||||
p.flush();
|
||||
for (Project.NameKey parent : parentCandidates) {
|
||||
stdout.print(parent + "\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (ProjectCreationFailedException err) {
|
||||
throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,21 +14,22 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import net.sf.ehcache.Ehcache;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/** Causes the caches to purge all entries and reload. */
|
||||
@RequiresCapability(GlobalCapability.FLUSH_CACHES)
|
||||
final class FlushCaches extends CacheCommand {
|
||||
private static final String WEB_SESSIONS = "web_sessions";
|
||||
|
||||
@@ -44,27 +45,8 @@ final class FlushCaches extends CacheCommand {
|
||||
@Inject
|
||||
IdentifiedUser currentUser;
|
||||
|
||||
private PrintWriter p;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canFlushCaches()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"Flush Caches\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
flush();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void flush() throws Failure {
|
||||
protected void run() throws Failure {
|
||||
if (caches.contains(WEB_SESSIONS)
|
||||
&& !currentUser.getCapabilities().canAdministrateServer()) {
|
||||
String msg = String.format(
|
||||
@@ -73,7 +55,6 @@ final class FlushCaches extends CacheCommand {
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
p = toPrintWriter(err);
|
||||
if (list) {
|
||||
if (all || caches.size() > 0) {
|
||||
throw error("error: cannot use --list with --all or --cache");
|
||||
@@ -106,10 +87,10 @@ final class FlushCaches extends CacheCommand {
|
||||
|
||||
private void doList() {
|
||||
for (final String name : cacheNames()) {
|
||||
p.print(name);
|
||||
p.print('\n');
|
||||
stderr.print(name);
|
||||
stderr.print('\n');
|
||||
}
|
||||
p.flush();
|
||||
stderr.flush();
|
||||
}
|
||||
|
||||
private void doBulkFlush() {
|
||||
@@ -120,12 +101,12 @@ final class FlushCaches extends CacheCommand {
|
||||
try {
|
||||
c.removeAll();
|
||||
} catch (Throwable e) {
|
||||
p.println("error: cannot flush cache \"" + name + "\": " + e);
|
||||
stderr.println("error: cannot flush cache \"" + name + "\": " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
p.flush();
|
||||
stderr.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,25 +14,24 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.server.git.WorkQueue;
|
||||
import com.google.gerrit.server.git.WorkQueue.Task;
|
||||
import com.google.gerrit.server.util.IdGenerator;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.AdminHighPriorityCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/** Kill a task in the work queue. */
|
||||
final class KillCommand extends BaseCommand {
|
||||
@Inject
|
||||
private IdentifiedUser currentUser;
|
||||
|
||||
@AdminHighPriorityCommand
|
||||
@RequiresCapability(GlobalCapability.KILL_TASK)
|
||||
final class KillCommand extends SshCommand {
|
||||
@Inject
|
||||
private WorkQueue workQueue;
|
||||
|
||||
@@ -48,33 +47,14 @@ final class KillCommand extends BaseCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canKillTask()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"Kill Task\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
KillCommand.this.commitMurder();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void commitMurder() {
|
||||
final PrintWriter p = toPrintWriter(err);
|
||||
protected void run() {
|
||||
for (final Integer id : taskIds) {
|
||||
final Task<?> task = workQueue.getTask(id);
|
||||
if (task != null) {
|
||||
task.cancel(true);
|
||||
} else {
|
||||
p.print("kill: " + IdGenerator.format(id) + ": No such task\n");
|
||||
stderr.print("kill: " + IdGenerator.format(id) + ": No such task\n");
|
||||
}
|
||||
}
|
||||
p.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,20 +22,16 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.VisibleGroups;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ListGroupsCommand extends BaseCommand {
|
||||
|
||||
public class ListGroupsCommand extends SshCommand {
|
||||
@Inject
|
||||
private VisibleGroups.Factory visibleGroupsFactory;
|
||||
|
||||
@@ -57,18 +53,7 @@ public class ListGroupsCommand extends BaseCommand {
|
||||
private Account.Id user;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) throws IOException {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine();
|
||||
ListGroupsCommand.this.display();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void display() throws Failure {
|
||||
final PrintWriter stdout = toPrintWriter(out);
|
||||
protected void run() throws Failure {
|
||||
try {
|
||||
if (user != null && !projects.isEmpty()) {
|
||||
throw new UnloggedFailure(1, "fatal: --user and --project options are not compatible.");
|
||||
@@ -92,8 +77,6 @@ public class ListGroupsCommand extends BaseCommand {
|
||||
throw die(e);
|
||||
} catch (NoSuchGroupException e) {
|
||||
throw die(e);
|
||||
} finally {
|
||||
stdout.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,15 @@
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class Query extends BaseCommand {
|
||||
class Query extends SshCommand {
|
||||
@Inject
|
||||
private QueryProcessor processor;
|
||||
|
||||
@@ -75,19 +74,14 @@ class Query extends BaseCommand {
|
||||
private List<String> query;
|
||||
|
||||
@Override
|
||||
public void start(Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
|
||||
parseCommandLine();
|
||||
verifyCommandLine();
|
||||
processor.query(join(query, " "));
|
||||
}
|
||||
});
|
||||
protected void run() throws Exception {
|
||||
processor.query(join(query, " "));
|
||||
}
|
||||
|
||||
private void verifyCommandLine() throws UnloggedFailure {
|
||||
@Override
|
||||
protected void parseCommandLine() throws UnloggedFailure {
|
||||
processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
|
||||
super.parseCommandLine();
|
||||
if (processor.getIncludeFiles() &&
|
||||
!(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
|
||||
throw new UnloggedFailure(1, "--files option needs --patch-sets or --current-patch-set");
|
||||
|
||||
@@ -17,17 +17,13 @@ package com.google.gerrit.sshd.commands;
|
||||
import com.google.gerrit.common.errors.NameAlreadyUsedException;
|
||||
import com.google.gerrit.common.errors.NoSuchGroupException;
|
||||
import com.google.gerrit.server.account.PerformRenameGroup;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RenameGroupCommand extends BaseCommand {
|
||||
|
||||
public class RenameGroupCommand extends SshCommand {
|
||||
@Argument(index = 0, required = true, metaVar = "GROUP", usage = "name of the group to be renamed")
|
||||
private String groupName;
|
||||
|
||||
@@ -38,21 +34,15 @@ public class RenameGroupCommand extends BaseCommand {
|
||||
private PerformRenameGroup.Factory performRenameGroupFactory;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) throws IOException {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine();
|
||||
try {
|
||||
performRenameGroupFactory.create().renameGroup(groupName, newGroupName);
|
||||
} catch (OrmException e) {
|
||||
throw die(e);
|
||||
} catch (NameAlreadyUsedException e) {
|
||||
throw die(e);
|
||||
} catch (NoSuchGroupException e) {
|
||||
throw die(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
protected void run() throws Failure {
|
||||
try {
|
||||
performRenameGroupFactory.create().renameGroup(groupName, newGroupName);
|
||||
} catch (OrmException e) {
|
||||
throw die(e);
|
||||
} catch (NameAlreadyUsedException e) {
|
||||
throw die(e);
|
||||
} catch (NoSuchGroupException e) {
|
||||
throw die(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,16 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.git.PushAllProjectsOp;
|
||||
import com.google.gerrit.server.git.ReplicationQueue;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
@@ -31,7 +32,8 @@ import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Force a project to replicate, again. */
|
||||
final class Replicate extends BaseCommand {
|
||||
@RequiresCapability(GlobalCapability.START_REPLICATION)
|
||||
final class Replicate extends SshCommand {
|
||||
@Option(name = "--all", usage = "push all known projects")
|
||||
private boolean all;
|
||||
|
||||
@@ -54,24 +56,7 @@ final class Replicate extends BaseCommand {
|
||||
private ProjectCache projectCache;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canStartReplication()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"Start Replication\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
Replicate.this.schedule();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void schedule() throws Failure {
|
||||
protected void run() throws Failure {
|
||||
if (all && projectNames.size() > 0) {
|
||||
throw new UnloggedFailure(1, "error: cannot combine --all and PROJECT");
|
||||
}
|
||||
|
||||
@@ -33,13 +33,12 @@ import com.google.gerrit.server.patch.PublishComments;
|
||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.gerrit.util.cli.CmdLineParser;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.slf4j.Logger;
|
||||
@@ -52,7 +51,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ReviewCommand extends BaseCommand {
|
||||
public class ReviewCommand extends SshCommand {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(ReviewCommand.class);
|
||||
|
||||
@@ -130,63 +129,55 @@ public class ReviewCommand extends BaseCommand {
|
||||
private List<ApproveOption> optionList;
|
||||
|
||||
@Override
|
||||
public final void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Failure {
|
||||
initOptionList();
|
||||
parseCommandLine();
|
||||
if (abandonChange) {
|
||||
if (restoreChange) {
|
||||
throw error("abandon and restore actions are mutually exclusive");
|
||||
}
|
||||
if (submitChange) {
|
||||
throw error("abandon and submit actions are mutually exclusive");
|
||||
}
|
||||
if (publishPatchSet) {
|
||||
throw error("abandon and publish actions are mutually exclusive");
|
||||
}
|
||||
if (deleteDraftPatchSet) {
|
||||
throw error("abandon and delete actions are mutually exclusive");
|
||||
}
|
||||
}
|
||||
if (publishPatchSet) {
|
||||
if (restoreChange) {
|
||||
throw error("publish and restore actions are mutually exclusive");
|
||||
}
|
||||
if (submitChange) {
|
||||
throw error("publish and submit actions are mutually exclusive");
|
||||
}
|
||||
if (deleteDraftPatchSet) {
|
||||
throw error("publish and delete actions are mutually exclusive");
|
||||
}
|
||||
}
|
||||
|
||||
boolean ok = true;
|
||||
for (final PatchSet.Id patchSetId : patchSetIds) {
|
||||
try {
|
||||
approveOne(patchSetId);
|
||||
} catch (UnloggedFailure e) {
|
||||
ok = false;
|
||||
writeError("error: " + e.getMessage() + "\n");
|
||||
} catch (NoSuchChangeException e) {
|
||||
ok = false;
|
||||
writeError("no such change " + patchSetId.getParentKey().get());
|
||||
} catch (Exception e) {
|
||||
ok = false;
|
||||
writeError("fatal: internal server error while approving "
|
||||
+ patchSetId + "\n");
|
||||
log.error("internal error while approving " + patchSetId, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
throw new UnloggedFailure(1, "one or more approvals failed;"
|
||||
+ " review output above");
|
||||
}
|
||||
|
||||
protected void run() throws UnloggedFailure {
|
||||
if (abandonChange) {
|
||||
if (restoreChange) {
|
||||
throw error("abandon and restore actions are mutually exclusive");
|
||||
}
|
||||
});
|
||||
if (submitChange) {
|
||||
throw error("abandon and submit actions are mutually exclusive");
|
||||
}
|
||||
if (publishPatchSet) {
|
||||
throw error("abandon and publish actions are mutually exclusive");
|
||||
}
|
||||
if (deleteDraftPatchSet) {
|
||||
throw error("abandon and delete actions are mutually exclusive");
|
||||
}
|
||||
}
|
||||
if (publishPatchSet) {
|
||||
if (restoreChange) {
|
||||
throw error("publish and restore actions are mutually exclusive");
|
||||
}
|
||||
if (submitChange) {
|
||||
throw error("publish and submit actions are mutually exclusive");
|
||||
}
|
||||
if (deleteDraftPatchSet) {
|
||||
throw error("publish and delete actions are mutually exclusive");
|
||||
}
|
||||
}
|
||||
|
||||
boolean ok = true;
|
||||
for (final PatchSet.Id patchSetId : patchSetIds) {
|
||||
try {
|
||||
approveOne(patchSetId);
|
||||
} catch (UnloggedFailure e) {
|
||||
ok = false;
|
||||
writeError("error: " + e.getMessage() + "\n");
|
||||
} catch (NoSuchChangeException e) {
|
||||
ok = false;
|
||||
writeError("no such change " + patchSetId.getParentKey().get());
|
||||
} catch (Exception e) {
|
||||
ok = false;
|
||||
writeError("fatal: internal server error while approving "
|
||||
+ patchSetId + "\n");
|
||||
log.error("internal error while approving " + patchSetId, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
throw new UnloggedFailure(1, "one or more approvals failed;"
|
||||
+ " review output above");
|
||||
}
|
||||
}
|
||||
|
||||
private void approveOne(final PatchSet.Id patchSetId) throws
|
||||
@@ -344,7 +335,8 @@ public class ReviewCommand extends BaseCommand {
|
||||
return projectControl.getProject().getNameKey().equals(change.getProject());
|
||||
}
|
||||
|
||||
private void initOptionList() {
|
||||
@Override
|
||||
protected void parseCommandLine() throws UnloggedFailure {
|
||||
optionList = new ArrayList<ApproveOption>();
|
||||
|
||||
for (ApprovalType type : approvalTypes.getApprovalTypes()) {
|
||||
@@ -360,6 +352,8 @@ public class ReviewCommand extends BaseCommand {
|
||||
"--" + category.getName().toLowerCase().replace(' ', '-');
|
||||
optionList.add(new ApproveOption(name, usage, type));
|
||||
}
|
||||
|
||||
super.parseCommandLine();
|
||||
}
|
||||
|
||||
private void writeError(final String msg) {
|
||||
|
||||
@@ -25,12 +25,11 @@ import com.google.gerrit.server.patch.RemoveReviewer;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.slf4j.Logger;
|
||||
@@ -43,7 +42,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class SetReviewersCommand extends BaseCommand {
|
||||
public class SetReviewersCommand extends SshCommand {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(SetReviewersCommand.class);
|
||||
|
||||
@@ -85,28 +84,21 @@ public class SetReviewersCommand extends BaseCommand {
|
||||
private Set<Change.Id> changes = new HashSet<Change.Id>();
|
||||
|
||||
@Override
|
||||
public final void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Failure {
|
||||
parseCommandLine();
|
||||
|
||||
boolean ok = true;
|
||||
for (Change.Id changeId : changes) {
|
||||
try {
|
||||
ok &= modifyOne(changeId);
|
||||
} catch (Exception err) {
|
||||
ok = false;
|
||||
log.error("Error updating reviewers on change " + changeId, err);
|
||||
writeError("fatal", "internal error while updating " + changeId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
throw error("fatal: one or more updates failed; review output above");
|
||||
}
|
||||
protected void run() throws UnloggedFailure {
|
||||
boolean ok = true;
|
||||
for (Change.Id changeId : changes) {
|
||||
try {
|
||||
ok &= modifyOne(changeId);
|
||||
} catch (Exception err) {
|
||||
ok = false;
|
||||
log.error("Error updating reviewers on change " + changeId, err);
|
||||
writeError("fatal", "internal error while updating " + changeId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
throw error("fatal: one or more updates failed; review output above");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean modifyOne(Change.Id changeId) throws Exception {
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.Version;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.lifecycle.LifecycleListener;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.SitePath;
|
||||
import com.google.gerrit.server.git.WorkQueue;
|
||||
import com.google.gerrit.server.git.WorkQueue.Task;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshDaemon;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@@ -30,13 +30,11 @@ import net.sf.ehcache.config.CacheConfiguration;
|
||||
|
||||
import org.apache.mina.core.service.IoAcceptor;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.eclipse.jgit.storage.file.WindowCacheStatAccessor;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
@@ -47,6 +45,7 @@ import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
/** Show the current cache states. */
|
||||
@RequiresCapability(GlobalCapability.VIEW_CACHES)
|
||||
final class ShowCaches extends CacheCommand {
|
||||
private static volatile long serverStarted;
|
||||
|
||||
@@ -67,9 +66,6 @@ final class ShowCaches extends CacheCommand {
|
||||
@Option(name = "--show-jvm", usage = "show details about the JVM")
|
||||
private boolean showJVM;
|
||||
|
||||
@Inject
|
||||
private IdentifiedUser currentUser;
|
||||
|
||||
@Inject
|
||||
private WorkQueue workQueue;
|
||||
|
||||
@@ -80,42 +76,21 @@ final class ShowCaches extends CacheCommand {
|
||||
@SitePath
|
||||
private File sitePath;
|
||||
|
||||
private PrintWriter p;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canViewCaches()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"View Caches\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
display();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void display() {
|
||||
p = toPrintWriter(out);
|
||||
|
||||
protected void run() {
|
||||
Date now = new Date();
|
||||
p.format(
|
||||
stdout.format(
|
||||
"%-25s %-20s now %16s\n",
|
||||
"Gerrit Code Review",
|
||||
Version.getVersion() != null ? Version.getVersion() : "",
|
||||
new SimpleDateFormat("HH:mm:ss zzz").format(now));
|
||||
p.format(
|
||||
stdout.format(
|
||||
"%-25s %-20s uptime %16s\n",
|
||||
"", "",
|
||||
uptime(now.getTime() - serverStarted));
|
||||
p.print('\n');
|
||||
stdout.print('\n');
|
||||
|
||||
p.print(String.format(//
|
||||
stdout.print(String.format(//
|
||||
"%1s %-18s %-4s|%-20s| %-5s |%-14s|\n" //
|
||||
, "" //
|
||||
, "Name" //
|
||||
@@ -124,7 +99,7 @@ final class ShowCaches extends CacheCommand {
|
||||
, "AvgGet" //
|
||||
, "Hit Ratio" //
|
||||
));
|
||||
p.print(String.format(//
|
||||
stdout.print(String.format(//
|
||||
"%1s %-18s %-4s|%6s %6s %6s| %-5s |%-4s %-4s %-4s|\n" //
|
||||
, "" //
|
||||
, "" //
|
||||
@@ -137,7 +112,7 @@ final class ShowCaches extends CacheCommand {
|
||||
, "Mem" //
|
||||
, "Agg" //
|
||||
));
|
||||
p.print("------------------"
|
||||
stdout.print("------------------"
|
||||
+ "-------+--------------------+----------+--------------+\n");
|
||||
for (final Ehcache cache : getAllCaches()) {
|
||||
final CacheConfiguration cfg = cache.getCacheConfiguration();
|
||||
@@ -146,7 +121,7 @@ final class ShowCaches extends CacheCommand {
|
||||
final long total = stat.getCacheHits() + stat.getCacheMisses();
|
||||
|
||||
if (useDisk) {
|
||||
p.print(String.format(//
|
||||
stdout.print(String.format(//
|
||||
"D %-18s %-4s|%6s %6s %6s| %7s |%4s %4s %4s|\n" //
|
||||
, cache.getName() //
|
||||
, interval(cfg.getTimeToLiveSeconds()) //
|
||||
@@ -159,7 +134,7 @@ final class ShowCaches extends CacheCommand {
|
||||
, percent(stat.getCacheHits(), total) //
|
||||
));
|
||||
} else {
|
||||
p.print(String.format(//
|
||||
stdout.print(String.format(//
|
||||
" %-18s %-4s|%6s %6s %6s| %7s |%4s %4s %4s|\n" //
|
||||
, cache.getName() //
|
||||
, interval(cfg.getTimeToLiveSeconds()) //
|
||||
@@ -171,7 +146,7 @@ final class ShowCaches extends CacheCommand {
|
||||
));
|
||||
}
|
||||
}
|
||||
p.print('\n');
|
||||
stdout.print('\n');
|
||||
|
||||
if (gc) {
|
||||
System.gc();
|
||||
@@ -187,7 +162,7 @@ final class ShowCaches extends CacheCommand {
|
||||
jvmSummary();
|
||||
}
|
||||
|
||||
p.flush();
|
||||
stdout.flush();
|
||||
}
|
||||
|
||||
private void memSummary() {
|
||||
@@ -200,17 +175,17 @@ final class ShowCaches extends CacheCommand {
|
||||
final int jgitOpen = WindowCacheStatAccessor.getOpenFiles();
|
||||
final long jgitBytes = WindowCacheStatAccessor.getOpenBytes();
|
||||
|
||||
p.format("Mem: %s total = %s used + %s free + %s buffers\n",
|
||||
stdout.format("Mem: %s total = %s used + %s free + %s buffers\n",
|
||||
bytes(mTotal),
|
||||
bytes(mInuse - jgitBytes),
|
||||
bytes(mFree),
|
||||
bytes(jgitBytes));
|
||||
p.format(" %s max\n", bytes(mMax));
|
||||
p.format(" %8d open files, %8d cpus available, %8d threads\n",
|
||||
stdout.format(" %s max\n", bytes(mMax));
|
||||
stdout.format(" %8d open files, %8d cpus available, %8d threads\n",
|
||||
jgitOpen,
|
||||
r.availableProcessors(),
|
||||
ManagementFactory.getThreadMXBean().getThreadCount());
|
||||
p.print('\n');
|
||||
stdout.print('\n');
|
||||
}
|
||||
|
||||
private void taskSummary() {
|
||||
@@ -224,7 +199,7 @@ final class ShowCaches extends CacheCommand {
|
||||
case SLEEPING: tasksSleeping++; break;
|
||||
}
|
||||
}
|
||||
p.format(
|
||||
stdout.format(
|
||||
"Tasks: %4d total = %4d running + %4d ready + %4d sleeping\n",
|
||||
tasksTotal,
|
||||
tasksRunning,
|
||||
@@ -245,7 +220,7 @@ final class ShowCaches extends CacheCommand {
|
||||
oldest = Math.min(oldest, s.getCreationTime());
|
||||
}
|
||||
|
||||
p.format(
|
||||
stdout.format(
|
||||
"SSH: %4d users, oldest session started %s ago\n",
|
||||
list.size(),
|
||||
uptime(now - oldest));
|
||||
@@ -254,22 +229,22 @@ final class ShowCaches extends CacheCommand {
|
||||
private void jvmSummary() {
|
||||
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
|
||||
p.format("JVM: %s %s %s\n",
|
||||
stdout.format("JVM: %s %s %s\n",
|
||||
runtimeBean.getVmVendor(),
|
||||
runtimeBean.getVmName(),
|
||||
runtimeBean.getVmVersion());
|
||||
p.format(" on %s %s %s\n", "",
|
||||
stdout.format(" on %s %s %s\n", "",
|
||||
osBean.getName(),
|
||||
osBean.getVersion(),
|
||||
osBean.getArch());
|
||||
try {
|
||||
p.format(" running as %s on %s\n",
|
||||
stdout.format(" running as %s on %s\n",
|
||||
System.getProperty("user.name"),
|
||||
InetAddress.getLocalHost().getHostName());
|
||||
} catch (UnknownHostException e) {
|
||||
}
|
||||
p.format(" cwd %s\n", path(new File(".").getAbsoluteFile().getParentFile()));
|
||||
p.format(" site %s\n", path(sitePath));
|
||||
stdout.format(" cwd %s\n", path(new File(".").getAbsoluteFile().getParentFile()));
|
||||
stdout.format(" site %s\n", path(sitePath));
|
||||
}
|
||||
|
||||
private String path(File file) {
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.util.IdGenerator;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.RequiresCapability;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.gerrit.sshd.SshDaemon;
|
||||
import com.google.gerrit.sshd.SshSession;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.mina.core.service.IoAcceptor;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
@@ -40,39 +40,16 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/** Show the current SSH connections. */
|
||||
final class ShowConnections extends BaseCommand {
|
||||
@RequiresCapability(GlobalCapability.VIEW_CONNECTIONS)
|
||||
final class ShowConnections extends SshCommand {
|
||||
@Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
|
||||
private boolean numeric;
|
||||
|
||||
private PrintWriter p;
|
||||
|
||||
@Inject
|
||||
IdentifiedUser currentUser;
|
||||
|
||||
@Inject
|
||||
private SshDaemon daemon;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canViewConnections()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"View Connections\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
ShowConnections.this.display();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void display() throws Failure {
|
||||
p = toPrintWriter(out);
|
||||
|
||||
protected void run() throws Failure {
|
||||
final IoAcceptor acceptor = daemon.getIoAcceptor();
|
||||
if (acceptor == null) {
|
||||
throw new Failure(1, "fatal: sshd no longer running");
|
||||
@@ -93,9 +70,9 @@ final class ShowConnections extends BaseCommand {
|
||||
});
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
p.print(String.format("%-8s %8s %8s %-15s %s\n", //
|
||||
stdout.print(String.format("%-8s %8s %8s %-15s %s\n", //
|
||||
"Session", "Start", "Idle", "User", "Remote Host"));
|
||||
p.print("--------------------------------------------------------------\n");
|
||||
stdout.print("--------------------------------------------------------------\n");
|
||||
for (final IoSession io : list) {
|
||||
ServerSession s = (ServerSession) ServerSession.getSession(io, true);
|
||||
SshSession sd = s != null ? s.getAttribute(SshSession.KEY) : null;
|
||||
@@ -104,16 +81,14 @@ final class ShowConnections extends BaseCommand {
|
||||
final long start = io.getCreationTime();
|
||||
final long idle = now - io.getLastIoTime();
|
||||
|
||||
p.print(String.format("%8s %8s %8s %-15.15s %.30s\n", //
|
||||
stdout.print(String.format("%8s %8s %8s %-15.15s %.30s\n", //
|
||||
id(sd), //
|
||||
time(now, start), //
|
||||
age(idle), //
|
||||
username(sd), //
|
||||
hostname(remoteAddress)));
|
||||
}
|
||||
p.print("--\n");
|
||||
|
||||
p.flush();
|
||||
stdout.print("--\n");
|
||||
}
|
||||
|
||||
private static String id(final SshSession sd) {
|
||||
|
||||
@@ -23,13 +23,13 @@ import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.util.IdGenerator;
|
||||
import com.google.gerrit.sshd.AdminHighPriorityCommand;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Display the current work queue. */
|
||||
@AdminHighPriorityCommand
|
||||
final class ShowQueue extends BaseCommand {
|
||||
final class ShowQueue extends SshCommand {
|
||||
@Option(name = "-w", usage = "display without line width truncation")
|
||||
private boolean wide;
|
||||
|
||||
@@ -52,12 +52,11 @@ final class ShowQueue extends BaseCommand {
|
||||
@Inject
|
||||
private IdentifiedUser currentUser;
|
||||
|
||||
private PrintWriter p;
|
||||
private int columns = 80;
|
||||
private int taskNameWidth;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
public void start(final Environment env) throws IOException {
|
||||
String s = env.getEnv().get(Environment.ENV_COLUMNS);
|
||||
if (s != null && !s.isEmpty()) {
|
||||
try {
|
||||
@@ -66,19 +65,11 @@ final class ShowQueue extends BaseCommand {
|
||||
columns = 80;
|
||||
}
|
||||
}
|
||||
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine();
|
||||
ShowQueue.this.display();
|
||||
}
|
||||
});
|
||||
super.start(env);
|
||||
}
|
||||
|
||||
private void display() {
|
||||
p = toPrintWriter(out);
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
final List<Task<?>> pending = workQueue.getTasks();
|
||||
Collections.sort(pending, new Comparator<Task<?>>() {
|
||||
public int compare(Task<?> a, Task<?> b) {
|
||||
@@ -103,9 +94,9 @@ final class ShowQueue extends BaseCommand {
|
||||
|
||||
taskNameWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 8 - 4;
|
||||
|
||||
p.print(String.format("%-8s %-12s %-8s %s\n", //
|
||||
stdout.print(String.format("%-8s %-12s %-8s %s\n", //
|
||||
"Task", "State", "", "Command"));
|
||||
p.print("----------------------------------------------"
|
||||
stdout.print("----------------------------------------------"
|
||||
+ "--------------------------------\n");
|
||||
|
||||
int numberOfPendingTasks = 0;
|
||||
@@ -158,7 +149,7 @@ final class ShowQueue extends BaseCommand {
|
||||
|
||||
// Shows information about tasks depending on the user rights
|
||||
if (viewAll || (!hasCustomizedPrint && regularUserCanSee)) {
|
||||
p.print(String.format("%8s %-12s %-8s %s\n", //
|
||||
stdout.print(String.format("%8s %-12s %-8s %s\n", //
|
||||
id(task.getTaskId()), start, "", format(task)));
|
||||
} else if (regularUserCanSee) {
|
||||
if (remoteName == null) {
|
||||
@@ -167,20 +158,18 @@ final class ShowQueue extends BaseCommand {
|
||||
remoteName = remoteName + "/" + projectName;
|
||||
}
|
||||
|
||||
p.print(String.format("%8s %-12s %-8s %s\n", //
|
||||
stdout.print(String.format("%8s %-12s %-8s %s\n", //
|
||||
id(task.getTaskId()), start, "", remoteName));
|
||||
}
|
||||
}
|
||||
p.print("----------------------------------------------"
|
||||
stdout.print("----------------------------------------------"
|
||||
+ "--------------------------------\n");
|
||||
|
||||
if (viewAll) {
|
||||
numberOfPendingTasks = pending.size();
|
||||
}
|
||||
|
||||
p.print(" " + numberOfPendingTasks + " tasks\n");
|
||||
|
||||
p.flush();
|
||||
stdout.print(" " + numberOfPendingTasks + " tasks\n");
|
||||
}
|
||||
|
||||
private static String id(final int id) {
|
||||
|
||||
@@ -15,29 +15,16 @@
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.common.Version;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
final class VersionCommand extends BaseCommand {
|
||||
final class VersionCommand extends SshCommand {
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Failure {
|
||||
parseCommandLine();
|
||||
protected void run() throws Failure {
|
||||
String v = Version.getVersion();
|
||||
if (v == null) {
|
||||
throw new Failure(1, "fatal: version unavailable");
|
||||
}
|
||||
|
||||
String v = Version.getVersion();
|
||||
if (v == null) {
|
||||
throw new Failure(1, "fatal: version unavailable");
|
||||
}
|
||||
|
||||
final PrintWriter stdout = toPrintWriter(out);
|
||||
stdout.println("gerrit version " + v);
|
||||
stdout.flush();
|
||||
}
|
||||
});
|
||||
stdout.println("gerrit version " + v);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user