blob: bb04f7a00148cf50da33faa17f625d3c28d2e9fc [file] [log] [blame]
// 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);
}
}
}
}