From: James Moger Date: Fri, 28 Mar 2014 23:44:48 +0000 (-0400) Subject: Implement management commands in repositories dispatcher X-Git-Tag: v1.5.0~68^2~18 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a9dc74e73eea068b8cbb5c96958abccae88b4abc;p=gitblit.git Implement management commands in repositories dispatcher --- diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index 430ae6d1..26ab3f3b 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -226,7 +226,7 @@ public class GitBlit extends GitblitManager { public boolean deleteRepositoryModel(RepositoryModel model) { boolean success = repositoryManager.deleteRepositoryModel(model); if (success && ticketService != null) { - return ticketService.deleteAll(model); + ticketService.deleteAll(model); } return success; } diff --git a/src/main/java/com/gitblit/models/UserModel.java b/src/main/java/com/gitblit/models/UserModel.java index 675835d3..64bca825 100644 --- a/src/main/java/com/gitblit/models/UserModel.java +++ b/src/main/java/com/gitblit/models/UserModel.java @@ -543,7 +543,7 @@ public class UserModel implements Principal, Serializable, Comparable // admins can create any repository return true; } - if (canCreate) { + if (canCreate()) { String projectPath = StringUtils.getFirstPathElement(repository); if (!StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase(getPersonalPath())) { // personal repository @@ -552,6 +552,16 @@ public class UserModel implements Principal, Serializable, Comparable } return false; } + + /** + * Returns true if the user is allowed to administer the specified repository + * + * @param repo + * @return true if the user can administer the repository + */ + public boolean canAdmin(RepositoryModel repo) { + return canAdmin() || isMyPersonalRepository(repo.name); + } public boolean isAuthenticated() { return !UserModel.ANONYMOUS.equals(this) && isAuthenticated; diff --git a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java index bcf30c26..6809ba62 100644 --- a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java +++ b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java @@ -165,7 +165,7 @@ public class WelcomeShell implements Factory { 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(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); 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 56f2c355..930c058f 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 readKeys(List sshKeys) throws UnsupportedEncodingException, IOException { int idx = -1; - if ((idx = sshKeys.indexOf("-")) >= 0) { + if (sshKeys.isEmpty() || (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/KeysDispatcher.java b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java index 5f508e60..9bb60003 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/KeysDispatcher.java @@ -59,7 +59,7 @@ public class KeysDispatcher extends DispatchCommand { protected final Logger log = LoggerFactory.getLogger(getClass()); - @Argument(metaVar = "-|", usage = "the key(s) to add", required = true) + @Argument(metaVar = "", usage = "the key(s) to add") private List addKeys = new ArrayList(); @Override @@ -82,7 +82,7 @@ public class KeysDispatcher extends DispatchCommand { private final String ALL = "ALL"; - @Argument(metaVar = "-|||ALL", usage = "the key to remove", required = true) + @Argument(metaVar = "||ALL", usage = "the key to remove", required = true) private List removeKeys = new ArrayList(); @Override 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 f2fbabbe..292c2126 100644 --- a/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java +++ b/src/main/java/com/gitblit/transport/ssh/gitblit/RepositoriesDispatcher.java @@ -15,18 +15,29 @@ */ package com.gitblit.transport.ssh.gitblit; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.kohsuke.args4j.Argument; + +import com.gitblit.GitBlitException; +import com.gitblit.Keys; +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.Constants.AuthorizationControl; import com.gitblit.manager.IGitblit; +import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.models.RepositoryModel; 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.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 = "repositories", aliases = { "repos" }, description = "Repository management commands") @@ -34,7 +45,411 @@ public class RepositoriesDispatcher extends DispatchCommand { @Override protected void setup(UserModel user) { + // primary commands + register(user, NewRepository.class); + register(user, RenameRepository.class); + register(user, RemoveRepository.class); + register(user, ShowRepository.class); register(user, ListRepositories.class); + + // repository-specific commands + register(user, SetField.class); + } + + public static abstract class RepositoryCommand extends SshCommand { + @Argument(index = 0, required = true, metaVar = "REPOSITORY", usage = "repository") + protected String repository; + + protected RepositoryModel getRepository(boolean requireRepository) throws UnloggedFailure { + IGitblit gitblit = getContext().getGitblit(); + RepositoryModel repo = gitblit.getRepositoryModel(repository); + if (requireRepository && repo == null) { + throw new UnloggedFailure(1, String.format("Repository %s does not exist!", repository)); + } + return repo; + } + + protected String sanitize(String name) throws UnloggedFailure { + // automatically convert backslashes to forward slashes + name = name.replace('\\', '/'); + // Automatically replace // with / + name = name.replace("//", "/"); + + // prohibit folder paths + if (name.startsWith("/")) { + throw new UnloggedFailure(1, "Illegal leading slash"); + } + if (name.startsWith("../")) { + throw new UnloggedFailure(1, "Illegal relative slash"); + } + if (name.contains("/../")) { + throw new UnloggedFailure(1, "Illegal relative slash"); + } + if (name.endsWith("/")) { + name = name.substring(0, name.length() - 1); + } + return name; + } + } + + @CommandMetaData(name = "new", aliases = { "add" }, description = "Create a new repository") + @UsageExample(syntax = "${cmd} myRepo") + public static class NewRepository extends RepositoryCommand { + + @Override + public void run() throws UnloggedFailure { + + UserModel user = getContext().getClient().getUser(); + + String name = sanitize(repository); + + if (!user.canCreate(name)) { + // try to prepend personal path + String path = StringUtils.getFirstPathElement(name); + if ("".equals(path)) { + name = user.getPersonalPath() + "/" + name; + } + } + + if (getRepository(false) != null) { + throw new UnloggedFailure(1, String.format("Repository %s already exists!", name)); + } + + if (!user.canCreate(name)) { + throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to create %s", name)); + } + + IGitblit gitblit = getContext().getGitblit(); + + RepositoryModel repo = new RepositoryModel(); + repo.name = name; + repo.projectPath = StringUtils.getFirstPathElement(name); + String restriction = gitblit.getSettings().getString(Keys.git.defaultAccessRestriction, "PUSH"); + repo.accessRestriction = AccessRestrictionType.fromName(restriction); + String authorization = gitblit.getSettings().getString(Keys.git.defaultAuthorizationControl, null); + repo.authorizationControl = AuthorizationControl.fromName(authorization); + + if (user.isMyPersonalRepository(name)) { + // personal repositories are private by default + repo.addOwner(user.username); + repo.accessRestriction = AccessRestrictionType.VIEW; + repo.authorizationControl = AuthorizationControl.NAMED; + } + + try { + gitblit.updateRepositoryModel(repository, repo, true); + stdout.println(String.format("%s created.", repo.name)); + } catch (GitBlitException e) { + log.error("Failed to add " + repository, e); + throw new UnloggedFailure(1, e.getMessage()); + } + } + } + + @CommandMetaData(name = "rename", aliases = { "mv" }, description = "Rename a repository") + @UsageExample(syntax = "${cmd} myRepo.git otherRepo.git", description = "Rename the repository from myRepo.git to otherRepo.git") + public static class RenameRepository extends RepositoryCommand { + @Argument(index = 1, required = true, metaVar = "NEWNAME", usage = "the new repository name") + protected String newRepositoryName; + + @Override + public void run() throws UnloggedFailure { + RepositoryModel repo = getRepository(true); + IGitblit gitblit = getContext().getGitblit(); + UserModel user = getContext().getClient().getUser(); + + String name = sanitize(newRepositoryName); + if (!user.canCreate(name)) { + // try to prepend personal path + String path = StringUtils.getFirstPathElement(name); + if ("".equals(path)) { + name = user.getPersonalPath() + "/" + name; + } + } + + if (null != gitblit.getRepositoryModel(name)) { + throw new UnloggedFailure(1, String.format("Repository %s already exists!", name)); + } + + if (repo.name.equalsIgnoreCase(name)) { + throw new UnloggedFailure(1, "Repository names are identical"); + } + + if (!user.canAdmin(repo)) { + throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to rename %s", repository)); + } + + if (!user.canCreate(name)) { + throw new UnloggedFailure(1, String.format("Sorry, you don't have permission to move %s to %s/", repository, name)); + } + + // set the new name + repo.name = name; + + try { + gitblit.updateRepositoryModel(repository, repo, false); + stdout.println(String.format("Renamed repository %s to %s.", repository, name)); + } catch (GitBlitException e) { + String msg = String.format("Failed to rename repository from %s to %s", repository, name); + log.error(msg, e); + throw new UnloggedFailure(1, msg); + } + } + } + + @CommandMetaData(name = "set", description = "Set the specified field of a repository") + @UsageExample(syntax = "${cmd} myRepo description John's personal projects", description = "Set the description of a repository") + public static class SetField extends RepositoryCommand { + + @Argument(index = 1, required = true, metaVar = "FIELD", usage = "the field to update") + protected String fieldName; + + @Argument(index = 2, required = true, metaVar = "VALUE", usage = "the new value") + protected List fieldValues = new ArrayList(); + + protected enum Field { + description; + + static Field fromString(String name) { + for (Field field : values()) { + if (field.name().equalsIgnoreCase(name)) { + return field; + } + } + return null; + } + } + + @Override + protected String getUsageText() { + String fields = Joiner.on(", ").join(Field.values()); + StringBuilder sb = new StringBuilder(); + sb.append("Valid fields are:\n ").append(fields); + return sb.toString(); + } + + @Override + public void run() throws UnloggedFailure { + RepositoryModel repo = getRepository(true); + + Field field = Field.fromString(fieldName); + if (field == null) { + throw new UnloggedFailure(1, String.format("Unknown field %s", fieldName)); + } + + if (!getContext().getClient().getUser().canAdmin(repo)) { + throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to administer %s", repository)); + } + + String value = Joiner.on(" ").join(fieldValues).trim(); + IGitblit gitblit = getContext().getGitblit(); + + switch(field) { + case description: + repo.description = value; + break; + default: + throw new UnloggedFailure(1, String.format("Field %s was not properly handled by the set command.", fieldName)); + } + + try { + gitblit.updateRepositoryModel(repo.name, repo, false); + stdout.println(String.format("Set %s.%s = %s", repo.name, fieldName, value)); + } catch (GitBlitException e) { + String msg = String.format("Failed to set %s.%s = %s", repo.name, fieldName, value); + log.error(msg, e); + throw new UnloggedFailure(1, msg); + } + } + + protected boolean toBool(String value) throws UnloggedFailure { + String v = value.toLowerCase(); + if (v.equals("t") + || v.equals("true") + || v.equals("yes") + || v.equals("on") + || v.equals("y") + || v.equals("1")) { + return true; + } else if (v.equals("f") + || v.equals("false") + || v.equals("no") + || v.equals("off") + || v.equals("n") + || v.equals("0")) { + return false; + } + throw new UnloggedFailure(1, String.format("Invalid boolean value %s", value)); + } + } + + @CommandMetaData(name = "remove", aliases = { "rm" }, description = "Remove a repository") + @UsageExample(syntax = "${cmd} myRepo.git", description = "Delete myRepo.git") + public static class RemoveRepository extends RepositoryCommand { + + @Override + public void run() throws UnloggedFailure { + + RepositoryModel repo = getRepository(true); + + if (!getContext().getClient().getUser().canAdmin(repo)) { + throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to delete %s", repository)); + } + + IGitblit gitblit = getContext().getGitblit(); + if (gitblit.deleteRepositoryModel(repo)) { + stdout.println(String.format("%s has been deleted.", repository)); + } else { + throw new UnloggedFailure(1, String.format("Failed to delete %s!", repository)); + } + } + } + + @CommandMetaData(name = "show", description = "Show the details of a repository") + @UsageExample(syntax = "${cmd} myRepo.git", description = "Display myRepo.git") + public static class ShowRepository extends RepositoryCommand { + + @Override + public void run() throws UnloggedFailure { + + RepositoryModel r = getRepository(true); + + if (!getContext().getClient().getUser().canAdmin(r)) { + throw new UnloggedFailure(1, String.format("Sorry, you do not have permission to see the %s settings.", repository)); + } + + IGitblit gitblit = getContext().getGitblit(); + + // fields + StringBuilder fb = new StringBuilder(); + fb.append("Description : ").append(toString(r.description)).append('\n'); + fb.append("Origin : ").append(toString(r.origin)).append('\n'); + fb.append("Default Branch : ").append(toString(r.HEAD)).append('\n'); + fb.append('\n'); + fb.append("GC Period : ").append(r.gcPeriod).append('\n'); + fb.append("GC Threshold : ").append(r.gcThreshold).append('\n'); + fb.append('\n'); + fb.append("Accept Tickets : ").append(toString(r.acceptNewTickets)).append('\n'); + fb.append("Accept Patchsets : ").append(toString(r.acceptNewPatchsets)).append('\n'); + fb.append("Require Approval : ").append(toString(r.requireApproval)).append('\n'); + fb.append("Merge To : ").append(toString(r.mergeTo)).append('\n'); + fb.append('\n'); + fb.append("Incremental push tags : ").append(toString(r.useIncrementalPushTags)).append('\n'); + fb.append("Show remote branches : ").append(toString(r.showRemoteBranches)).append('\n'); + fb.append("Skip size calculations : ").append(toString(r.skipSizeCalculation)).append('\n'); + fb.append("Skip summary metrics : ").append(toString(r.skipSummaryMetrics)).append('\n'); + fb.append("Max activity commits : ").append(r.maxActivityCommits).append('\n'); + fb.append("Author metric exclusions : ").append(toString(r.metricAuthorExclusions)).append('\n'); + fb.append("Commit Message Renderer : ").append(r.commitMessageRenderer).append('\n'); + fb.append("Mailing Lists : ").append(toString(r.mailingLists)).append('\n'); + fb.append('\n'); + fb.append("Access Restriction : ").append(r.accessRestriction).append('\n'); + fb.append("Authorization Control : ").append(r.authorizationControl).append('\n'); + fb.append('\n'); + fb.append("Is Frozen : ").append(toString(r.isFrozen)).append('\n'); + fb.append("Allow Forks : ").append(toString(r.allowForks)).append('\n'); + fb.append("Verify Committer : ").append(toString(r.verifyCommitter)).append('\n'); + fb.append('\n'); + fb.append("Federation Strategy : ").append(r.federationStrategy).append('\n'); + fb.append("Federation Sets : ").append(toString(r.federationSets)).append('\n'); + fb.append('\n'); + fb.append("Indexed Branches : ").append(toString(r.indexedBranches)).append('\n'); + fb.append('\n'); + fb.append("Pre-Receive Scripts : ").append(toString(r.preReceiveScripts)).append('\n'); + fb.append(" inherited : ").append(toString(gitblit.getPreReceiveScriptsInherited(r))).append('\n'); + fb.append("Post-Receive Scripts : ").append(toString(r.postReceiveScripts)).append('\n'); + fb.append(" inherited : ").append(toString(gitblit.getPostReceiveScriptsInherited(r))).append('\n'); + String fields = fb.toString(); + + // owners + String owners; + if (r.owners.isEmpty()) { + owners = FlipTable.EMPTY; + } else { + String[] pheaders = { "Account", "Name" }; + Object [][] pdata = new Object[r.owners.size()][]; + for (int i = 0; i < r.owners.size(); i++) { + String owner = r.owners.get(i); + UserModel u = gitblit.getUserModel(owner); + pdata[i] = new Object[] { owner, u == null ? "" : u.getDisplayName() }; + } + owners = FlipTable.of(pheaders, pdata, Borders.COLS); + } + + // team permissions + List tperms = gitblit.getTeamAccessPermissions(r); + String tpermissions; + if (tperms.isEmpty()) { + tpermissions = FlipTable.EMPTY; + } else { + String[] pheaders = { "Team", "Permission", "Type" }; + Object [][] pdata = new Object[tperms.size()][]; + for (int i = 0; i < tperms.size(); i++) { + RegistrantAccessPermission ap = tperms.get(i); + pdata[i] = new Object[] { ap.registrant, ap.permission, ap.permissionType }; + } + tpermissions = FlipTable.of(pheaders, pdata, Borders.COLS); + } + + // user permissions + List uperms = gitblit.getUserAccessPermissions(r); + String upermissions; + if (uperms.isEmpty()) { + upermissions = FlipTable.EMPTY; + } else { + String[] pheaders = { "Account", "Name", "Permission", "Type", "Source", "Mutable" }; + Object [][] pdata = new Object[uperms.size()][]; + for (int i = 0; i < uperms.size(); i++) { + RegistrantAccessPermission ap = uperms.get(i); + String name = ""; + try { + String dn = gitblit.getUserModel(ap.registrant).displayName; + if (dn != null) { + name = dn; + } + } catch (Exception e) { + } + pdata[i] = new Object[] { ap.registrant, name, ap.permission, ap.permissionType, ap.source, ap.mutable ? "Y":"" }; + } + upermissions = FlipTable.of(pheaders, pdata, Borders.COLS); + } + + // assemble table + String title = r.name; + String [] headers = new String[] { title }; + String[][] data = new String[8][]; + data[0] = new String [] { "FIELDS" }; + data[1] = new String [] {fields }; + data[2] = new String [] { "OWNERS" }; + data[3] = new String [] { owners }; + data[4] = new String [] { "TEAM PERMISSIONS" }; + data[5] = new String [] { tpermissions }; + data[6] = new String [] { "USER PERMISSIONS" }; + data[7] = new String [] { upermissions }; + stdout.println(FlipTable.of(headers, data)); + } + + protected String toString(String val) { + if (val == null) { + return ""; + } + return val; + } + + protected String toString(Collection collection) { + if (collection == null) { + return ""; + } + return Joiner.on(", ").join(collection); + } + + protected String toString(boolean val) { + if (val) { + return "Y"; + } + return ""; + } + } /* List repositories */ diff --git a/src/site/setup_transport_ssh.mkd b/src/site/setup_transport_ssh.mkd index 5bac2ff4..0f09910e 100644 --- a/src/site/setup_transport_ssh.mkd +++ b/src/site/setup_transport_ssh.mkd @@ -23,8 +23,8 @@ First you'll need to create an SSH key pair, if you don't already have one or if Then you can upload your *public* key right from the command-line. - cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add - - cat c:\\.ssh\id_rsa.pub | ssh -l -p 29418 gitblit keys add - + cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add + cat c:\\.ssh\id_rsa.pub | ssh -l -p 29418 gitblit keys add **NOTE:** It is important to note that *ssh-keygen* generates a public/private keypair (e.g. id_rsa and id_rsa.pub). You want to upload the *public* key, which is denoted by the *.pub* file extension. @@ -62,7 +62,7 @@ The *gitblit* command has many subcommands for interacting with Gitblit. Add an SSH public key to your account. This command accepts a public key piped to stdin. - cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add - + cat ~/.ssh/id_rsa.pub | ssh -l -p 29418 gitblit keys add ##### keys list