Add pagination options to ListBranches REST API

Add limit and skip options to allow pagination. Those options are
supported by other similar REST API (ListProjects, ListGroups).

Change-Id: I2d14c8cc33f00033ef894b550e29e377ac5778b6
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index e2e7fb0..20648dc 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -933,6 +933,54 @@
 [[branch-options]]
 ==== Branch Options
 
+Limit(n)::
+Limit the number of branches to be included in the results.
++
+.Request
+----
+  GET /projects/testproject/branches?n=1 HTTP/1.0
+----
++
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "ref": "HEAD",
+      "revision": "master",
+      "can_delete": false
+    }
+  ]
+----
+
+Skip(s)::
+Skip the given number of branches from the beginning of the list.
++
+.Request
+----
+  GET /projects/testproject/branches?n=1&s=0 HTTP/1.0
+----
++
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  [
+    {
+      "ref": "HEAD",
+      "revision": "master",
+      "can_delete": false
+    }
+  ]
+----
+
 Substring(m)::
 Limit the results to those projects that match the specified substring.
 +
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
index 75d2762..ca8d57b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
@@ -120,6 +120,56 @@
   }
 
   @Test
+  public void listBranchesUsingPagination() throws Exception {
+    pushTo("refs/heads/master");
+    pushTo("refs/heads/someBranch1");
+    pushTo("refs/heads/someBranch2");
+    pushTo("refs/heads/someBranch3");
+
+    // using only limit
+    RestResponse r =
+        adminSession.get("/projects/" + project.get() + "/branches?n=4");
+    List<BranchInfo> result = toBranchInfoList(r);
+    assertEquals(4, result.size());
+    assertEquals("HEAD", result.get(0).ref);
+    assertEquals("refs/meta/config", result.get(1).ref);
+    assertEquals("refs/heads/master", result.get(2).ref);
+    assertEquals("refs/heads/someBranch1", result.get(3).ref);
+
+    // limit higher than total number of branches
+    r = adminSession.get("/projects/" + project.get() + "/branches?n=25");
+    result = toBranchInfoList(r);
+    assertEquals(6, result.size());
+    assertEquals("HEAD", result.get(0).ref);
+    assertEquals("refs/meta/config", result.get(1).ref);
+    assertEquals("refs/heads/master", result.get(2).ref);
+    assertEquals("refs/heads/someBranch1", result.get(3).ref);
+    assertEquals("refs/heads/someBranch2", result.get(4).ref);
+    assertEquals("refs/heads/someBranch3", result.get(5).ref);
+
+    // using skip only
+    r = adminSession.get("/projects/" + project.get() + "/branches?s=2");
+    result = toBranchInfoList(r);
+    assertEquals(4, result.size());
+    assertEquals("refs/heads/master", result.get(0).ref);
+    assertEquals("refs/heads/someBranch1", result.get(1).ref);
+    assertEquals("refs/heads/someBranch2", result.get(2).ref);
+    assertEquals("refs/heads/someBranch3", result.get(3).ref);
+
+    // skip more branches than the number of available branches
+    r = adminSession.get("/projects/" + project.get() + "/branches?s=7");
+    result = toBranchInfoList(r);
+    assertEquals(0, result.size());
+
+    // using skip and limit
+    r = adminSession.get("/projects/" + project.get() + "/branches?s=2&n=2");
+    result = toBranchInfoList(r);
+    assertEquals(2, result.size());
+    assertEquals("refs/heads/master", result.get(0).ref);
+    assertEquals("refs/heads/someBranch1", result.get(1).ref);
+  }
+
+  @Test
   public void listBranchesUsingFilter() throws Exception {
     pushTo("refs/heads/master");
     pushTo("refs/heads/someBranch1");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index 6f42148..6e07fb4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -58,6 +58,12 @@
   private final DynamicMap<RestView<BranchResource>> branchViews;
   private final WebLinks webLinks;
 
+  @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of branches to list")
+  private int limit;
+
+  @Option(name = "--start", aliases = {"-s"}, metaVar = "CNT", usage = "number of branches to skip")
+  private int start;
+
   @Option(name = "--match", aliases = {"-m"}, metaVar = "MATCH", usage = "match branches substring")
   private String matchSubstring;
 
@@ -170,6 +176,17 @@
     } else {
       filteredBranches = branches;
     }
+    if (!filteredBranches.isEmpty()) {
+      int end = filteredBranches.size();
+      if (limit > 0 && start + limit < end) {
+        end = start + limit;
+      }
+      if (start <= end) {
+        filteredBranches = filteredBranches.subList(start, end);
+      } else {
+        filteredBranches = Collections.emptyList();
+      }
+    }
     return filteredBranches;
   }