diff options
author | James Moger <james.moger@gitblit.com> | 2014-03-26 21:33:21 -0400 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2014-04-10 18:58:10 -0400 |
commit | 617909819cd1b955647dd8584036fc7b2a014265 (patch) | |
tree | 0c608817a000fba9f25984b0f37c0167c7c881d6 /src/main | |
parent | ec9703a5aa4bda8d764537ea040e464bd422980b (diff) | |
download | gitblit-617909819cd1b955647dd8584036fc7b2a014265.tar.gz gitblit-617909819cd1b955647dd8584036fc7b2a014265.zip |
Improve command help with formatting and usage examples
Diffstat (limited to 'src/main')
16 files changed, 467 insertions, 180 deletions
diff --git a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java index ccf2586b..a9fe6f0f 100644 --- a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java +++ b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java @@ -113,36 +113,71 @@ public class WelcomeShell implements Factory<Command> { String getMessage() { SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY); UserModel user = client.getUser(); + String hostname = getHostname(); + int port = settings.getInteger(Keys.git.sshPort, 0); + + final String b1 = StringUtils.rightPad("", 72, '═'); + final String b2 = StringUtils.rightPad("", 72, '─'); + final String nl = "\r\n"; StringBuilder msg = new StringBuilder(); - msg.append("\r\n"); - msg.append("Hi "); + msg.append(nl); + msg.append(b1); + msg.append(nl); + msg.append(" "); + msg.append(com.gitblit.Constants.getGitBlitVersion()); + msg.append(nl); + msg.append(b1); + msg.append(nl); + msg.append(nl); + msg.append(" Hi "); msg.append(user.getDisplayName()); - msg.append(", you have successfully connected to Gitblit over SSH"); - msg.append("\r\n"); - msg.append("with client: "); + msg.append(", you have successfully connected over SSH."); + msg.append(nl); + msg.append(nl); + msg.append(" client: "); msg.append(session.getClientVersion()); - msg.append("\r\n"); - msg.append("\r\n"); + msg.append(nl); + msg.append(nl); - msg.append("You may clone a repository with the following Git syntax:\r\n"); - msg.append("\r\n"); + msg.append(b2); + msg.append(nl); + msg.append(nl); + msg.append(" You may clone a repository with the following Git syntax:"); + msg.append(nl); + msg.append(nl); msg.append(" git clone "); - msg.append(formatUrl(user.username)); - msg.append("\r\n"); - msg.append("\r\n"); + msg.append(formatUrl(hostname, port, user.username)); + msg.append(nl); + msg.append(nl); + + msg.append(b2); + msg.append(nl); + msg.append(nl); + + msg.append(" You may upload an SSH public key with the following syntax:"); + msg.append(nl); + msg.append(nl); + + msg.append(String.format(" cat ~/.ssh/id_rsa.pub | ssh -l %s -p %d %s gitblit keys add -", user.username, port, hostname)); + msg.append(nl); + msg.append(nl); + + msg.append(b2); + msg.append(nl); + msg.append(nl); // display the core commands SshCommandFactory cmdFactory = (SshCommandFactory) session.getFactoryManager().getCommandFactory(); DispatchCommand root = cmdFactory.createRootDispatcher(client, ""); - String usage = root.usage().replace("\n", "\r\n"); + String usage = root.usage().replace("\n", nl); msg.append(usage); return msg.toString(); } - private String formatUrl(String username) { + private String getHostname() { String host = null; String url = settings.getString(Keys.web.canonicalUrl, "https://localhost:8443"); if (url != null) { @@ -154,15 +189,17 @@ public class WelcomeShell implements Factory<Command> { if (StringUtils.isEmpty(host)) { host = SystemReader.getInstance().getHostname(); } + return host; + } - int port = settings.getInteger(Keys.git.sshPort, 0); + private String formatUrl(String hostname, int port, String username) { if (port == 22) { // standard port - return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, host); + return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, hostname); } else { // non-standard port return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/REPOSITORY.git", - username, host, port); + username, hostname, port); } } } diff --git a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java index 6a190df6..d24a7163 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/BaseCommand.java @@ -37,7 +37,9 @@ import org.kohsuke.args4j.Option; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.gitblit.Keys; import com.gitblit.utils.IdGenerator; +import com.gitblit.utils.StringUtils; import com.gitblit.utils.WorkQueue; import com.gitblit.utils.WorkQueue.CancelableRunnable; import com.gitblit.utils.cli.CmdLineParser; @@ -200,9 +202,39 @@ public abstract class BaseCommand implements Command, SessionAware { } if (clp.wasHelpRequestedByOption()) { + CommandMetaData meta = getClass().getAnnotation(CommandMetaData.class); + String title = meta.name().toUpperCase() + ": " + meta.description(); + String b = com.gitblit.utils.StringUtils.leftPad("", title.length() + 2, '═'); StringWriter msg = new StringWriter(); - clp.printDetailedUsage(commandName, msg); - msg.write(usage()); + msg.write('\n'); + msg.write(b); + msg.write('\n'); + msg.write(' '); + msg.write(title); + msg.write('\n'); + msg.write(b); + msg.write("\n\n"); + msg.write("USAGE\n"); + msg.write("─────\n"); + msg.write(' '); + msg.write(commandName); + msg.write('\n'); + msg.write(' '); + clp.printSingleLineUsage(msg, null); + msg.write("\n\n"); + msg.write("ARGUMENTS & OPTIONS\n"); + msg.write("───────────────────\n"); + clp.printUsage(msg, null); + msg.write('\n'); + String examples = usage().trim(); + if (!StringUtils.isEmpty(examples)) { + msg.write('\n'); + msg.write("EXAMPLES\n"); + msg.write("────────\n"); + msg.write(examples); + msg.write('\n'); + } + throw new UnloggedFailure(1, msg.toString()); } } @@ -213,9 +245,33 @@ public abstract class BaseCommand implements Command, SessionAware { } public String usage() { + Class<? extends BaseCommand> clazz = getClass(); + if (clazz.isAnnotationPresent(UsageExamples.class)) { + return examples(clazz.getAnnotation(UsageExamples.class).examples()); + } else if (clazz.isAnnotationPresent(UsageExample.class)) { + return examples(clazz.getAnnotation(UsageExample.class)); + } return ""; } + protected String examples(UsageExample... examples) { + int sshPort = getContext().getGitblit().getSettings().getInteger(Keys.git.sshPort, 29418); + String username = getContext().getClient().getUsername(); + String hostname = "localhost"; + String ssh = String.format("ssh -l %s -p %d %s", username, sshPort, hostname); + + StringBuilder sb = new StringBuilder(); + for (UsageExample example : examples) { + sb.append(example.description()).append("\n\n"); + String syntax = example.syntax(); + syntax = syntax.replace("${ssh}", ssh); + syntax = syntax.replace("${username}", username); + syntax = syntax.replace("${cmd}", commandName); + sb.append(" ").append(syntax).append("\n\n"); + } + return sb.toString(); + } + protected void showHelp() throws UnloggedFailure { argv = new String [] { "--help" }; parseCommandLine(); diff --git a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java index b916bb17..6e9a87dd 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/DispatchCommand.java @@ -332,9 +332,9 @@ public abstract class DispatchCommand extends BaseCommand implements ExtensionPo continue; } - String displayName = name; + String displayName = name + (meta.admin() ? "*" : ""); if (commandToAliases.containsKey(meta.name())) { - displayName = name + " (" + Joiner.on(',').join(commandToAliases.get(meta.name())) + ")"; + displayName = name + (meta.admin() ? "*" : "")+ " (" + Joiner.on(',').join(commandToAliases.get(meta.name())) + ")"; } displayNames.put(name, displayName); diff --git a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java index ee464e7c..67e2805f 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java @@ -18,8 +18,12 @@ import java.io.IOException; import java.io.PrintWriter; import org.apache.sshd.server.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class SshCommand extends BaseCommand { + + protected Logger log = LoggerFactory.getLogger(getClass()); protected PrintWriter stdout; protected PrintWriter stderr; diff --git a/src/main/java/com/gitblit/transport/ssh/commands/UsageExample.java b/src/main/java/com/gitblit/transport/ssh/commands/UsageExample.java new file mode 100644 index 00000000..428dfde8 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/UsageExample.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 gitblit.com. + * + * 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.gitblit.transport.ssh.commands; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** +* Annotation tagged on a concrete Command to describe how to use it. +*/ +@Target({ElementType.TYPE}) +@Retention(RUNTIME) +public @interface UsageExample { +String syntax(); +String description() default ""; +} diff --git a/src/main/java/com/gitblit/transport/ssh/commands/UsageExamples.java b/src/main/java/com/gitblit/transport/ssh/commands/UsageExamples.java new file mode 100644 index 00000000..0193a98a --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/commands/UsageExamples.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 gitblit.com. + * + * 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.gitblit.transport.ssh.commands; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** +* Annotation tagged on a concrete Command to describe how to use it. +*/ +@Target({ElementType.TYPE}) +@Retention(RUNTIME) +public @interface UsageExamples { +UsageExample [] examples() default {}; +} diff --git a/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java b/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java index cc0c00a3..5383786a 100644 --- a/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/git/GarbageCollectionCommand.java @@ -25,8 +25,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.transport.ssh.commands.CommandMetaData; +import com.gitblit.transport.ssh.commands.UsageExample; @CommandMetaData(name = "gc", description = "Cleanup unnecessary files and optimize the local repository", admin = true) +@UsageExample(syntax = "${cmd} test/myrepository.git", description = "Garbage collect \"test/myrepository.git\"") public class GarbageCollectionCommand extends BaseGitCommand { private static final Logger log = LoggerFactory.getLogger(GarbageCollectionCommand.class); diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java index 55a87e4f..56f2c355 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/BaseKeyCommand.java @@ -36,7 +36,7 @@ abstract class BaseKeyCommand extends SshCommand { protected List<String> readKeys(List<String> sshKeys) throws UnsupportedEncodingException, IOException { int idx = -1; - if (sshKeys.isEmpty() || ((idx = sshKeys.indexOf("-")) >= 0)) { + if ((idx = sshKeys.indexOf("-")) >= 0) { String sshKey = ""; BufferedReader br = new BufferedReader(new InputStreamReader( in, Charsets.UTF_8)); diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java index 695f0850..f6740349 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/ConfigCommand.java @@ -14,9 +14,17 @@ import com.gitblit.models.ServerSettings; import com.gitblit.models.SettingModel; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.SshCommand; +import com.gitblit.transport.ssh.commands.UsageExample; +import com.gitblit.transport.ssh.commands.UsageExamples; import com.google.common.collect.Maps; @CommandMetaData(name = "config", description = "Administer Gitblit settings", admin = true) +@UsageExamples(examples = { + @UsageExample(syntax = "${cmd} --list", description = "List all settings"), + @UsageExample(syntax = "${cmd} git.sshPort", description = "Describe the git.sshPort setting"), + @UsageExample(syntax = "${cmd} git.sshPort 29418", description = "Set git.sshPort to 29418"), + @UsageExample(syntax = "${cmd} git.sshPort --reset", description = "Reset git.sshPort to it's default value"), +}) public class ConfigCommand extends SshCommand { @Argument(index = 0, metaVar = "KEY", usage = "The setting to describe or update") diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java b/src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java deleted file mode 100644 index 2917b6d2..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/CreateRepository.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014 gitblit.com. - * - * 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.gitblit.transport.ssh.gitblit; - -import org.kohsuke.args4j.Option; - -import com.gitblit.transport.ssh.commands.CommandMetaData; -import com.gitblit.transport.ssh.commands.SshCommand; - -@CommandMetaData(name = "create-repository", description = "Create new GIT repository", admin = true, hidden = true) -public class CreateRepository extends SshCommand { - - @Option(name = "--name", aliases = {"-n"}, required = true, metaVar = "NAME", usage = "name of repository to be created") - private String name; - - @Option(name = "--description", aliases = {"-d"}, metaVar = "DESCRIPTION", usage = "description of repository") - private String repositoryDescription; - - @Override - public void run() { - stdout.println(String.format("Repository <%s> was created", name)); - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java index 86d8a8c9..42000646 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/GitblitDispatcher.java @@ -26,8 +26,6 @@ public class GitblitDispatcher extends DispatchCommand { protected void setup(UserModel user) { // commands in this dispatcher register(user, VersionCommand.class); - register(user, CreateRepository.class); - register(user, SetAccountCommand.class); register(user, ConfigCommand.class); // nested dispatchers diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java index 8c4aa228..61764c42 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java @@ -30,6 +30,7 @@ import com.gitblit.transport.ssh.SshKey; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.transport.ssh.commands.SshCommand; +import com.gitblit.transport.ssh.commands.UsageExample; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; @@ -50,11 +51,12 @@ public class KeysDispatcher extends DispatchCommand { } @CommandMetaData(name = "add", description = "Add an SSH public key to your account") + @UsageExample(syntax = "cat ~/.ssh/id_rsa.pub | ${ssh} ${cmd} -", description = "Upload your SSH public key and add it to your account") public static class AddKey extends BaseKeyCommand { protected final Logger log = LoggerFactory.getLogger(getClass()); - @Argument(metaVar = "<stdin>|KEY", usage = "the key to add") + @Argument(metaVar = "-|<KEY>", usage = "the key(s) to add", required = true) private List<String> addKeys = new ArrayList<String>(); @Override @@ -70,6 +72,7 @@ public class KeysDispatcher extends DispatchCommand { } @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove an SSH public key from your account") + @UsageExample(syntax = "${cmd} 2", description = "Remove the SSH key identified as #2 in `keys list`") public static class RemoveKey extends BaseKeyCommand { protected final Logger log = LoggerFactory.getLogger(getClass()); @@ -131,7 +134,7 @@ public class KeysDispatcher extends DispatchCommand { } } - @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered public keys") + @CommandMetaData(name = "list", aliases = { "ls" }, description = "List your registered SSH public keys") public static class ListKeys extends SshCommand { @Option(name = "-L", usage = "list complete public key parameters") diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java index 4be60abc..f2fbabbe 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java @@ -23,6 +23,7 @@ import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.transport.ssh.commands.ListFilterCommand; +import com.gitblit.transport.ssh.commands.UsageExample; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; @@ -38,6 +39,7 @@ public class RepositoriesDispatcher extends DispatchCommand { /* List repositories */ @CommandMetaData(name = "list", aliases = { "ls" }, description = "List repositories") + @UsageExample(syntax = "${cmd} mirror/.* -v", description = "Verbose list of all repositories in the 'mirror' directory") public static class ListRepositories extends ListFilterCommand<RepositoryModel> { @Override @@ -72,7 +74,7 @@ public class RepositoriesDispatcher extends DispatchCommand { String size = r.size; if (!r.hasCommits) { lm = ""; - size = "(empty)"; + size = FlipTable.EMPTY; } if (verbose) { String owners = ""; diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java b/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java deleted file mode 100644 index 3f98778a..00000000 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/SetAccountCommand.java +++ /dev/null @@ -1,88 +0,0 @@ -//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.gitblit.transport.ssh.gitblit; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - -import com.gitblit.transport.ssh.SshKey; -import com.gitblit.transport.ssh.commands.CommandMetaData; - -/** Set a user's account settings. **/ -@CommandMetaData(name = "set-account", description = "Change an account's settings", admin = true) -public class SetAccountCommand extends BaseKeyCommand { - - private static final String ALL = "ALL"; - - @Argument(index = 0, required = true, metaVar = "USER", usage = "full name, email-address, ssh username or account id") - private String user; - - @Option(name = "--add-ssh-key", metaVar = "-|KEY", usage = "public keys to add to the account") - private List<String> addSshKeys = new ArrayList<String>(); - - @Option(name = "--delete-ssh-key", metaVar = "-|KEY", usage = "public keys to delete from the account") - private List<String> deleteSshKeys = new ArrayList<String>(); - - @Override - public void run() throws IOException, UnloggedFailure { - validate(); - setAccount(); - } - - private void validate() throws UnloggedFailure { - if (addSshKeys.contains("-") && deleteSshKeys.contains("-")) { - throw new UnloggedFailure(1, "Only one option may use the stdin"); - } - if (deleteSshKeys.contains(ALL)) { - deleteSshKeys = Collections.singletonList(ALL); - } - } - - private void setAccount() throws IOException, UnloggedFailure { - addSshKeys = readKeys(addSshKeys); - if (!addSshKeys.isEmpty()) { - addSshKeys(addSshKeys); - } - - deleteSshKeys = readKeys(deleteSshKeys); - if (!deleteSshKeys.isEmpty()) { - deleteSshKeys(deleteSshKeys); - } - } - - private void addSshKeys(List<String> keys) throws UnloggedFailure, - IOException { - for (String key : keys) { - SshKey sshKey = new SshKey(key); - getKeyManager().addKey(user, sshKey); - } - } - - private void deleteSshKeys(List<String> keys) { - if (keys.contains(ALL)) { - getKeyManager().removeAllKeys(user); - } else { - for (String key : keys) { - SshKey sshKey = new SshKey(key); - getKeyManager().removeKey(user, sshKey); - } - } - } -} diff --git a/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java index bed966da..d892d9a0 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/UsersDispatcher.java @@ -15,75 +15,308 @@ */ package com.gitblit.transport.ssh.gitblit; +import java.util.ArrayList; import java.util.List; import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import com.gitblit.Constants.AccessPermission; import com.gitblit.manager.IGitblit; import com.gitblit.models.RegistrantAccessPermission; +import com.gitblit.models.RepositoryModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.commands.CommandMetaData; import com.gitblit.transport.ssh.commands.DispatchCommand; import com.gitblit.transport.ssh.commands.ListFilterCommand; import com.gitblit.transport.ssh.commands.SshCommand; +import com.gitblit.transport.ssh.commands.UsageExample; +import com.gitblit.transport.ssh.commands.UsageExamples; +import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FlipTable; import com.gitblit.utils.FlipTable.Borders; +import com.gitblit.utils.StringUtils; +import com.google.common.base.Joiner; @CommandMetaData(name = "users", description = "User management commands", admin = true) public class UsersDispatcher extends DispatchCommand { @Override protected void setup(UserModel user) { + // primary user commands + register(user, NewUser.class); + register(user, RemoveUser.class); register(user, ShowUser.class); register(user, ListUsers.class); + + // user-specific commands + register(user, SetName.class); + register(user, Permissions.class); + register(user, DisableUser.class); + register(user, EnableUser.class); } - @CommandMetaData(name = "show", description = "Show a user") - public static class ShowUser extends SshCommand { + public static abstract class UserCommand extends SshCommand { @Argument(index = 0, required = true, metaVar = "USERNAME", usage = "username") protected String username; + protected UserModel getUser(boolean requireUser) throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + UserModel user = gitblit.getUserModel(username); + if (requireUser && user == null) { + throw new UnloggedFailure(1, String.format("User %s does not exist!", username)); + } + return user; + } + } + + @CommandMetaData(name = "new", description = "Create a new user account") + @UsageExample(syntax = "${cmd} john 12345 --email john@smith.com --canFork --canCreate") + public static class NewUser extends UserCommand { + + @Argument(index = 1, required = true, metaVar = "PASSWORD", usage = "password") + protected String password; + + @Option(name = "--email", metaVar = "ADDRESS", usage = "email address") + protected String email; + + @Option(name = "--canAdmin", usage = "can administer the server") + protected boolean canAdmin; + + @Option(name = "--canFork", usage = "can fork repositories") + protected boolean canFork; + + @Option(name = "--canCreate", usage = "can create personal repositories") + protected boolean canCreate; + + @Option(name = "--disabled", usage = "create a disabled user account") + protected boolean disabled; + + @Override + public void run() throws UnloggedFailure { + + if (getUser(false) != null) { + throw new UnloggedFailure(1, String.format("User %s already exists!", username)); + } + + UserModel user = new UserModel(username); + user.password = password; + + if (email != null) { + user.emailAddress = email; + } + + user.canAdmin = canAdmin; + user.canFork = canFork; + user.canCreate = canCreate; + user.disabled = disabled; + + IGitblit gitblit = getContext().getGitblit(); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("%s created.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to create %s!", username)); + } + } + } + + @CommandMetaData(name = "set-name", description = "Set the display name of an account") + @UsageExample(syntax = "${cmd} john John Smith", description = "The display name to \"John Smith\" for john's account") + public static class SetName extends UserCommand { + + @Argument(index = 1, multiValued = true, required = true, metaVar = "NAME", usage = "display name") + protected List<String> displayName = new ArrayList<String>(); + + @Override + public void run() throws UnloggedFailure { + UserModel user = getUser(true); + + IGitblit gitblit = getContext().getGitblit(); + user.displayName = Joiner.on(" ").join(displayName); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("Set the display name of %s to \"%s\".", username, user.displayName)); + } else { + throw new UnloggedFailure(1, String.format("Failed to set the display name of %s!", username)); + } + } + } + + @CommandMetaData(name = "disable", description = "Prohibit an account from authenticating") + @UsageExample(syntax = "${cmd} john", description = "Prevent John from authenticating") + public static class DisableUser extends UserCommand { + + @Override + public void run() throws UnloggedFailure { + + UserModel user = getUser(true); + user.disabled = true; + + IGitblit gitblit = getContext().getGitblit(); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("%s is not allowed to authenticate.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to disable %s!", username)); + } + } + } + + @CommandMetaData(name = "enable", description = "Allow an account to authenticate") + @UsageExample(syntax = "${cmd} john", description = "Allow John to authenticate") + public static class EnableUser extends UserCommand { + + @Override + public void run() throws UnloggedFailure { + + UserModel user = getUser(true); + user.disabled = false; + + IGitblit gitblit = getContext().getGitblit(); + if (gitblit.updateUserModel(username, user)) { + stdout.println(String.format("%s may now authenticate.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to enable %s!", username)); + } + } + } + + @CommandMetaData(name = "permissions", aliases = { "perms" }, description = "Add or remove permissions from an account") + @UsageExample(syntax = "${cmd} john RW:alpha/repo.git RWC:alpha/repo2.git", description = "Add or set permissions for John") + public static class Permissions extends UserCommand { + + @Argument(index = 1, multiValued = true, metaVar = "[PERMISSION:]REPOSITORY", usage = "a repository expression") + protected List<String> permissions; + + @Option(name = "--remove", aliases = { "-r" }, metaVar = "REPOSITORY|ALL", usage = "remove a repository permission") + protected List<String> removals; + + @Override + public void run() throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + UserModel user = getUser(true); + + boolean modified = false; + if (!ArrayUtils.isEmpty(removals)) { + if (removals.contains("ALL")) { + user.permissions.clear(); + } else { + for (String repo : removals) { + user.removeRepositoryPermission(repo); + log.info(String.format("Removing permission for %s from %s", repo, username)); + } + } + modified = true; + } + + if (!ArrayUtils.isEmpty(permissions)) { + for (String perm : permissions) { + String repo = AccessPermission.repositoryFromRole(perm); + if (StringUtils.findInvalidCharacter(repo) == null) { + // explicit permision, confirm repository + RepositoryModel r = gitblit.getRepositoryModel(repo); + if (r == null) { + throw new UnloggedFailure(1, String.format("Repository %s does not exist!", repo)); + } + } + AccessPermission ap = AccessPermission.permissionFromRole(perm); + user.setRepositoryPermission(repo, ap); + log.info(String.format("Setting %s:%s for %s", ap.name(), repo, username)); + } + modified = true; + } + + if (modified && gitblit.updateUserModel(username, user)) { + // reload & display new permissions + user = gitblit.getUserModel(username); + } + + showPermissions(user); + } + + protected void showPermissions(UserModel user) { + List<RegistrantAccessPermission> perms = user.getRepositoryPermissions(); + String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; + Object [][] pdata = new Object[perms.size()][]; + for (int i = 0; i < perms.size(); i++) { + RegistrantAccessPermission ap = perms.get(i); + pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; + } + stdout.println(FlipTable.of(pheaders, pdata, Borders.BODY_HCOLS)); + } + } + + @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove a user account") + @UsageExample(syntax = "${cmd} john", description = "Delete john's account") + public static class RemoveUser extends UserCommand { + @Override public void run() throws UnloggedFailure { + + UserModel user = getUser(true); IGitblit gitblit = getContext().getGitblit(); - UserModel u = gitblit.getUserModel(username); - if (u == null) { - throw new UnloggedFailure(1, String.format("Unknown user \"%s\"", username)); + if (gitblit.deleteUserModel(user)) { + stdout.println(String.format("%s has been deleted.", username)); + } else { + throw new UnloggedFailure(1, String.format("Failed to delete %s!", username)); } + } + } + + @CommandMetaData(name = "show", description = "Show the details of an account") + @UsageExample(syntax = "${cmd} john", description = "Display john's account") + public static class ShowUser extends UserCommand { + + @Override + public void run() throws UnloggedFailure { + + UserModel u = getUser(true); // fields String [] fheaders = new String [] { "Field", "Value" }; Object [][] fdata = new Object[5][]; fdata[0] = new Object [] { "Email", u.emailAddress }; fdata[1] = new Object [] { "Type", u.accountType }; - fdata[2] = new Object [] { "Can Admin", u.canAdmin() ? "Y":"N" }; - fdata[3] = new Object [] { "Can Fork", u.canFork() ? "Y":"N" }; - fdata[4] = new Object [] { "Can Create", u.canCreate() ? "Y":"N" }; + fdata[2] = new Object [] { "Can Admin", u.canAdmin() ? "Y":"" }; + fdata[3] = new Object [] { "Can Fork", u.canFork() ? "Y":"" }; + fdata[4] = new Object [] { "Can Create", u.canCreate() ? "Y":"" }; String fields = FlipTable.of(fheaders, fdata, Borders.COLS); // teams - String [] theaders = new String [] { "Team", "Type" }; - Object [][] tdata = new Object[u.teams.size()][]; - int i = 0; - for (TeamModel t : u.teams) { - tdata[i] = new Object [] { t.name, t.accountType }; - i++; + String teams; + if (u.teams.size() == 0) { + teams = FlipTable.EMPTY; + } else { + String [] theaders = new String [] { "Team", "Type" }; + Object [][] tdata = new Object[u.teams.size()][]; + int i = 0; + for (TeamModel t : u.teams) { + tdata[i] = new Object [] { t.name, t.accountType }; + i++; + } + teams = FlipTable.of(theaders, tdata, Borders.COLS); } - String teams = FlipTable.of(theaders, tdata, Borders.COLS); // permissions List<RegistrantAccessPermission> perms = u.getRepositoryPermissions(); - String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; - Object [][] pdata = new Object[perms.size()][]; - for (i = 0; i < perms.size(); i++) { - RegistrantAccessPermission ap = perms.get(i); - pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"N" }; + String permissions; + if (perms.isEmpty()) { + permissions = FlipTable.EMPTY; + } else { + String[] pheaders = { "Repository", "Permission", "Type", "Source", "Mutable" }; + Object [][] pdata = new Object[perms.size()][]; + for (int i = 0; i < perms.size(); i++) { + RegistrantAccessPermission ap = perms.get(i); + pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; + } + permissions = FlipTable.of(pheaders, pdata, Borders.COLS); } - String permissions = FlipTable.of(pheaders, pdata, Borders.COLS); // assemble user table - String [] headers = new String[] { u.getDisplayName() + (u.username.equals(u.getDisplayName()) ? "" : (" (" + u.username + ")")) }; + String userTitle = u.getDisplayName() + (u.username.equals(u.getDisplayName()) ? "" : (" (" + u.username + ")")); + if (u.disabled) { + userTitle += " [DISABLED]"; + } + String [] headers = new String[] { userTitle }; String[][] data = new String[6][]; data[0] = new String [] { "FIELDS" }; data[1] = new String [] { fields }; @@ -95,7 +328,11 @@ public class UsersDispatcher extends DispatchCommand { } } - @CommandMetaData(name = "list", aliases= { "ls" }, description = "List users") + @CommandMetaData(name = "list", aliases= { "ls" }, description = "List accounts") + @UsageExamples( examples = { + @UsageExample(syntax = "${cmd}", description = "List accounts as a table"), + @UsageExample(syntax = "${cmd} j.*", description = "List all accounts that start with 'j'"), + }) public static class ListUsers extends ListFilterCommand<UserModel> { @Override @@ -125,10 +362,12 @@ public class UsersDispatcher extends DispatchCommand { for (int i = 0; i < list.size(); i++) { UserModel u = list.get(i); - String name = u.disabled ? "-" : ((u.canAdmin() ? "*" : " ")) + u.username; + String name = (u.disabled ? "-" : ((u.canAdmin() ? "*" : " "))) + u.username; if (verbose) { data[i] = new Object[] { name, u.displayName, u.accountType, - u.emailAddress, u.canCreate() ? "Y":"", u.canFork() ? "Y" : ""}; + u.emailAddress, + (u.canAdmin() || u.canCreate()) ? "Y":"", + (u.canAdmin() || u.canFork()) ? "Y" : ""}; } else { data[i] = new Object[] { name, u.displayName, u.accountType, u.emailAddress }; @@ -147,8 +386,8 @@ public class UsersDispatcher extends DispatchCommand { u.getDisplayName(), u.accountType, u.emailAddress == null ? "" : u.emailAddress, - u.canCreate() ? "Y":"", - u.canFork() ? "Y" : ""); + (u.canAdmin() || u.canCreate()) ? "Y":"", + (u.canAdmin() || u.canFork()) ? "Y" : ""); } } else { for (UserModel u : users) { diff --git a/src/main/java/com/gitblit/utils/FlipTable.java b/src/main/java/com/gitblit/utils/FlipTable.java index 7aa5f0b1..0197517d 100644 --- a/src/main/java/com/gitblit/utils/FlipTable.java +++ b/src/main/java/com/gitblit/utils/FlipTable.java @@ -36,7 +36,7 @@ package com.gitblit.utils; * </pre> */ public final class FlipTable { - private static final String EMPTY = "(empty)"; + public static final String EMPTY = "(empty)"; public static enum Borders { FULL(15), BODY_HCOLS(13), HCOLS(12), BODY(9), HEADER(8), COLS(4); |