Add "pretty" log variants for more flexible log formatting.

Variant views are triggered by passing the 'pretty=FORMAT' URL param, such
as 'pretty=full' or 'pretty=oneline'.

Additional variants or overrides can be declared in gitiles.config with:
  [logFormat "oneline"]
    variant = myOnelineVariantOverride
  [logFormat "special"]
    variant = myCompletelyCustomVariant

Change-Id: If30554995062110bfbf22ee8760db8303d50b69c
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 4d1da1c..ff1fb7b 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
@@ -18,6 +18,7 @@
 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Optional;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
@@ -33,6 +34,7 @@
 import org.eclipse.jgit.errors.RevWalkException;
 import org.eclipse.jgit.http.server.ServletUtils;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
@@ -63,6 +65,7 @@
 
   static final String LIMIT_PARAM = "n";
   static final String START_PARAM = "s";
+  private static final String PRETTY_PARAM = "pretty";
   private static final int DEFAULT_LIMIT = 100;
   private static final int MAX_LIMIT = 10000;
 
@@ -85,8 +88,16 @@
 
     try {
       GitilesAccess access = getAccess(req);
+      Config config = access.getConfig();
       DateFormatter df = new DateFormatter(access, Format.DEFAULT);
-      Map<String, Object> data = new LogSoyData(req, view).toSoyData(paginator, null, df);
+
+      // Allow the user to select a logView variant with the "pretty" param.
+      String pretty = Iterables.getFirst(view.getParameters().get(PRETTY_PARAM), "default");
+      Map<String, Object> data = new LogSoyData(req, view,
+          config.getBoolean("logFormat", pretty, "verbose", false)).toSoyData(paginator, null, df);
+      String variant = config.getString("logFormat", pretty, "variant");
+      data.put("logEntryPretty", pretty);
+      data.put("logEntryVariant", Objects.firstNonNull(variant, pretty));
 
       if (!view.getRevision().nameIsId()) {
         List<Map<String, Object>> tags = Lists.newArrayListWithExpectedSize(1);
@@ -108,7 +119,6 @@
       }
 
       data.put("title", title);
-      GitilesConfig.putVariant(access.getConfig(), "logEntry", "logEntryVariant", data);
 
       renderHtml(req, res, "gitiles.logDetail", data);
     } catch (RevWalkException e) {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java b/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java
index a4be388..cdfdcf5 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/LogSoyData.java
@@ -27,20 +27,29 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 import javax.servlet.http.HttpServletRequest;
 
 public class LogSoyData {
   private static final ImmutableSet<Field> FIELDS = Sets.immutableEnumSet(Field.ABBREV_SHA,
-      Field.URL, Field.SHORT_MESSAGE, Field.AUTHOR, Field.COMMITTER, Field.BRANCHES, Field.TAGS);
+      Field.SHA, Field.URL, Field.SHORT_MESSAGE, Field.MESSAGE, Field.AUTHOR, Field.COMMITTER,
+      Field.BRANCHES, Field.TAGS);
+  private static final ImmutableSet<Field> VERBOSE_FIELDS = Field.setOf(FIELDS, Field.DIFF_TREE);
 
   private final HttpServletRequest req;
   private final GitilesView view;
+  private final boolean verbose;
 
   public LogSoyData(HttpServletRequest req, GitilesView view) {
+    this(req, view, false);
+  }
+
+  public LogSoyData(HttpServletRequest req, GitilesView view, boolean verbose) {
     this.req = req;
     this.view = view;
+    this.verbose = verbose;
   }
 
   public Map<String, Object> toSoyData(RevWalk walk, int limit, @Nullable String revision,
@@ -54,7 +63,8 @@
 
     List<Map<String, Object>> entries = Lists.newArrayListWithCapacity(paginator.getLimit());
     for (RevCommit c : paginator) {
-      entries.add(new CommitSoyData().toSoyData(req, c, FIELDS, df));
+      Set<Field> fs = verbose ? VERBOSE_FIELDS : FIELDS;
+      entries.add(new CommitSoyData().setRevWalk(paginator.getWalk()).toSoyData(req, c, fs, df));
     }
 
     data.put("entries", entries);
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css b/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css
index 4418114..f1d5326 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css
@@ -183,14 +183,18 @@
   text-align: right;
 }
 pre.commit-message, pre.tag-message {
-  border-bottom: #ddd solid 1px; /* BORDER */
   padding: 5px 2em; /* PADDING */
   color: #000;
   font-size: 9pt;
   margin: 0;
 }
 
+ol.log ul.diff-tree {
+  margin-left: 2em;
+  padding: 5px 0 5px 0;
+}
 ul.diff-tree {
+  border-top: #ddd solid 1px; /* BORDER */
   font-size: 9pt;
   list-style-type: none;
   margin: 0;
@@ -260,23 +264,29 @@
 
 /* Styles for the log detail page. */
 
-ol.shortlog {
+ol.log {
   list-style-type: none;
   margin: 0;
+  padding-left: 0;
+}
+ol.oneline.log, ol.default.log {
   padding: 5px 2em; /* PADDING */
 }
-ol.shortlog li {
+ol.log > li {
   border-bottom: #ddd solid 1px; /* BORDER */
   padding-top: 2px;
   padding-bottom: 2px;
 }
-ol.shortlog li.first {
+ol.full.log > li, ol.fuller.log > li {
+  padding-bottom: 5px;
+}
+ol.log > li.first {
   border-top: #ddd solid 1px; /* BORDER */
 }
-ol.shortlog li:hover {
+ol.log > li:hover {
   background: #eee; /* HOVER_BACKGROUND */
 }
-ol.shortlog .sha1 {
+ol.log span.sha1 {
   font-family: monospace;
 }
 .log-nav {
@@ -289,10 +299,11 @@
 .time {
   font-size: 9pt; /* SHORTLOG_SMALL_FONT_SIZE */
   font-style: italic;
+  margin-right: 3px;
 }
 .branch-label, .tag-label {
   font-size: 9pt; /* SHORTLOG_SMALL_FONT_SIZE */
-  margin-left: 3px;
+  margin-right: 3px;
 }
 a.branch-label {
   color: #dd4b39;
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy
index a834148..c617a08 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/LogDetail.soy
@@ -46,6 +46,7 @@
  * List of log entries.
  *
  * @param? logEntryVariant variant name for log entry template.
+ * @param? logEntryPretty base "pretty" format for the log entry template.
  * @param entries list of log entries; see .logEntry.
  * @param? nextUrl URL for the next page of results.
  * @param? previousUrl URL for the previous page of results.
@@ -58,7 +59,7 @@
 {/if}
 
 {if length($entries)}
-  <ol class="shortlog">
+  <ol class="{$logEntryPretty ?: 'default'} log">
     {foreach $entry in $entries}
       <li{if $previousUrl and isFirst($entry)} class="first"{/if}>
         {delcall gitiles.logEntry variant="$logEntryVariant ?: 'default'"
@@ -79,23 +80,27 @@
 
 
 /**
- * Single shortlog entry.
+ * Single pretty log entry, similar to --pretty=oneline.
  *
  * @param abbrevSha abbreviated SHA-1.
+ * @param sha commit SHA-1.
  * @param url URL to commit detail page.
  * @param shortMessage short commit message.
- * @param author author information with at least "name", "time", and "relativeTime" keys.
- * @param committer committer information with at least "name", "time", and "relativeTime" keys.
+ * @param message list of commit message parts, where each part contains:
+ *     text: raw text of the part.
+ *     url: optional URL that should be linked to from the part.
+ * @param author author information with at least "name" and "relativeTime" keys.
+ * @param committer committer information with at least "time" and "relativeTime" keys.
  * @param branches list of branches for this entry, with "name" and "url" keys.
  * @param tags list of tags for this entry, with "name" and "url" keys.
  */
-{deltemplate gitiles.logEntry variant="'default'"}
+{deltemplate gitiles.logEntry variant="'oneline'"}
 <a href="{$url}">
   <span class="sha1">{$abbrevSha}</span>
   // nbsp instad of CSS padding/margin because those cause a break in the
   // underline.
   &nbsp;
-  {sp}<span class="commit-message">{$shortMessage}</span>
+  {sp}<span>{$shortMessage}</span>
 </a>
 {sp}<span class="author" title="{$author.email}">{msg desc="commit author name"}by {$author.name}{/msg}</span>
 {sp}<span class="time" title="{$author.time}">- {$author.relativeTime}</span>
@@ -110,3 +115,79 @@
   {/foreach}
 {/if}
 {/deltemplate}
+
+
+/**
+ * Default single log entry (oneline format).
+ *
+ * @param abbrevSha abbreviated SHA-1.
+ * @param sha commit SHA-1.
+ * @param url URL to commit detail page.
+ * @param shortMessage short commit message.
+ * @param message list of commit message parts, where each part contains:
+ *     text: raw text of the part.
+ *     url: optional URL that should be linked to from the part.
+ * @param author author information with at least "name" and "relativeTime" keys.
+ * @param committer committer information with at least "time" and "relativeTime" keys.
+ * @param branches list of branches for this entry, with "name" and "url" keys.
+ * @param tags list of tags for this entry, with "name" and "url" keys.
+ */
+{deltemplate gitiles.logEntry variant="'default'"}
+{delcall gitiles.logEntry variant="'oneline'" data="all" /}
+{/deltemplate}
+
+
+/**
+ * Single pretty log entry, similar to --pretty=full.
+ *
+ * @param abbrevSha abbreviated SHA-1.
+ * @param sha commit SHA-1.
+ * @param url URL to commit detail page.
+ * @param shortMessage short commit message.
+ * @param message list of commit message parts, where each part contains:
+ *     text: raw text of the part.
+ *     url: optional URL that should be linked to from the part.
+ * @param author author information with at least "name" and "relativeTime" keys.
+ * @param committer committer information with at least "time" and "relativeTime" keys.
+ * @param branches list of branches for this entry, with "name" and "url" keys.
+ * @param tags list of tags for this entry, with "name" and "url" keys.
+ */
+{deltemplate gitiles.logEntry variant="'full'"}
+<div class="git-commit">
+<table>
+  <tr>
+    <th>{msg desc="Header for commit SHA entry"}commit{/msg}</th>
+    <td class="sha1">
+      <a href="{$url}">{$sha}</a>
+    </td>
+    <td>
+      {if length($branches)}
+        {foreach $branch in $branches}
+          {sp}<a href="{$branch.url}" class="branch-label">{$branch.name}</a>
+        {/foreach}
+      {/if}
+      {if length($tags)}
+        {foreach $tag in $tags}
+          {sp}<a href="{$tag.url}" class="tag-label">{$tag.name}</a>
+        {/foreach}
+      {else}
+        {sp}
+      {/if}
+    </td>
+  </tr>
+  <tr>
+    <th>{msg desc="Header for commit author"}author{/msg}</th>
+    <td>{call .person_ data="$author" /}</td>
+    <td>{$author.time}</td>
+  </tr>
+  <tr>
+    <th>{msg desc="Header for committer"}committer{/msg}</th>
+    <td>{call .person_ data="$committer" /}</td>
+    <td>{$committer.time}</td>
+  </tr>
+</table>
+</div>
+<pre class="commit-message">
+  {$message}
+</pre>
+{/deltemplate}