Merge "Lists subdirectories at the top of the source tree"
diff --git a/java/com/google/gitiles/TreeSoyData.java b/java/com/google/gitiles/TreeSoyData.java
index 1e4e4b7..e1aeb21 100644
--- a/java/com/google/gitiles/TreeSoyData.java
+++ b/java/com/google/gitiles/TreeSoyData.java
@@ -41,6 +41,14 @@
    */
   private static final int MAX_SYMLINK_TARGET_LENGTH = 72;
 
+  private static final Map<String, Integer> TYPE_WEIGHT =
+      Map.of(
+          "TREE", 0,
+          "GITLINK", 1,
+          "SYMLINK", 2,
+          "REGULAR_FILE", 3,
+          "EXECUTABLE_FILE", 3);
+
   /**
    * Maximum number of bytes to load from a blob that claims to be a symlink. If the blob is larger
    * than this byte limit it will be displayed as a binary file instead of as a symlink.
@@ -65,6 +73,10 @@
     return lastSlash >= 0 ? "..." + target.substring(lastSlash) : target;
   }
 
+  static int sortByType(Map<String, String> m1, Map<String, String> m2) {
+    return TYPE_WEIGHT.get(m1.get("type")).compareTo(TYPE_WEIGHT.get(m2.get("type")));
+  }
+
   private final ObjectReader reader;
   private final GitilesView view;
   private final Config cfg;
@@ -90,7 +102,7 @@
       throws MissingObjectException, IOException {
     ReadmeHelper readme =
         new ReadmeHelper(reader, view, MarkdownConfig.get(cfg), rootTree, requestUri);
-    List<Object> entries = Lists.newArrayList();
+    List<Map<String, String>> entries = Lists.newArrayList();
     GitilesView.Builder urlBuilder = GitilesView.path().copyFrom(view);
     while (tw.next()) {
       FileType type = FileType.forEntry(tw);
@@ -129,6 +141,8 @@
       entries.add(entry);
     }
 
+    entries.sort(TreeSoyData::sortByType);
+
     Map<String, Object> data = Maps.newHashMapWithExpectedSize(3);
     data.put("sha", treeId.name());
     data.put("entries", entries);
diff --git a/javatests/com/google/gitiles/PathServletTest.java b/javatests/com/google/gitiles/PathServletTest.java
index a32de9f..68b1e3e 100644
--- a/javatests/com/google/gitiles/PathServletTest.java
+++ b/javatests/com/google/gitiles/PathServletTest.java
@@ -64,8 +64,8 @@
     assertThat(data).containsEntry("type", "TREE");
     List<Map<String, ?>> entries = getTreeEntries(data);
     assertThat(entries).hasSize(2);
-    assertThat(entries.get(0).get("name")).isEqualTo("baz");
-    assertThat(entries.get(1).get("name")).isEqualTo("foo/");
+    assertThat(entries.get(0).get("name")).isEqualTo("foo/");
+    assertThat(entries.get(1).get("name")).isEqualTo("baz");
 
     data = buildData("/repo/+/master/foo");
     assertThat(data).containsEntry("type", "TREE");
diff --git a/javatests/com/google/gitiles/TreeSoyDataTest.java b/javatests/com/google/gitiles/TreeSoyDataTest.java
index 98d416a..cafe4e2 100644
--- a/javatests/com/google/gitiles/TreeSoyDataTest.java
+++ b/javatests/com/google/gitiles/TreeSoyDataTest.java
@@ -17,8 +17,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gitiles.TreeSoyData.getTargetDisplayName;
 import static com.google.gitiles.TreeSoyData.resolveTargetUrl;
+import static com.google.gitiles.TreeSoyData.sortByType;
 
 import com.google.common.base.Strings;
+import java.util.Map;
+import java.util.HashMap;
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -64,4 +67,25 @@
     assertThat(resolveTargetUrl(view, "../../../../")).isNull();
     assertThat(resolveTargetUrl(view, "../../a/../../..")).isNull();
   }
+
+  @Test
+  public void sortByTypeSortsCorrect() throws Exception {
+    Map<String, String> m1 = new HashMap<String, String>();
+    Map<String, String> m2 = new HashMap<String, String>();
+    Map<String, String> m3 = new HashMap<String, String>();
+    Map<String, String> m4 = new HashMap<String, String>();
+    Map<String, String> m5 = new HashMap<String, String>();
+    m1.put("type", "TREE");
+    m2.put("type", "TREE");
+    m3.put("type", "SYMLINK");
+    m4.put("type", "REGULAR_FILE");
+    m5.put("type", "GITLINK");
+    assertThat(sortByType(m1, m2)).isEqualTo(0);
+    assertThat(sortByType(m2, m3)).isEqualTo(-1);
+    assertThat(sortByType(m3, m4)).isEqualTo(-1);
+    assertThat(sortByType(m4, m1)).isEqualTo(1);
+    assertThat(sortByType(m1, m4)).isEqualTo(-1);
+    assertThat(sortByType(m5, m2)).isEqualTo(1);
+    assertThat(sortByType(m2, m5)).isEqualTo(-1);
+  }
 }