ls-projects command can show the project hierarchy
The ls-projects command has a new --tree option that will make the
output of the project names be printed in a ascii art hierarcy to
show the projects access rights inheritance.
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index 1200e18..1e7b4cc 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -32,6 +32,11 @@
\-b::
Name of the branch for which the command will display the sha of each project.
+\--tree::
+\-t::
+ Displays project inheritance in a tree-like format.
+ This option does not work together with the show-branch option.
+
EXAMPLES
--------
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
index fda0448..7618432 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
@@ -33,8 +33,16 @@
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;
@@ -54,6 +62,12 @@
@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() {
@@ -66,7 +80,18 @@
}
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)) {
@@ -83,27 +108,58 @@
}
final ProjectControl pctl = e.controlFor(currentUser);
- 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.
+ if (!showTree) {
+
+ if (!pctl.isVisible()) {
+ // Require the project itself to be visible to the user.
//
continue;
}
- stdout.print(ref.getObjectId().name());
- stdout.print(' ');
+ 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);
+ }
}
- stdout.print(p.getName());
- stdout.println();
+ // 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);
@@ -124,4 +180,139 @@
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);
+ }
+ }
+ }
}