/* * 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 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") 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 */ @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 { @Override protected List getItems() { IGitblit gitblit = getContext().getGitblit(); UserModel user = getContext().getClient().getUser(); List repositories = gitblit.getRepositoryModels(user); return repositories; } @Override protected boolean matches(String filter, RepositoryModel r) { return r.name.matches(filter); } @Override protected void asTable(List list) { String[] headers; if (verbose) { String[] h = { "Name", "Description", "Owners", "Last Modified", "Size" }; headers = h; } else { String[] h = { "Name", "Last Modified", "Size" }; headers = h; } Object[][] data = new Object[list.size()][]; for (int i = 0; i < list.size(); i++) { RepositoryModel r = list.get(i); String lm = formatDate(r.lastChange); String size = r.size; if (!r.hasCommits) { lm = ""; size = FlipTable.EMPTY; } if (verbose) { String owners = ""; if (!ArrayUtils.isEmpty(r.owners)) { owners = Joiner.on(",").join(r.owners); } data[i] = new Object[] { r.name, r.description, owners, lm, size }; } else { data[i] = new Object[] { r.name, lm, size }; } } stdout.println(FlipTable.of(headers, data, Borders.BODY_HCOLS)); } @Override protected void asTabbed(List list) { if (verbose) { for (RepositoryModel r : list) { String lm = formatDate(r.lastChange); String owners = ""; if (!ArrayUtils.isEmpty(r.owners)) { owners = Joiner.on(",").join(r.owners); } String size = r.size; if (!r.hasCommits) { lm = ""; size = "(empty)"; } outTabbed(r.name, r.description == null ? "" : r.description, owners, lm, size); } } else { for (RepositoryModel r : list) { outTabbed(r.name); } } } } }