// Copyright (C) 2009 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gerrit.sshd.commands; import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.config.WildProjectName; import com.google.gerrit.server.git.GitRepositoryManager; 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.BaseCommand; import com.google.gwtorm.client.OrmException; import com.google.inject.Inject; import org.apache.sshd.server.Environment; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.kohsuke.args4j.Option; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; final class ListProjects extends BaseCommand { private static final String NODE_PREFIX = "|-- "; private static final String LAST_NODE_PREFIX = "`-- "; private static final String DEFAULT_TAB_SEPARATOR = "|"; private static final String NOT_VISIBLE_PROJECT = "(x)"; @Inject private ReviewDb db; @Inject private IdentifiedUser currentUser; @Inject private ProjectCache projectCache; @Inject private GitRepositoryManager repoManager; @Inject @WildProjectName private Project.NameKey wildProject; @Option(name = "--show-branch", aliases = {"-b"}, usage = "displays the sha of each project in the specified branch") private String showBranch; @Option(name = "--tree", aliases = {"-t"}, usage = "displays project inheritance in a tree-like format\n" + "this option does not work together with the show-branch option") private boolean showTree; private String currentTabSeparator = DEFAULT_TAB_SEPARATOR; @Override public void start(final Environment env) { startThread(new CommandRunnable() { @Override public void run() throws Exception { parseCommandLine(); ListProjects.this.display(); } }); } private void display() throws Failure { if (showTree && (showBranch != null)) { throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible."); } final PrintWriter stdout = toPrintWriter(out); TreeMap<String, TreeNode> treeMap = null; if (showTree) { treeMap = new TreeMap<String, TreeNode>(); } try { for (final Project p : db.projects().all()) { if (p.getNameKey().equals(wildProject)) { // This project "doesn't exist". At least not as a repository. // continue; } final ProjectState e = projectCache.get(p.getNameKey()); if (e == null) { // If we can't get it from the cache, pretend its not present. // continue; } final ProjectControl pctl = e.controlFor(currentUser); if (!showTree) { if (!pctl.isVisible()) { // Require the project itself to be visible to the user. // continue; } if (showBranch != null) { final Ref ref = getBranchRef(p.getNameKey()); if (ref == null || ref.getObjectId() == null || !pctl.controlForRef(ref.getLeaf().getName()).isVisible()) { // No branch, or the user can't see this branch, so skip it. // continue; } stdout.print(ref.getObjectId().name()); stdout.print(' '); } stdout.print(p.getName() + "\n"); } else { treeMap.put(p.getName(), new TreeNode(p, pctl.isVisible())); } } if (showTree && treeMap.size() > 0) { final List<TreeNode> sortedNodes = new ArrayList<TreeNode>(); // Builds the inheritance tree using a list. // for (final TreeNode key : treeMap.values()) { final String parentName = key.getParentName(); if (parentName != null) { final TreeNode node = treeMap.get((String)parentName); if (node != null) { node.addChild(key); } else { sortedNodes.add(key); } } else { sortedNodes.add(key); } } // Builds a fake root node, which contains the sorted projects. // final TreeNode fakeRoot = new TreeNode(null, sortedNodes, false); printElement(stdout, fakeRoot, -1, false, sortedNodes.get(sortedNodes.size() - 1)); stdout.flush(); } } catch (OrmException e) { throw new Failure(1, "fatal: database error", e); } finally { stdout.flush(); } } private Ref getBranchRef(Project.NameKey projectName) { try { final Repository r = repoManager.openRepository(projectName); try { return r.getRef(showBranch); } finally { r.close(); } } catch (IOException ioe) { return null; } } /** Class created to manipulate the nodes of the project inheritance tree **/ private static class TreeNode { private final List<TreeNode> children; private final Project project; private final boolean isVisible; /** * Constructor * @param p Project */ public TreeNode(Project p, boolean visible) { this.children = new ArrayList<TreeNode>(); this.project = p; this.isVisible = visible; } /** * Constructor used for creating the fake node * @param p Project * @param c List of nodes */ public TreeNode(Project p, List<TreeNode> c, boolean visible) { this.children = c; this.project = p; this.isVisible = visible; } /** * Returns if the the node is leaf * @return True if is lead, false, otherwise */ public boolean isLeaf() { return children.size() == 0; } /** * Returns the project parent name * @return Project parent name */ public String getParentName() { if (project.getParent() != null) { return project.getParent().get(); } return null; } /** * Adds a child to the list * @param node TreeNode child */ public void addChild(TreeNode node) { children.add(node); } /** * Returns the project instance * @return Project instance */ public Project getProject() { return project; } /** * Returns the list of children nodes * @return List of children nodes */ public List<TreeNode> getChildren() { return children; } /** * Returns if the project is visible to the user * @return True if is visible, false, otherwise */ public boolean isVisible() { return isVisible; } } /** * Used to display the project inheritance tree recursively * @param stdout PrintWriter used do print * @param node Tree node * @param level Current level of the tree * @param isLast True, if is the last node of a level, false, otherwise * @param lastParentNode Last "root" parent node */ private void printElement(final PrintWriter stdout, TreeNode node, int level, boolean isLast, final TreeNode lastParentNode) { // Checks if is not the "fake" root project. // if (node.getProject() != null) { // Check if is not the last "root" parent node, // so the "|" separator will not longer be needed. // if (!currentTabSeparator.equals(" ")) { final String nodeProject = node.getProject().getName(); final String lastParentProject = lastParentNode.getProject().getName(); if (nodeProject.equals(lastParentProject)) { currentTabSeparator = " "; } } if (level > 0) { stdout.print(String.format("%-" + 4 * level + "s", currentTabSeparator)); } final String prefix = isLast ? LAST_NODE_PREFIX : NODE_PREFIX ; String printout; if (node.isVisible()) { printout = prefix + node.getProject().getName(); } else { printout = prefix + NOT_VISIBLE_PROJECT; } stdout.print(printout + "\n"); } if (node.isLeaf()) { return; } else { final List<TreeNode> children = node.getChildren(); ++level; for(TreeNode treeNode : children) { final boolean isLastIndex = children.indexOf(treeNode) == children.size() - 1; printElement(stdout, treeNode, level, isLastIndex, lastParentNode); } } } }