Add support for --name-status to /+log/

Change-Id: Ia32cdd8e10d0089954897e1fc30ab5549d8256c5
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
index e1594fc..41a0239 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
@@ -24,7 +24,9 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.google.common.primitives.Longs;
+import com.google.gitiles.CommitData.Field;
 import com.google.gitiles.DateFormatter.Format;
 import com.google.gson.reflect.TypeToken;
 
@@ -58,6 +60,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -70,6 +73,7 @@
   static final String LIMIT_PARAM = "n";
   static final String START_PARAM = "s";
   private static final String PRETTY_PARAM = "pretty";
+  private static final String NAME_STATUS_PARAM = "name-status";
   private static final int DEFAULT_LIMIT = 100;
   private static final int MAX_LIMIT = 10000;
 
@@ -144,13 +148,20 @@
       return;
     }
 
+    Set<Field> fs = Sets.newEnumSet(CommitJsonData.DEFAULT_FIELDS, Field.class);
+    String nameStatus = Iterables.getFirst(view.getParameters().get(NAME_STATUS_PARAM), null);
+    if ("1".equals(nameStatus) || "".equals(nameStatus)) {
+      fs.add(Field.DIFF_TREE);
+    }
+
     try {
       DateFormatter df = new DateFormatter(getAccess(req), Format.DEFAULT);
       Map<String, Object> result = Maps.newLinkedHashMap();
       List<CommitJsonData.Commit> entries = Lists.newArrayListWithCapacity(paginator.getLimit());
       for (RevCommit c : paginator) {
         paginator.getWalk().parseBody(c);
-        entries.add(new CommitJsonData().setRevWalk(paginator.getWalk()).toJsonData(req, c, df));
+        entries.add(new CommitJsonData().setRevWalk(paginator.getWalk())
+            .toJsonData(req, c, fs, df));
       }
       result.put("log", entries);
       if (paginator.getPreviousStart() != null) {
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/LogServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/LogServletTest.java
new file mode 100644
index 0000000..0acb39d
--- /dev/null
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/LogServletTest.java
@@ -0,0 +1,109 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// 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.gitiles;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gitiles.DateFormatter.Format;
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+
+/** Tests for {@link LogServlet}. */
+@RunWith(JUnit4.class)
+public class LogServletTest extends ServletTest {
+  @Test
+  public void basicLog() throws Exception {
+    RevCommit commit = repo.branch("HEAD").commit().create();
+    repo.getRevWalk().parseBody(commit);
+
+    JsonElement result = buildJson("/repo/+log", "");
+    JsonArray log = result.getAsJsonObject().get("log").getAsJsonArray();
+
+    GitilesAccess access = new TestGitilesAccess(repo.getRepository()).forRequest(null);
+    DateFormatter df = new DateFormatter(access, Format.DEFAULT);
+
+    assertThat(log).hasSize(1);
+    CommitJsonData.Commit jsonCommit = getJsonCommit(log.get(0));
+    verifyJsonCommit(jsonCommit, commit, df);
+    assertThat(jsonCommit.treeDiff).isNull();
+  }
+
+  @Test
+  public void treeDiffLog() throws Exception {
+    String contents1 = "foo\n";
+    String contents2 = "foo\ncontents\n";
+    RevCommit c1 = repo.update("master", repo.commit().add("foo", contents1));
+    RevCommit c2 = repo.update("master", repo.commit().parent(c1).add("foo", contents2));
+    repo.getRevWalk().parseBody(c1);
+    repo.getRevWalk().parseBody(c2);
+
+    JsonElement result = buildJson("/repo/+log/master", "&name-status=1");
+    JsonArray log = result.getAsJsonObject().get("log").getAsJsonArray();
+
+    GitilesAccess access = new TestGitilesAccess(repo.getRepository()).forRequest(null);
+    DateFormatter df = new DateFormatter(access, Format.DEFAULT);
+
+    assertThat(log).hasSize(2);
+
+    CommitJsonData.Commit jsonCommit2 = getJsonCommit(log.get(0));
+    verifyJsonCommit(jsonCommit2, c2, df);
+    assertThat(jsonCommit2.treeDiff).hasSize(1);
+    assertThat(jsonCommit2.treeDiff.get(0).type).isEqualTo("modify");
+    assertThat(jsonCommit2.treeDiff.get(0).oldPath).isEqualTo("foo");
+    assertThat(jsonCommit2.treeDiff.get(0).newPath).isEqualTo("foo");
+
+    CommitJsonData.Commit jsonCommit1 = getJsonCommit(log.get(1));
+    verifyJsonCommit(jsonCommit1, c1, df);
+    assertThat(jsonCommit1.treeDiff.get(0).type).isEqualTo("add");
+    assertThat(jsonCommit1.treeDiff.get(0).oldPath).isEqualTo("/dev/null");
+    assertThat(jsonCommit1.treeDiff.get(0).newPath).isEqualTo("foo");
+  }
+
+  private CommitJsonData.Commit getJsonCommit(JsonElement element) {
+    GsonBuilder builder = new GsonBuilder();
+    builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
+    return builder.create().fromJson(element, CommitJsonData.Commit.class);
+  }
+
+  private void verifyJsonCommit(
+      CommitJsonData.Commit jsonCommit, RevCommit commit, DateFormatter df)
+      throws Exception {
+    assertThat(jsonCommit.commit).isEqualTo(commit.name());
+    assertThat(jsonCommit.tree).isEqualTo(commit.getTree().name());
+
+    ArrayList<String> expectedParents = new ArrayList<>();
+    for (int i = 0; i < commit.getParentCount(); i++) {
+      expectedParents.add(commit.getParent(i).name());
+    }
+    assertThat(jsonCommit.parents).containsExactlyElementsIn(expectedParents);
+
+    assertThat(jsonCommit.author.name).isEqualTo(commit.getAuthorIdent().getName());
+    assertThat(jsonCommit.author.email).isEqualTo(commit.getAuthorIdent().getEmailAddress());
+    assertThat(jsonCommit.author.time).isEqualTo(df.format(commit.getAuthorIdent()));
+    assertThat(jsonCommit.committer.name).isEqualTo(commit.getCommitterIdent().getName());
+    assertThat(jsonCommit.committer.email).isEqualTo(commit.getCommitterIdent().getEmailAddress());
+    assertThat(jsonCommit.committer.time).isEqualTo(df.format(commit.getCommitterIdent()));
+    assertThat(jsonCommit.message).isEqualTo(commit.getFullMessage());
+  }
+}
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java
index 14aeb63..3eac7ea 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java
@@ -20,6 +20,8 @@
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 
 import com.google.common.net.HttpHeaders;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
 import com.google.gson.Gson;
 
 import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
@@ -90,8 +92,8 @@
     return res;
   }
 
-  private String buildJsonRaw(String path) throws Exception {
-    FakeHttpServletResponse res = buildResponse(path, "format=json", SC_OK);
+  private String buildJsonRaw(String path, String additionalQueryString) throws Exception {
+    FakeHttpServletResponse res = buildResponse(path, "format=json" + additionalQueryString, SC_OK);
     assertThat(res.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json");
     String body = res.getActualBodyString();
     String magic = ")]}'\n";
@@ -99,12 +101,16 @@
     return body.substring(magic.length());
   }
 
+  protected JsonElement buildJson(String path, String additionalQueryString) throws Exception {
+    return new JsonParser().parse(buildJsonRaw(path, additionalQueryString));
+  }
+
   protected <T> T buildJson(String path, Class<T> classOfT) throws Exception {
-    return new Gson().fromJson(buildJsonRaw(path), classOfT);
+    return new Gson().fromJson(buildJsonRaw(path, ""), classOfT);
   }
 
   protected <T> T buildJson(String path, Type typeOfT) throws Exception {
-    return new Gson().<T>fromJson(buildJsonRaw(path), typeOfT);
+    return new Gson().<T>fromJson(buildJsonRaw(path, ""), typeOfT);
   }
 
   protected void assertNotFound(String path, String queryString) throws Exception {