ls-projects: Add --type to filter by project type

`gerrit ls-projects --type code` (the default) now skips permissions
only projects, restoring the output to what appears from Gerrit
2.1.7 and earlier.

`gerrit ls-projects --type permissions` will invert that output and
display only the special permissions-only projects.

`gerrit ls-projects --type all` will display everything.

Change-Id: If04bda437e45e51b5b071dac6edeb2a8ef730eb7
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index 2134df8..de8d5b5 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -8,7 +8,10 @@
 SYNOPSIS
 --------
 [verse]
-'ssh' -p <port> <host> 'gerrit ls-projects' [\--show-branch <BRANCH1> ...]
+ 'ssh' -p <port> <host> 'gerrit ls-projects'
+ [--show-branch <BRANCH1> ...]
+ [--tree]
+ [--type {code | permissions | all}]
 
 DESCRIPTION
 -----------
@@ -43,6 +46,13 @@
 	Displays project inheritance in a tree-like format.
 	This option does not work together with the show-branch option.
 
+--type::
+	Display only projects of the specified type. Supported
+	types are `code` (any project likely to contain user files),
+	`permissions` (projects created with the --permissions-only
+	flag), `all` (any type of project).  If not specified,
+	defaults to `code`.
+
 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 a17fc0d..ab57713 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
@@ -24,9 +24,13 @@
 import com.google.inject.Inject;
 
 import org.apache.sshd.server.Environment;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -36,11 +40,39 @@
 import java.util.TreeMap;
 
 final class ListProjects extends BaseCommand {
+  private static final Logger log = LoggerFactory.getLogger(ListProjects.class);
+
   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)";
 
+  static enum FilterType {
+    CODE {
+      @Override
+      boolean matches(Repository git) throws IOException {
+        return !PERMISSIONS.matches(git);
+      }
+    },
+    PERMISSIONS {
+      @Override
+      boolean matches(Repository git) throws IOException {
+        Ref head = git.getRef(Constants.HEAD);
+        return head != null
+          && head.isSymbolic()
+          && GitRepositoryManager.REF_CONFIG.equals(head.getLeaf().getName());
+      }
+    },
+    ALL {
+      @Override
+      boolean matches(Repository git) {
+        return true;
+      }
+    };
+
+    abstract boolean matches(Repository git) throws IOException;
+  }
+
   @Inject
   private IdentifiedUser currentUser;
 
@@ -58,6 +90,9 @@
       "this option does not work together with the show-branch option")
   private boolean showTree;
 
+  @Option(name = "--type", usage = "type of project")
+  private FilterType type = FilterType.CODE;
+
   private String currentTabSeparator = DEFAULT_TAB_SEPARATOR;
 
   @Override
@@ -100,21 +135,49 @@
           continue;
         }
 
-        if (showBranch != null) {
-          List<Ref> refs = getBranchRefs(projectName, pctl);
-          if (!hasValidRef(refs)) {
-           continue;
+        try {
+          if (showBranch != null) {
+            Repository git = repoManager.openRepository(projectName);
+            try {
+              if (!type.matches(git)) {
+                continue;
+              }
+
+              List<Ref> refs = getBranchRefs(projectName, pctl);
+              if (!hasValidRef(refs)) {
+               continue;
+              }
+
+              for (Ref ref : refs) {
+                if (ref == null) {
+                  // Print stub (forty '-' symbols)
+                  stdout.print("----------------------------------------");
+                } else {
+                  stdout.print(ref.getObjectId().name());
+                }
+                stdout.print(' ');
+              }
+            } finally {
+              git.close();
+            }
+
+          } else if (type != FilterType.ALL) {
+            Repository git = repoManager.openRepository(projectName);
+            try {
+              if (!type.matches(git)) {
+                continue;
+              }
+            } finally {
+              git.close();
+            }
           }
 
-          for (Ref ref : refs) {
-            if (ref == null) {
-              // Print stub (forty '-' symbols)
-              stdout.print("----------------------------------------");
-            } else {
-              stdout.print(ref.getObjectId().name());
-            }
-            stdout.print(' ');
-          }
+        } catch (RepositoryNotFoundException err) {
+          // If the Git repository is gone, the project doesn't actually exist anymore.
+          continue;
+        } catch (IOException err) {
+          log.warn("Unexpected error reading " + projectName, err);
+          continue;
         }
 
         stdout.print(projectName.get() + "\n");