Dissolve top level gitiles-* directories

The current layout of having gitiles-* directories with separate java
source trees dates back to the bad era of Maven, when this specific
layout was necessary to decompose the build into separate pom.xml files.

Moreover, src/{main,test}/java are also Maven artifacts, so that we go
even further and create three top level directories:

* java
* javatests
* resources

Change-Id: I4421096428db1e3de019a9b6c1253217cf7e4fbe
diff --git a/resources/BUILD b/resources/BUILD
new file mode 100644
index 0000000..020f145
--- /dev/null
+++ b/resources/BUILD
@@ -0,0 +1,5 @@
+filegroup(
+    name = "web_xml",
+    srcs = ["web.xml"],
+    visibility = ["//visibility:public"],
+)
diff --git a/resources/com/google/gitiles/BUILD b/resources/com/google/gitiles/BUILD
new file mode 100644
index 0000000..c5df3e1
--- /dev/null
+++ b/resources/com/google/gitiles/BUILD
@@ -0,0 +1,27 @@
+load(
+    "@com_googlesource_gerrit_bazlets//tools:genrule2.bzl",
+    "genrule2",
+)
+
+filegroup(
+    name = "gitiles",
+    srcs = glob(
+        ["**/*"],
+        exclude = ["BUILD"],
+    ),
+    visibility = ["//visibility:public"],
+)
+
+genrule2(
+    name = "webassets",
+    srcs = [":gitiles"],
+    outs = ["webassets.zip"],
+    cmd = " && ".join([
+        "o=$$PWD/$@",
+        "tar cf - $(SRCS) | tar -C $$TMP/ --strip-components=1 -xf -",
+        "cd $$TMP/com/google/gitiles/",
+        "mv static +static",
+        "zip -qr $$o .",
+    ]),
+    visibility = ["//visibility:public"],
+)
diff --git a/resources/com/google/gitiles/mime-types.properties b/resources/com/google/gitiles/mime-types.properties
new file mode 100644
index 0000000..0b4bd26
--- /dev/null
+++ b/resources/com/google/gitiles/mime-types.properties
@@ -0,0 +1,16 @@
+bmp = image/bmp
+css = text/css
+csv = text/csv
+gif = image/gif
+htm = text/html
+html = text/html
+jpeg = image/jpeg
+jpg = image/jpeg
+js = application/javascript
+md = text/markdown
+pdf = application/pdf
+png = image/png
+svg = image/svg+xml
+tiff = image/tiff
+txt = text/plain
+xml = text/xml
diff --git a/resources/com/google/gitiles/static/base.css b/resources/com/google/gitiles/static/base.css
new file mode 100644
index 0000000..2a56084
--- /dev/null
+++ b/resources/com/google/gitiles/static/base.css
@@ -0,0 +1,517 @@
+/**
+ * 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.
+ */
+
+/* Common styles and definitions. */
+
+@import "//fonts.googleapis.com/css?family=Open+Sans:300,400,700&subset=latin,cyrillic-ext,greek-ext,cyrillic,greek,vietnamese,latin-ext";
+@import "//fonts.googleapis.com/css?family=Source+Code+Pro";
+*,
+*::after,
+*::before {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+}
+h1, h2, h3, h4, h5, h6 {
+  font-weight: normal;
+  margin: .25em 0 .5em;
+}
+h1 {
+  font-size: 2em;
+}
+h2 {
+  font-size: 1.5em;
+}
+h3 {
+  font-size: 1.3em;
+}
+h4, h5, h6 {
+  font-size: 14px;
+  font-style: italic;
+}
+ul, ol {
+  list-style: none;
+}
+
+/* Utility classes */
+
+.u-sha1 {
+  background-color: #f1f2f3;
+  color: #000;
+  font-size: 13px;
+}
+.u-pre {
+  font-size: 10pt;
+  font-weight: 500;
+  white-space: pre;
+}
+.u-lineNum {
+  border-right: 1px solid #f1f2f3;
+  color: #666;
+  display: inline-block;
+  min-width: 3em;
+  text-align: right;
+}
+.u-noSelect {
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.u-monospace {
+  font-family: 'Source Code Pro', monospace;
+}
+
+/* Common.soy */
+
+.Site {
+  background: #fff;
+  color: #000;
+  display: -ms-flexbox;
+  display: flex;
+  font: 14px/1.54 'Open Sans', sans-serif;
+  min-height: 100vh;
+  -ms-flex-direction: column;
+  flex-direction: column;
+}
+.Site-header,
+.Site-footer {
+  background: #eee;
+  -ms-flex: none;
+  flex: none;
+}
+.Site-header--withNavbar {
+  background: #fff;
+}
+.Site-content {
+  -ms-flex: 1 0 auto;
+  flex: 1 0 auto;
+  padding: 20px;
+}
+.Container {
+  margin: 0 auto;
+  max-width: 980px;
+}
+.Container--fullWidth {
+  max-width: none;
+}
+.Header,
+.Footer {
+  -ms-flex-align: center;
+  align-items: center;
+  display: -ms-flexbox;
+  display: flex;
+  padding: 20px;
+}
+.Site-header--withNavbar .Header {
+  max-width: 980px;
+  margin: 0 auto;
+  padding: 10px 0;
+}
+.Header-title,
+.Header-image {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex: 1;
+  flex: 1;
+}
+.Header-anchor {
+  color: #666;
+  font-size: 32px;
+  font-weight: 300;
+  text-decoration: none;
+}
+.Header-anchorLogo {
+  display: inline-block;
+  margin-right: 10px;
+  vertical-align: middle;
+}
+.Header-nav {
+  background: #eee;
+  padding: 15px 0;
+}
+.Header-menu {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-pack: end;
+  justify-content: flex-end;
+}
+.Header-menuItem {
+  color: #00e;
+  display: inline-block;
+  margin-left: 15px;
+}
+.Header-menuItem--noAction {
+  color: inherit;
+}
+.Breadcrumbs {
+  font-size: 18px;
+  margin-bottom: 20px;
+}
+.Breadcrumbs-crumb {
+  color: #00e;
+}
+.Breadcrumbs-crumb:last-child {
+  color: #000;
+  font-weight: bold;
+}
+.Footer {
+  color: #666;
+}
+.Footer-poweredBy {
+  -ms-flex: 1;
+  flex: 1;
+}
+.Footer-formats,
+.Footer-links {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-pack: end;
+  justify-content: flex-end;
+}
+.Footer-formatsItem {
+  display: inline-block;
+}
+.Footer-formatsItem:first-child {
+  margin-right: 20px;
+}
+.Footer-link {
+  display: inline-block;
+  margin-left: 10px;
+}
+.RepoList-item {
+  display: -ms-flexbox;
+  display: flex;
+  left: -10px;
+  padding: 5px 0 5px 10px;
+  position: relative;
+  white-space: nowrap;
+  width: calc(100% + 20px);
+}
+.RepoList-item:link,
+.RepoList-item:visited {
+  text-decoration: none;
+}
+.RepoList-item:hover {
+  background: #eee;
+}
+.RepoList-item--header {
+  font-weight: bold;
+  margin: 0;
+}
+.RepoList-item--header:hover {
+  background: #fff;
+}
+.RepoList-itemName,
+.RepoList-itemDescription {
+  display: inline-block;
+}
+.RepoList-itemName {
+  margin-right: 10px;
+  min-width: 25%;
+  text-decoration: underline;
+}
+.RepoList-item--header > .RepoList-itemName {
+  text-decoration: none;
+}
+.RepoList-itemDescription {
+  color: #000;
+  -ms-flex: 1;
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* RepositoryIndex.soy */
+
+.RepoDescription {
+  margin: 10px 0;
+}
+.RepoMirroredFrom {
+  margin: 10px 0;
+  color: #666;
+}
+.CloneRepo {
+  background: #eee;
+  margin-bottom: 20px;
+  padding: 10px;
+}
+.CloneRepo-title {
+  margin-bottom: 2px;
+}
+.CloneRepo-command {
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  display: block;
+  font-size: inherit;
+  padding: 10px;
+  width: 100%;
+}
+.RepoShortlog {
+  display: -ms-flexbox;
+  display: flex;
+}
+.RepoShortlog-refs {
+  -ms-flex: none !important;
+  flex: none !important;
+  width: 20%;
+}
+.RepoShortlog-refs > .RefList:first-child {
+  margin: 0;
+}
+.RepoShortlog-log {
+  -ms-flex: 1;
+  flex: 1;
+  width: 80%;
+}
+.RepoIndexDoc {
+  border-top: 1px solid #ddd;
+  margin-top: 20px;
+  padding-top: 5px;
+}
+
+/* RefList.soy */
+
+.Refs {}
+.RefList {
+  margin: 15px 0;
+}
+.RefList-title {
+  margin: 0;
+}
+.RefList-items {}
+.RefList-item {
+  padding: 2px 0;
+}
+
+/* LogDetail.soy */
+
+.LogNav {
+  margin: 10px 0;
+  text-align: center;
+}
+.CommitLog {}
+.CommitLog-item {
+  padding: 2px 0;
+}
+.CommitLog-item--oneline:hover {
+  background: #eee;
+}
+.CommitLog-item--full {
+  margin-bottom: 20px;
+}
+.CommitLog-item--empty {
+  padding: 10px 0;
+  text-align: center;
+}
+.CommitLog-sha1 {
+  border-radius: 3px;
+  display: inline-block;
+  margin-right: 3px;
+  padding: 2px 4px;
+  text-align: center;
+}
+.CommitLog-time {
+  color: #666;
+}
+.CommitLog-branchLabel {
+  color: #dd4b39;
+}
+.CommitLog-tagLabel {
+  color: #093;
+}
+.CommitLog-rename {
+  font-size: 0.9em;
+  display: block;
+  padding-left: 5px;
+}
+
+/* ObjectDetail.soy */
+
+.Metadata {
+  margin-bottom: 15px;
+}
+.Metadata-title {
+  font-weight: bold;
+  padding-right: 10px;
+  text-align: right;
+}
+.MetadataMessage {
+  background-color: #fafafa;
+  border: 1px solid #ccc;
+  color: #000;
+  margin: 0;
+  padding: 12px;
+  white-space: pre-wrap;
+}
+.DiffTree {
+  margin: 10px 0 5px;
+}
+.DiffTree-action {
+  margin-left: .5em;
+}
+.DiffTree-action--add {
+  color: #060;
+}
+.DiffTree-action--delete {
+  color: #600;
+}
+.DiffTree-action--rename,
+.DiffTree-action--copy {
+  color: #006;
+}
+.DiffSummary {}
+.TreeDetail-sha1,
+.BlobSha1 {
+  margin: 10px 0;
+  padding: 5px 10px;
+}
+.FileList {
+  margin-left: 25px;
+}
+.FileList-item {
+  padding: 1px 0;
+  position: relative;
+}
+.FileList-item:hover {
+  background: #eee;
+}
+.FileList-item::before {
+  left: -22px;
+  position: absolute;
+  top: 4px;
+}
+.FileList-itemLink {
+  display: block;
+}
+/* Tree icons are taken from the public domain Tango icons:
+ * http://tango.freedesktop.org/Tango_Icon_Library
+ * Compressed with pngcrush -brute -rem tEXt -rem tIME -rem iTXt -rem zTXt */
+.FileList-item--gitTree::before {
+  /* places/folder.png */
+  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAAb5JREFUOMulkr1KA0EQgGdvTwwnYmER0gQsrFKmSy+pLESw9Qm0F/ICNnba+h6iEOuAEWslKJKTOyJJvIT72d1xZuOFC0giOLA77O7Mt/PnNptN+I+49Xr9GhH3f3mb0v1ht9vtLAUYYw5ItkgDL3KyD8PhcLvdbl/WarXT3DjLMnAcR/f7/YfxeKwtgC5RKQVhGILWeg4hQ6hUKjWyucmhLFEUuWR3QYBWAZABQ9i5CCmXy16pVALP80BKaaG+70MQBLvzFMjRKKXh8j6FSYKF7ITdEWLa4/ktokN74wiqjSMpnVcbQZqmEJHz+ckeCPFjWKwULpyspAqhdXVXdcnZcPjsIgn+2BsVA8jVYuWlgJ3yBj0icgq2uoK+lg4t+ZvLomSKamSQ4AI5BcMADtMhyNoSgNIISUaFNtwlazcDcBc4gjjVwCWid2usCWroYEhnaqbzFJLUzAHIXRDChXCcQP8zhkSZ5eNLgHAUzwDcRu4CoIRn/wsGUQIIy4Vr9TH6SYFCNzw4nALn5627K4vIttOUOwfa5YnrDYzt/9OLv9I5l8kk5hZ3XLO20b7tbR7zHLy/BX8G0IeBEM7ZN1NGIaFUaKLgAAAAAElFTkSuQmCC);
+}
+.FileList-item--symlink::before {
+  /* actions/edit-redo.png */
+  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAABZUlEQVQ4y2NgIAL4zWL7D8TGDOQCqAEg7Ey2Aes+58AMcSSkmB2I3YB4HhCfh9kOMoCgIUAJMyDe2D+b58jKe553133M+r/uU/b/zV+L/y97n/i/+JIYbkOAAl5AfGLNTde/69+n/1/4MuD/gtsqKBhkALIh5S0M1jDN2kC8a+UNt/8b36f+X3JP5f/0u1pwjeuvS8E1g3DpZQm4ITAD5s09ZPBq49uE/0vvq4E1gPCJC5z/yy+IoGgG4a5HJqjeCJ3Pc2vjy+T/ux4Y/j99Rfz/7GtK/xfeUkbBN+8pY9cMAkFzuT5uepPy/+w1lf+TF3L/Q4p3OD5zRQ67Zlg873vk9n/mMlaQ5EcgLgZiA2R5nJphCjbfNP8LVeCBJyUa40xpO+5afQXS/8jKC0DJg+uPKx+bOJf1HDYXEJPW46JW8LcduKYzbdZMph4gn4ccQxSAOAuIo4FYdvsKFpYdK1iYCekDACq5JXDHGJhDAAAAAElFTkSuQmCC);
+}
+.FileList-item--regularFile::before {
+  /* mimetypes/text-x-generic.png */
+  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAtElEQVQ4ja2TSw7CIBRFu0r3wyIYsAMHDl1CC92IDqC0wAbQi4Fo5VOMJGcAyT33vQEDY+xCCPGdnIZ48NB7kPkSLMvipZQBpVQC70Br7Y0xZQGC4zj6aZoCnPMPhBBtwb41NoN1XesCBHPNhyfI7fziFu6HJti3IgS0vrcFuZ3Btm2JpmC/M5jnOdEU5JoRstYmigKES80x7JyrC+IE7+1xAoSrgp//AqX02vsbn5nz8K/zAP9CzjbgFoHjAAAAAElFTkSuQmCC);
+}
+.FileList-item--executableFile::before {
+  /* mimetypes/text-x-script.png */
+  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA7ADsAOwdIxY2AAAACXBIWXMAAAsTAAALEwEAmpwYAAABOklEQVQ4T5WTTYqDQBBGPdKcZnIJdy7EVRByBQPi0iwHAlGT0YBHcG82kkV2VnffoKar7FYzIQYLPtqffq+rEJ04jn/CMMSV+XZs0YO1RcyLoO/7MdAD9gAI8wiK+CyYA0IDFABhrmFBAHNYbwYTIxFyWN8KwAiEmIEaknqVUposCoBBGE8cIGUipUKp5IJAzOe2JxI0pG1b3Gw2+Hg8FjoQzy0rLVAavt0GuGkaXrfb7avAzs0CNcHtDD4ejwzTve/7X/9GGFqn2QmkDmzbBJ9OJ8yyDNM0Rdd1J8mzgDqQPDNJLFwUBZ7PZ7xcLizwPI9gfj+NYAX2k+kx7vc7byrLEquqwuv1ivv9HpMk4ec6Uwfvqus63lzXNeZ5zoIRpoqiKP/09wVBwNDhcOB1t9v9OmuLTnw62dQfVIHPYx/I/0kAAAAASUVORK5CYII=);
+}
+.FileList-item--gitlink::before {
+  /* emblems/emblem-symbolic-link.png */
+  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAABO0lEQVQ4T7WT3U6EMBCF92m99yGMZuODGL01eueyXqjvwc+WUlooUPkfZxq3sQEvJLHJSQvlfDOnDbub/dUFCrZof3t9uaPF1kFeBwjD8E+apukfAVEUQRzHkCQJJKfEznES43tUGP0OIOMheIGn50e4f7izCo4H4GkKDEXPBIviCIZh8AFkfv94g5Qx4Jw7AOn4Grh1iqATdrUAEJmxFLIsgzzPQSnpQc4SIkMIh7ZtfQCjys6soNQlVFW1AEgpQWQCjDE+gHIKIaxZo7mu69UOiqJwRXxAxnFDQllS5XUzSeO+kmoJoOzUni41NE1tM9JBTdMII85d14FpjI2lihUA9wCNA8zzDOM4WkBjvgFqrQO8ulzmNgLl/8SPyTT0WL3vLLBGsNZ6HUBV1tT3vROd/Fk/AZt/Z/J+AUN8ayghXmezAAAAAElFTkSuQmCC);
+}
+.FileContents {
+  border-collapse: collapse;
+  border-spacing: 0;
+  margin: 10px 0;
+}
+.FileContents-line {
+  border: none;
+}
+.FileContents-lineNum {
+  padding-right: 10px;
+  width: 1%;
+}
+/* Used to prevent copying the line number. */
+.FileContents-lineNum::before {
+  color: #aaa;
+  content: attr(data-line-number);
+  cursor: pointer;
+}
+.FileContents-lineContents {
+  line-height: 1.3em;
+  min-height: 1em;
+  padding-left: 10px;
+}
+.FileContents-lineContents:target {
+  background: #cfd8dc;
+}
+.InlineReadme {
+  border-top: 1px solid #ddd;
+  margin: 10px 0;
+  padding: 7px 0;
+}
+.InlineReadme-path {
+  color: #666;
+}
+
+/* BlameDetail.soy */
+
+.Blame {
+  border-collapse: collapse;
+  font-size: 8pt;
+  margin: 0 auto;
+}
+.Blame-region--bg1 {
+  background: #fff;
+}
+.Blame-region--bg2 {
+  background: #f1f2f3;
+}
+.Blame-sha1,
+.Blame-author,
+.Blame-time,
+.Blame-regionLink {
+  font-size: 8pt;
+  padding: 0 3px;
+  white-space: nowrap;
+}
+.Blame-regionLink {
+  text-align: right;
+}
+.Blame-lineNum .u-lineNum {
+  padding: 0 8px;
+  text-align: right;
+}
+.Blame-lineNum:hover {
+  text-decoration: underline;
+}
+.Blame-lineContent {
+  font-size: 9pt;
+  line-height: 1.3em;
+  padding: 0 8px;
+}
+
+/* DiffDetail.soy */
+
+.Diff {
+  margin: 10px 0;
+}
+.Diff-fileIndex {
+  color: #444;
+  font-weight: bold;
+}
+.Diff-unified {
+  border-bottom: 1px solid #ddd;
+  border-top: 1px solid #ddd;
+  padding: 10px 0;
+}
+.Diff-hunk {
+  color: #00c;
+}
+.Diff-delete {
+  color: #c00;
+}
+.Diff-insert {
+  color: #080;
+}
diff --git a/resources/com/google/gitiles/static/doc.css b/resources/com/google/gitiles/static/doc.css
new file mode 100644
index 0000000..a531087
--- /dev/null
+++ b/resources/com/google/gitiles/static/doc.css
@@ -0,0 +1,277 @@
+/**
+ * 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.
+ */
+
+/* Markdown rendered in /+doc/ or tree view page . */
+
+.Site-Content--markdown {
+  padding-top: 0;
+}
+.Header-nav ul {
+  max-width: 980px;
+  margin: 0 auto;
+}
+.Header-nav li {
+  display: inline-block;
+  margin-right: 15px;
+}
+.doc h1, .doc h2, .doc h3, .doc h4, .doc h5, .doc h6 {
+  font-weight: normal;
+  margin: 1.236em 0 .618em;
+}
+.doc.RepoIndexDoc h1 {
+  margin-top: .25em;
+}
+.doc h1 {
+  font-size: 2em;
+}
+.doc h2 {
+  font-size: 1.5em;
+}
+.doc h3 {
+  font-size: 1.3em;
+}
+.doc h4 {
+  font-size: 1.3em;
+  font-weight: lighter;
+  font-style: italic;
+}
+.doc h5 {
+  font-size: 16px;
+  font-weight: lighter;
+  font-style: italic;
+}
+.doc h6 {
+  font-size: 14px;
+  font-style: italic;
+}
+.doc hr {
+  border: none;
+  border-top: 1px solid #aaa;
+  display: block;
+  margin-top: 25px;
+}
+.doc a {
+  text-decoration: none;
+}
+.doc a:link {
+  color: #245dc1;
+}
+.doc a:visited {
+  color: #7759ae;
+}
+.doc a:hover {
+  text-decoration: underline;
+}
+.doc a.h {
+  display: inline-block;
+  font-weight: normal;
+  width: 1.5em;
+  margin-left: -1.5em;
+  margin-top: -1em;
+  margin-bottom: -1em;
+}
+.doc a.h:link,
+.doc a.h:visited {
+  color: #444;
+}
+.doc a.h:focus {
+  outline: none;
+}
+.doc a.h:hover {
+  text-decoration: none;
+}
+.doc a.h span {
+  display: inline-block;
+  width: 1.5em;
+}
+.doc h1:hover a.h span:before,
+.doc h2:hover a.h span:before,
+.doc h3:hover a.h span:before,
+.doc h4:hover a.h span:before,
+.doc h5:hover a.h span:before,
+.doc h6:hover a.h span:before {
+  content: '#';
+  font-weight: normal;
+  color: #AAA;
+}
+.doc h1:hover a:hover.h span:before,
+.doc h2:hover a:hover.h span:before,
+.doc h3:hover a:hover.h span:before,
+.doc h4:hover a:hover.h span:before,
+.doc h5:hover a:hover.h span:before,
+.doc h6:hover a:hover.h span:before {
+  text-decoration: underline;
+}
+.doc h1:hover a:visited.h span:before,
+.doc h2:hover a:visited.h span:before,
+.doc h3:hover a:visited.h span:before,
+.doc h4:hover a:visited.h span:before,
+.doc h5:hover a:visited.h span:before,
+.doc h6:hover a:visited.h span:before {
+  color: #AAA;
+}
+.doc ul, .doc ol {
+  margin: 10px 10px 10px 30px;
+  padding: 0;
+}
+.doc ul {
+  list-style-type: disc;
+}
+.doc ol {
+  list-style-type: decimal;
+}
+.doc img {
+  border: 0;
+  max-width: 100%;
+}
+.doc iframe {
+  min-width: 100px;
+  min-height: 30px;
+}
+iframe.noborder {
+  border: 0;
+}
+.doc em {
+  font-weight: normal;
+  font-style: italic;
+}
+.doc strong {
+  font-weight: bold;
+  color: inherit;
+}
+.doc p {
+  margin: 10px 0;
+}
+.doc blockquote {
+  background-color: #fffde7;
+  border-left: 2px solid #fab700;
+  padding: 2px 10px 2px 10px;
+}
+.doc pre {
+  padding: 12px;
+  font-size: 10pt;
+  background-color: #fafafa;
+  border: 1px solid #ccc;
+  overflow-x: auto;
+}
+.doc code {
+  padding: 2px 4px;
+  background-color: #F5F5F5;
+  border: transparent;
+  border-radius: 4px;
+}
+.doc .code {
+  font-family: 'Source Code Pro', monospace;
+}
+.doc dl dt {
+  margin-top: 1em;
+}
+.doc table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+.doc th {
+  text-align: center;
+}
+.doc th,
+.doc td {
+  border: 1px solid #eee;
+  padding: 4px 12px;
+  vertical-align: top;
+}
+.doc th {
+  background-color: #f5f5f5;
+}
+.toc {
+  margin-top: 30px;
+}
+.toc-aux {
+  background: #f9f9f9;
+  border: 1px solid #f2f2f2;
+}
+.toc h2 {
+  margin: 0 0 5px 0;
+}
+.toc ul {
+  margin: 10px 10px 10px 30px;
+}
+.toc ul li {
+  margin-left: 0px;
+  list-style: disc;
+}
+.toc ul ul {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+.toc ul ul li {
+  list-style: circle;
+}
+.note,
+.promo,
+.aside {
+  border: 1px solid;
+  border-radius: 4px;
+  margin: 10px 0;
+  padding: 10px;
+}
+.note {
+  background: #fffbe4;
+  border-color: #f8f6e6;
+}
+.promo {
+  background: #f6f9ff;
+  border-color: #eff2f9;
+}
+.aside {
+  background: #f9f9f9;
+  border-color: #f2f2f2;
+}
+.note :first-child,
+.promo :first-child,
+.aside :first-child {
+  margin-top: 0;
+}
+.note p:last-child,
+.promo p:last-child,
+.aside p:last-child {
+  margin-bottom: 0;
+}
+.cols {
+  margin: 0 -1.533%;
+  width: 103.067%;
+}
+.col-1, .col-2, .col-3, .col-4, .col-5, .col-6,
+.col-7, .col-8, .col-9, .col-10, .col-11, .col-12 {
+  display: inline-block;
+  margin: 0 1.488% 20px;
+  vertical-align: top;
+}
+.cols h1, .cols h2, .cols h3, .cols h4, .cols h5, .cols h6 {
+  margin: .67em 0;
+}
+.col-1 { width: 5.357%; }
+.col-2 { width: 13.690%; }
+.col-3 { width: 22.024%; }
+.col-4 { width: 30.357%; }
+.col-5 { width: 38.690%; }
+.col-6 { width: 47.024%; }
+.col-7 { width: 55.357%; }
+.col-8 { width: 63.690%; }
+.col-9 { width: 72.024%; }
+.col-10 { width: 80.357%; }
+.col-11 { width: 88.690%; }
+.col-12 { width: 97.024%; }
+.cols hr { width: 80%; }
diff --git a/resources/com/google/gitiles/static/prettify/prettify.css b/resources/com/google/gitiles/static/prettify/prettify.css
new file mode 100644
index 0000000..d44b3a2
--- /dev/null
+++ b/resources/com/google/gitiles/static/prettify/prettify.css
@@ -0,0 +1 @@
+.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
\ No newline at end of file
diff --git a/resources/com/google/gitiles/templates/BlameDetail.soy b/resources/com/google/gitiles/templates/BlameDetail.soy
new file mode 100644
index 0000000..909ee0f
--- /dev/null
+++ b/resources/com/google/gitiles/templates/BlameDetail.soy
@@ -0,0 +1,88 @@
+// Copyright 2014 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Detail page showing blame info for a file.
+ *
+ * @param title human-readable revision name.
+ * @param repositoryName name of this repository.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for custom styling.
+ * @param breadcrumbs breadcrumbs for this page.
+ * @param data blob data, matching the params for .blobBox.
+ * @param? regions for non-binary files, list of regions, one per line, with the
+ *     following keys:
+ *       abbrevSha: abbreviated SHA-1 of revision for this line; if missing,
+ *           assume blame info is missing.
+ *       author: author information with at least "name" and "relativeTime"
+ *           keys.
+ *       time: time of the revision.
+ *       blameUrl: URL for a blame of this file at this commit.
+ *       commitUrl: URL for detail about the commit.
+ *       diffUrl: URL for a diff of this file at this commit.
+ *       class: class name for tr.
+ *     All keys but "class" are optional.
+ */
+{template .blameDetail}
+{if $regions}
+  {call .header data="all"}
+    {param css: [gitiles.PRETTIFY_CSS_URL] /}
+    {param containerClass: 'Container--fullWidth' /}
+  {/call}
+
+  {call .blobHeader data="$data" /}
+
+  <table class="Blame">
+    {foreach $line in $data.lines}
+      {let $i: index($line) /}
+      {let $region: $regions[$i] /}
+      <tr class="Blame-region {$region.class}">
+        {if isNonnull($region.abbrevSha)}
+          <td class="Blame-author">{$region.author.name}</td>
+          <td class="Blame-sha1"><a class="u-sha1 u-monospace Blame-sha1" href="{$region.commitUrl}">{$region.abbrevSha}</a></td>
+          <td class="Blame-time">{$region.author.time}</span>
+          <td class="Blame-regionLink">
+            [<a href="{$region.diffUrl}">{msg desc="text for diff URL"}diff{/msg}</a>]
+            [<a href="{$region.blameUrl}">{msg desc="text for blame URL"}{$region.blameText}{/msg}</a>]
+          </td>
+        {else}
+          <td colspan="4"></td>
+        {/if}
+        {let $n: $i + 1 /}
+        <td class="Blame-lineNum">
+          <a class="u-monospace u-lineNum" href="#{$n}" name="{$n}">{$n}</a>
+        </td>
+        <td class="u-pre u-monospace Blame-lineContent">
+          {foreach $span in $line}
+            <span class="{$span.classes}">{$span.text}</span>
+          {/foreach}
+        </td>
+      </tr>
+    {/foreach}
+  </table>
+{else}
+  {call .header data="all" /}
+  {call .blobDetail data="$data" /}
+  <div class="FileContents-binary">
+    {msg desc="blame not available for binary file"}
+      No blame information available
+    {/msg}
+  </div>
+{/if}
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
diff --git a/resources/com/google/gitiles/templates/Common.soy b/resources/com/google/gitiles/templates/Common.soy
new file mode 100644
index 0000000..ca3e21f
--- /dev/null
+++ b/resources/com/google/gitiles/templates/Common.soy
@@ -0,0 +1,169 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Common header for Gitiles.
+ *
+ * @param title title for this page. Always suffixed with repository name and a
+ *     sitewide title.
+ * @param? repositoryName repository name for this page, if applicable.
+ * @param? menuEntries optional list of menu entries with "text" and optional
+ *     "url" keys.
+ * @param? customVariant variant name for custom styling.
+ * @param breadcrumbs navigation breadcrumbs for this page.
+ * @param? css optional list of CSS URLs to include.
+ * @param? containerClass optional class to append to the main container.
+ */
+{template .header}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>
+    {$title}
+    {if $repositoryName}
+      {sp}- {$repositoryName}
+    {/if}
+    {sp}- {msg desc="name of the application"}{gitiles.SITE_TITLE}{/msg}
+  </title>
+
+  <link rel="stylesheet" type="text/css" href="{gitiles.BASE_CSS_URL}">
+  {if $css and length($css)}
+    {foreach $url in $css}
+      <link rel="stylesheet" type="text/css" href="{$url}">
+    {/foreach}
+  {/if}
+  {delcall gitiles.customHeadTagPart variant="$customVariant ?: ''" /}
+
+</head>
+<body class="Site">
+  <header class="Site-header">
+    <div class="Header">
+      {delcall gitiles.customHeader variant="$customVariant ?: ''" /}
+
+      {if $menuEntries and length($menuEntries)}
+        <div class="Header-menu">
+        {foreach $entry in $menuEntries}
+          {sp}
+          {if $entry.url}
+            <a class="Header-menuItem" href="{$entry.url}">{$entry.text}</a>
+          {else}
+            <span class="Header-menuItem Header-menuItem--noAction">{$entry.text}</span>
+          {/if}
+        {/foreach}
+        {sp}
+        </div>
+      {/if}
+    </div>
+  </header>
+
+  <div class="Site-content">
+    <div class="Container {if $containerClass}{$containerClass}{/if}">
+      {if $breadcrumbs and length($breadcrumbs)}
+        <div class="Breadcrumbs">
+          {foreach $entry in $breadcrumbs}
+            {if not isFirst($entry)}{sp}/{sp}{/if}
+            {if not isLast($entry)}
+              <a class="Breadcrumbs-crumb" href="{$entry.url}">{$entry.text}</a>
+            {else}
+              <span class="Breadcrumbs-crumb">{$entry.text}</span>
+            {/if}
+          {/foreach}
+        </div>
+      {/if}
+{/template}
+
+/**
+ * Default (empty) custom head tag part
+ *
+ * This can be used to include per-project CSS/JS by
+ * providing custom variants.
+ */
+{deltemplate gitiles.customHeadTagPart}
+  <!-- default customHeadTagPart -->
+{/deltemplate}
+
+/**
+ * Default custom header implementation for Gitiles.
+ */
+{deltemplate gitiles.customHeader}
+<!-- default customHeader -->
+<div class="Header-title">
+  {msg desc="short name of the application"}{gitiles.SITE_TITLE}{/msg}
+</div>
+{/deltemplate}
+
+/**
+ * Footer 'powered by' element
+ *
+ * Please call this in custom variants as well.
+ */
+{template .footerPoweredBy}
+<span class="Footer-poweredBy">
+  Powered by <a href="https://gerrit.googlesource.com/gitiles/">Gitiles</a>
+</span>
+{/template}
+
+/**
+ * Footer format badge
+ *
+ * You can use this in custom footers as well.
+ */
+{template .footerFormatBadge}
+<span class="Footer-formats">
+  <a class="u-monospace Footer-formatsItem" href="?format=TEXT">{msg desc="text format"}txt{/msg}</a>
+    {sp}
+  <a class="u-monospace Footer-formatsItem" href="?format=JSON">{msg desc="JSON format"}json{/msg}</a>
+</span>
+{/template}
+
+/**
+ * Default Footer
+ */
+{deltemplate gitiles.customFooter}
+<!-- default customFooter -->
+<footer class="Site-footer">
+  <div class="Footer">
+   {call gitiles.footerPoweredBy /}
+   {call gitiles.footerFormatBadge /}
+  </div>
+</footer>
+{/deltemplate}
+
+/**
+ * Main footer.
+ *
+ * The footer tag part can be customized by creating a customFooter
+ * variant template.
+ *
+ * @param? customVariant variant name for custom styling.
+ */
+{template .footer}
+    </div> <!-- Container -->
+  </div> <!-- Site-content -->
+  {delcall gitiles.customFooter variant="$customVariant ?: ''" /}
+</body>
+</html>
+{/template}
+
+/**
+ * Placeholder for streaming rendering.
+ *
+ * Insert this in a template to use with
+ * Renderer#renderStreaming(HttpServletResponse, String).
+ */
+{template .streamingPlaceholder}
+<div id="STREAMED_OUTPUT_BLOCK" />
+{/template}
diff --git a/resources/com/google/gitiles/templates/DiffDetail.soy b/resources/com/google/gitiles/templates/DiffDetail.soy
new file mode 100644
index 0000000..252ab7b
--- /dev/null
+++ b/resources/com/google/gitiles/templates/DiffDetail.soy
@@ -0,0 +1,61 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Detail page showing diffs for a single commit.
+ *
+ * @param title human-readable revision name.
+ * @param repositoryName name of this repository.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for custom styling.
+ * @param breadcrumbs breadcrumbs for this page.
+ * @param? commit optional commit for which diffs are displayed, with keys
+ *     corresponding to the gitiles.commitDetail template (minus "diffTree").
+ */
+{template .diffDetail}
+{call .header data="all" /}
+
+{if $commit}
+  {call .commitDetail data="$commit" /}
+{/if}
+{call .streamingPlaceholder /}
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
+
+/**
+ * File header for a single unified diff patch.
+ *
+ * @param firstParts parts of the first line of the header, with "text" and
+ *     optional "url" fields.
+ * @param rest remaining lines of the header, if any.
+ * @param fileIndex position of the file within the difference.
+ */
+{template .diffHeader}
+<pre class="u-pre u-monospace Diff">
+  <a name="F{$fileIndex}" class="Diff-fileIndex"></a>
+  {foreach $part in $firstParts}
+    {if not isFirst($part)}{sp}{/if}
+    {if $part.url}
+      <a href="{$part.url}">{$part.text}</a>
+    {else}
+      {$part.text}
+    {/if}
+  {/foreach}{\n}
+  {$rest}
+</pre>
+{/template}
diff --git a/resources/com/google/gitiles/templates/Doc.soy b/resources/com/google/gitiles/templates/Doc.soy
new file mode 100644
index 0000000..426ba15
--- /dev/null
+++ b/resources/com/google/gitiles/templates/Doc.soy
@@ -0,0 +1,90 @@
+// 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Documentation page rendered from markdown.
+ *
+ * @param? siteTitle h1 title from navbar.md.
+ * @param pageTitle h1 title from specific page.
+ * @param? logoUrl url of image logo.
+ * @param? homeUrl url to jump to top of site.
+ * @param? analyticsId Google Analytics Property ID.
+ * @param? sourceUrl url for source view of the page.
+ * @param? logUrl url for log history of page.
+ * @param? blameUrl url for blame of page source.
+ * @param? navbarHtml navar.md converted to SafeHtml.
+ * @param? customVariant variant name for custom styling.
+ */
+{template .markdownDoc}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <title>
+    {if $siteTitle}{$siteTitle} -{sp}{/if}
+    {$pageTitle}
+  </title>
+  <link rel="stylesheet" type="text/css" href="{gitiles.BASE_CSS_URL}" />
+  <link rel="stylesheet" type="text/css" href="{gitiles.DOC_CSS_URL}" />
+  <link rel="stylesheet" type="text/css" href="{gitiles.PRETTIFY_CSS_URL}" />
+  {delcall gitiles.customHeadTagPart variant="$customVariant ?: ''" /}
+</head>
+<body class="Site">
+  <header class="Site-header {if $navbarHtml}Site-header--withNavbar{/if}">
+    <div class="Header">
+      <div class="Header-title">
+        {if $homeUrl}<a class="Header-anchor" href="{$homeUrl}">{/if}
+        {if $logoUrl}<img class="Header-anchorLogo" src="{$logoUrl}" alt="project logo" />{/if}
+        {if $siteTitle}<span class="Header-anchorTitle">{$siteTitle}</span>{/if}
+        {if $homeUrl}</a>{/if}
+      </div>
+    </div>
+    {if $navbarHtml}
+      <nav class="Header-nav" role="navigation">
+        {$navbarHtml}
+      </div>
+    {/if}
+  </header>
+  <div class="Site-content Site-Content--markdown">
+    <div class="Container">
+      <div class="doc">
+        {call .streamingPlaceholder /}
+      </div>
+    </div>
+  </div>
+  <footer class="Site-footer">
+    <div class="Footer">
+      {call gitiles.footerPoweredBy /}
+      <div class="Footer-links">
+        {if $sourceUrl}<a class="Footer-link" href="{$sourceUrl}">{msg desc="text for the source link"}source{/msg}</a>{/if}
+        {if $logUrl}<a class="Footer-link" href="{$logUrl}">{msg desc="text for the log link"}log{/msg}</a>{/if}
+        {if $blameUrl}<a class="Footer-link" href="{$blameUrl}">{msg desc="text for the blame link"}blame{/msg}</a>{/if}
+    </div>
+  </footer>
+  {if $analyticsId}
+    /* From https://developers.google.com/analytics/devguides/collection/analyticsjs/ */
+    <script>
+    (function(i,s,o,g,r,a,m){lb}i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){lb}
+    (i[r].q=i[r].q||[]).push(arguments){rb},i[r].l=1*new Date();a=s.createElement(o),
+    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+    {rb})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+    ga('create', '{$analyticsId}', 'auto');
+    ga('send', 'pageview', {lb}title: '{$pageTitle}'{rb});
+    </script>
+  {/if}
+</body>
+</html>
+{/template}
diff --git a/resources/com/google/gitiles/templates/HostIndex.soy b/resources/com/google/gitiles/templates/HostIndex.soy
new file mode 100644
index 0000000..ab3b18c
--- /dev/null
+++ b/resources/com/google/gitiles/templates/HostIndex.soy
@@ -0,0 +1,69 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * HTML page for /.
+ *
+ * @param hostName host name.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for custom styling.
+ * @param? prefix prefix path for matching repositories.
+ * @param? breadcrumbs map of breadcrumbs for header.
+ * @param repositories list of repository description maps with name, cloneUrl,
+ *     and optional description values.
+ */
+{template .hostIndex}
+{call .header}
+  {param title: $prefix ? $prefix : $hostName ? $hostName + ' Git repositories' : 'Git repositories' /}
+  {param menuEntries: $menuEntries /}
+  {param breadcrumbs: $breadcrumbs /}
+  {param customVariant: $customVariant /}
+{/call}
+
+{if length($repositories)}
+  {if not $breadcrumbs}
+    <h1>
+      {msg desc="Git repositories available on the host"}
+        Git repositories on {$hostName}
+      {/msg}
+    </h1>
+  {/if}
+
+  <div class="RepoList">
+    <div class="RepoList-item RepoList-item--header">
+      <span class="RepoList-itemName">
+        {msg desc="column header for repository name"}
+          Name
+        {/msg}
+      </span>
+      <span class="RepoList-itemDescription">
+        {msg desc="column header for repository description"}
+          Description
+        {/msg}
+      </span>
+    </div>
+    {foreach $repo in $repositories}
+      <a class="RepoList-item" href="{$repo.url}">
+        <span class="RepoList-itemName">{$repo.name}</span>
+        <span class="RepoList-itemDescription">{$repo.description}</span>
+      </a>
+    {/foreach}
+  </div>
+{/if}
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
diff --git a/resources/com/google/gitiles/templates/LogDetail.soy b/resources/com/google/gitiles/templates/LogDetail.soy
new file mode 100644
index 0000000..be7c9b3
--- /dev/null
+++ b/resources/com/google/gitiles/templates/LogDetail.soy
@@ -0,0 +1,258 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Detail page showing a shortlog for a commit.
+ *
+ * @param title human-readable revision name.
+ * @param repositoryName name of this repository.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for custom styling.
+ * @param breadcrumbs breadcrumbs for this page.
+ * @param? tags optional list of tags encountered when peeling this object, with
+ *     keys corresponding to gitiles.tagDetail.
+ */
+{template .logDetail}
+{call .header data="all" /}
+
+{if $tags}
+  {foreach $tag in $tags}
+    {call gitiles.tagDetail data="$tag" /}
+  {/foreach}
+{/if}
+
+{call .streamingPlaceholder /}
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
+
+
+/**
+ * Header for list of log entries.
+ *
+ * @param? previousUrl URL for the previous page of results.
+ */
+{template .logEntriesHeader}
+{if $previousUrl}
+  <nav class="LogNav">
+    <a class="LogNav-prev" href="{$previousUrl}">{msg desc="text for previous URL"}&laquo; Previous{/msg}</a>
+  </nav>
+{/if}
+
+<ol class="CommitLog">
+{/template}
+
+
+/**
+ * Wrapper for a single log entry with pretty format and variant.
+ *
+ * @param variant variant name for log entry template.
+ * @param entry log entry; see .logEntry.
+ */
+{template .logEntryWrapper}
+<li class="CommitLog-item CommitLog-item--{$variant}">
+  {delcall gitiles.logEntry variant="$variant ?: 'default'" data="$entry" /}
+</li>
+{/template}
+
+
+/**
+ * Footer for the list of log entries.
+ *
+ * @param? nextUrl URL for the next page of results.
+ */
+{template .logEntriesFooter}
+</ol>
+{if $nextUrl}
+  <nav class="LogNav">
+    <a class="LogNav-next" href="{$nextUrl}">{msg desc="text for next URL"}Next &raquo;{/msg}</a>
+  </nav>
+{/if}
+{/template}
+
+
+/**
+ * Single log entry indicating the full log is empty.
+ */
+{template .emptyLog}
+<li class="CommitLog-item CommitLog-item--empty">{msg desc="informational text for when the log is empty"}No commits.{/msg}</p>
+{/template}
+
+
+/**
+ * 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 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.
+ * @param diffTree unused in this variant.
+ * @param rename if this entry was a rename or a copy of the path, an object containg:
+ *     changeType: the change type, "RENAME" or "COPY".
+ *     oldPath: the old path prior to the rename or copy.
+ *     newPath: the new path after the rename or copy.
+ *     score: the similarity score of the rename or copy.
+ */
+{deltemplate gitiles.logEntry variant="'oneline'"}
+<a class="u-sha1 u-monospace CommitLog-sha1" href="{$url}">{$abbrevSha}</a>
+{sp}<a href="{$url}">{$shortMessage}</a>
+{sp}<span class="CommitLog-author" title="{$author.email}">{msg desc="commit author name"}by {$author.name}{/msg}</span>
+{sp}<span class="CommitLog-time" title="{$author.time}">· {$author.relativeTime}</span>
+{if length($branches)}
+  {foreach $branch in $branches}
+    {sp}<a class="CommitLog-branchLabel" href="{$branch.url}">{$branch.name}</a>
+  {/foreach}
+{/if}
+{if length($tags)}
+  {foreach $tag in $tags}
+    {sp}<a class="CommitLog-tagLabel" href="{$tag.url}">{$tag.name}</a>
+  {/foreach}
+{/if}
+
+{if $rename}
+  <span class="CommitLog-rename">
+    [
+    {switch $rename.changeType}
+      {case 'RENAME'}
+        Renamed
+      {case 'COPY'}
+        Copied
+    {/switch}
+    {if $rename.score != 100}
+      {sp}({$rename.score}%)
+    {/if}
+    {sp}from {$rename.oldPath}]
+  </span>
+{/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.
+ * @param diffTree unused in this variant.
+ * @param rename if this entry was a rename or a copy of the path, an object containg:
+ *     changeType: the change type, "RENAME" or "COPY".
+ *     oldPath: the old path prior to the rename or copy.
+ *     newPath: the new path after the rename or copy.
+ *     score: the similarity score of the rename or copy.
+ */
+{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.
+ * @param diffTree unused in this variant.
+ * @param rename if this entry was a rename or a copy of the path, an object containg:
+ *     changeType: the change type, "RENAME" or "COPY".
+ *     oldPath: the old path prior to the rename or copy.
+ *     newPath: the new path after the rename or copy.
+ *     score: the similarity score of the rename or copy.
+ */
+{deltemplate gitiles.logEntry variant="'full'"}
+<div class="u-monospace Metadata">
+<table>
+  <tr>
+    <th class="Metadata-title">{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 class="Metadata-title">{msg desc="Header for commit author"}author{/msg}</th>
+    <td>{call .person_ data="$author" /}</td>
+    <td>{$author.time}</td>
+  </tr>
+  <tr>
+    <th class="Metadata-title">{msg desc="Header for committer"}committer{/msg}</th>
+    <td>{call .person_ data="$committer" /}</td>
+    <td>{$committer.time}</td>
+  </tr>
+
+  {if $rename}
+    <tr>
+      <td colspan="3">
+        <span class="CommitLog-rename">
+          [
+          {switch $rename.changeType}
+            {case 'RENAME'}
+              Renamed
+            {case 'COPY'}
+              Copied
+          {/switch}
+          {if $rename.score != 100}
+            {sp}({$rename.score}%)
+          {/if}
+          {sp}from {$rename.oldPath}]
+        </span>
+      </td>
+    </tr>
+  {/if}
+
+</table>
+</div>
+<pre class="u-pre u-monospace MetadataMessage">
+  {$message}
+</pre>
+{/deltemplate}
diff --git a/resources/com/google/gitiles/templates/ObjectDetail.soy b/resources/com/google/gitiles/templates/ObjectDetail.soy
new file mode 100644
index 0000000..ea0e06e
--- /dev/null
+++ b/resources/com/google/gitiles/templates/ObjectDetail.soy
@@ -0,0 +1,345 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Detailed listing of a commit.
+ *
+ * @param author map with "name", "email", and "time" keys for the commit author.
+ * @param committer map with "name", "email", and "time" keys for the committer.
+ * @param sha commit SHA-1.
+ * @param tree tree SHA-1.
+ * @param treeUrl tree URL.
+ * @param parents list of parent objects with the following keys:
+ *     sha: SHA-1.
+ *     url: URL to view the parent commit.
+ *     diffUrl: URL to display diffs relative to this parent.
+ *     blameUrl: optional URL to display blame of a file at this parent.
+ * @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 diffTree list of changed tree entries with the following keys:
+ *     changeType: string matching an org.eclipse.jgit.diff.DiffEntry.ChangeType
+ *         constant.
+ *     path: (new) path of the tree entry.
+ *     oldPath: old path, only for renames and copies.
+ *     url: URL to a detail page for the tree entry.
+ *     diffUrl: URL to a diff page for the tree entry's diff in this commit.
+ * @param logUrl URL to a log page starting at this commit.
+ * @param archiveUrl URL to a download link of this commit as an archive.
+ * @param archiveType type of the archive to download.
+ */
+{template .commitDetail}
+<div class="u-monospace Metadata">
+  <table>
+    <tr>
+      <th class="Metadata-title">{msg desc="Header for commit SHA entry"}commit{/msg}</th>
+      <td>
+        {$sha}
+      </td>
+      <td>
+        <span>
+          [<a href="{$logUrl}">{msg desc="text for the log link"}log{/msg}</a>]
+        </span>
+        {sp}<span>[<a href="{$archiveUrl}">{$archiveType}</a>]</span>
+      </td>
+    </tr>
+    <tr>
+      <th class="Metadata-title">{msg desc="Header for commit author"}author{/msg}</th>
+      <td>{call .person_ data="$author" /}</td>
+      <td>{$author.time}</td>
+    </tr>
+    <tr>
+      <th class="Metadata-title">{msg desc="Header for committer"}committer{/msg}</th>
+      <td>{call .person_ data="$committer" /}</td>
+      <td>{$committer.time}</td>
+    </tr>
+    <tr>
+      <th class="Metadata-title">{msg desc="Header for tree SHA entry"}tree{/msg}</th>
+      <td><a href="{$treeUrl}">{$tree}</a></td>
+    </tr>
+    {foreach $parent in $parents}
+      <tr>
+        <th class="Metadata-title">{msg desc="Header for parent SHA"}parent{/msg}</th>
+        <td>
+          <a href="{$parent.url}">{$parent.sha}</a>
+          {sp}<span>
+            [<a href="{$parent.diffUrl}">{msg desc="text for the parent diff link"}diff{/msg}</a>]
+            {if isNonnull($parent.blameUrl)}
+              {sp}[<a href="{$parent.blameUrl}">{msg desc="text for the parent blame link"}blame{/msg}</a>]
+            {/if}
+          </span>
+        </td>
+      </tr>
+    {/foreach}
+  </table>
+</div>
+{call .message_}
+  {param className: 'u-pre u-monospace MetadataMessage' /}
+  {param message: $message /}
+{/call}
+
+{if $diffTree and length($diffTree)}
+  <ul class="DiffTree">
+    {foreach $entry in $diffTree}
+      <li>
+        <a href="{$entry.url}">{$entry.path}</a>
+        {switch $entry.changeType}
+          {case 'ADD'}
+            <span class="DiffTree-action DiffTree-action--add">
+              {msg desc="Text for a new tree entry"}
+                [Added - <a href="{$entry.diffUrl}">diff</a>]
+              {/msg}
+            </span>
+          {case 'MODIFY'}
+            <span class="DiffTree-action DiffTree-action--modify">
+              {msg desc="Text for a modified tree entry"}
+                [<a href="{$entry.diffUrl}">diff</a>]
+              {/msg}
+            </span>
+          {case 'DELETE'}
+            <span class="DiffTree-action DiffTree-action--delete">
+              {msg desc="Text for a deleted tree entry"}
+                [Deleted - <a href="{$entry.diffUrl}">diff</a>]
+              {/msg}
+            </span>
+          {case 'RENAME'}
+            <span class="DiffTree-action DiffTree-action--rename">
+              {msg desc="Text for a renamed tree entry"}
+                [Renamed from {$entry.oldPath} - <a href="{$entry.diffUrl}">diff</a>]
+              {/msg}
+            </span>
+          {case 'COPY'}
+            <span class="DiffTree-action DiffTree-action--copy">
+              {msg desc="Text for a copied tree entry"}
+                [Copied from {$entry.oldPath} - <a href="{$entry.diffUrl}">diff</a>]
+              {/msg}
+            </span>
+          {default}
+        {/switch}
+      </li>
+    {/foreach}
+  </ul>
+  <div class="DiffSummary">
+    {if length($diffTree) == 1}
+      {msg desc="1 file changed"}1 file changed{/msg}
+    {else}
+      {msg desc="number of files changed"}{length($diffTree)} files changed{/msg}
+    {/if}
+  </div>
+{/if}
+
+{/template}
+
+/**
+ * Detailed listing of a tree.
+ *
+ * @param sha SHA of this path's tree.
+ * @param? logUrl optional URL to a log for this path.
+ * @param? archiveUrl optional URL to a download link of this tree as an archive.
+ * @param? archiveType type of the archive to download, if archiveUrl is set.
+ * @param entries list of entries with the following keys:
+ *     type: entry type, matching one of the constant names defined in
+ *         org.eclipse.jgit.lib.FileMode.
+ *     name: tree entry name.
+ *     url: URL to link to.
+ *     targetName: name of a symlink target, required only if type == 'SYMLINK'.
+ *     targetUrl: optional url of a symlink target, required only if
+ *         type == 'SYMLINK'.
+ * @param? readmePath optional path of the selected README.md file.
+ * @param? readmeHtml optional rendered README.md contents.
+ */
+{template .treeDetail}
+<div class="TreeDetail">
+  <div class="u-sha1 u-monospace TreeDetail-sha1">
+    {msg desc="SHA-1 for the path's tree"}tree: {$sha}{/msg}
+    {if $logUrl}{sp}[<a href="{$logUrl}">{msg desc="history for a path"}path history{/msg}</a>]{/if}
+    {if $archiveUrl}
+      {sp}<span>[<a href="{$archiveUrl}">{$archiveType}</a>]</span>
+    {/if}
+  </div>
+
+  {if length($entries)}
+    <ol class="FileList">
+      {foreach $entry in $entries}
+        <li class="FileList-item{sp}
+              {switch $entry.type}
+                {case 'TREE'}FileList-item--gitTree
+                {case 'SYMLINK'}FileList-item--symlink
+                {case 'REGULAR_FILE'}FileList-item--regularFile
+                {case 'EXECUTABLE_FILE'}FileList-item--executableFile
+                {case 'GITLINK'}gitlink
+                {default}regular-file
+              {/switch}
+              " title="
+              {switch $entry.type}
+                {case 'TREE'}{msg desc="Alt text for tree icon"}Tree{/msg}
+                {case 'SYMLINK'}{msg desc="Alt text for symlink icon"}Symlink{/msg}
+                {case 'REGULAR_FILE'}{msg desc="Alt text for regular file icon"}Regular file{/msg}
+                {case 'EXECUTABLE_FILE'}
+                  {msg desc="Alt text for executable file icon"}Executable file{/msg}
+                {case 'GITLINK'}
+                  {msg desc="Alt text for git submodule link icon"}Git submodule link{/msg}
+                {default}{msg desc="Alt text for other file icon"}Other{/msg}
+              {/switch}
+              {sp}- {$entry.name}">
+          <a class="FileList-itemLink" href="{$entry.url}">{$entry.name}</a>
+          {if $entry.type == 'SYMLINK'}
+            {sp}&#x21e8;{sp}
+            {if $entry.targetUrl}
+              <a href="{$entry.targetUrl}">{$entry.targetName}</a>
+            {else}
+              {$entry.targetName}
+            {/if}
+          {/if}
+          // TODO(dborowitz): Something reasonable for gitlinks.
+        </li>
+      {/foreach}
+    </ol>
+  {else}
+    <p>{msg desc="Informational text for when a tree is empty"}This tree is empty.{/msg}</p>
+  {/if}
+
+  {if $readmeHtml}
+    <div class="InlineReadme">
+      <div class="InlineReadme-path">{$readmePath}</div>
+      <div class="doc">{$readmeHtml}</div>
+    </div>
+  {/if}
+</div>
+{/template}
+
+/**
+ * Common header for a blob shared between detail, blame, etc. views.
+ *
+ * @param sha SHA of this file's blob.
+ * @param? fileUrl optional URL to a detail view of this file.
+ * @param? logUrl optional URL to a log for this file.
+ * @param? blameUrl optional URL to a blame for this file.
+ * @param? docUrl optional URL to view rendered file.
+ */
+{template .blobHeader}
+<div class="u-sha1 u-monospace BlobSha1">
+  {msg desc="SHA-1 for the file's blob"}blob: {$sha}{/msg}
+  {if $fileUrl}{sp}[<a href="{$fileUrl}">{msg desc="detail view of a file"}file{/msg}</a>]{/if}
+  {if $logUrl}{sp}[<a href="{$logUrl}">{msg desc="history for a file"}log{/msg}</a>]{/if}
+  {if $blameUrl}{sp}[<a href="{$blameUrl}">{msg desc="blame for a file"}blame{/msg}</a>]{/if}
+  {if $docUrl}{sp}[<a href="{$docUrl}">{msg desc="view rendered file"}view{/msg}</a>]{/if}
+</div>
+{/template}
+
+/**
+ * Detailed listing of a blob.
+ *
+ * @param sha SHA of this file's blob.
+ * @param? logUrl optional URL to a log for this file.
+ * @param? blameUrl optional URL to a blame for this file.
+ * @param lines lines (may be empty), or null for a binary file. Each line
+ *     is a list of entries with "classes" and "text" fields for pretty-printed
+ *     spans.
+ * @param? size for binary files only, size in bytes.
+ */
+{template .blobDetail}
+  {call .blobHeader data="all" /}
+
+  {if $lines != null}
+    {if $lines}
+      <table class="FileContents">
+        {foreach $line in $lines}
+          {let $n: index($line) + 1 /}
+          <tr class="u-pre u-monospace FileContents-line">
+            <td class="u-lineNum u-noSelect FileContents-lineNum"
+                data-line-number="{$n}" onclick="window.location.hash='#{$n}'"></td>
+            <td class="FileContents-lineContents" id="{$n}">
+              {foreach $span in $line}
+                <span class="{$span.classes}">{$span.text}</span>
+              {/foreach}
+            </td>
+          </tr>
+        {/foreach}
+      </table>
+    {else}
+      <div class="FileContents-empty">Empty file</div>
+    {/if}
+  {else}
+    <div class="FileContents-binary">
+      {msg desc="size of binary file in bytes"}{$size}-byte binary file{/msg}
+    </div>
+  {/if}
+{/template}
+
+/**
+ * Detailed listing of an annotated tag.
+ *
+ * @param sha SHA of this tag.
+ * @param? tagger optional map with "name", "email", and "time" keys for the
+ *     tagger.
+ * @param object SHA of the object this tag points to.
+ * @param message tag message.
+ */
+{template .tagDetail}
+<div class="u-monospace Metadata">
+  <table>
+    <tr>
+      <th class="Metadata-title">{msg desc="Header for tag SHA entry"}tag{/msg}</th>
+      <td class="sha">{$sha}</td>
+      <td>{sp}</td>
+    </tr>
+    {if $tagger}
+      <tr>
+        <th class="Metadata-title">{msg desc="Header for tagger"}tagger{/msg}</th>
+        <td>{call .person_ data="$tagger" /}</td>
+        <td>{$tagger.time}</td>
+      </tr>
+    {/if}
+    <tr>
+      <th class="Metadata-title">{msg desc="Header for tagged object SHA"}object{/msg}</th>
+      <td class="sha">{$object}</td>
+      <td>{sp}</td>
+    </tr>
+  </table>
+</div>
+{if $message and length($message)}
+  {call .message_}
+    {param className: 'u-pre u-monospace MetadataMessage' /}
+    {param message: $message /}
+  {/call}
+{/if}
+{/template}
+
+/**
+ * Line about a git person identity.
+ *
+ * @param name name.
+ * @param email email.
+ */
+{template .person_}
+{$name}{if $email} &lt;{$email}&gt;{/if}
+{/template}
+
+/**
+ * Preformatted message, possibly containing hyperlinks.
+ *
+ * @param className CSS class name for <pre> block.
+ * @param message list of message parts, where each part contains:
+ *     text: raw text of the part.
+ *     url: optional URL that should be linked to from the part.
+ */
+{template .message_ visibility="private"}
+<pre class="{$className}">
+  {foreach $part in $message}
+    {if $part.url}<a href="{$part.url}">{$part.text}</a>{else}{$part.text}{/if}
+  {/foreach}
+</pre>
+{/template}
diff --git a/resources/com/google/gitiles/templates/PathDetail.soy b/resources/com/google/gitiles/templates/PathDetail.soy
new file mode 100644
index 0000000..c4c5d5f
--- /dev/null
+++ b/resources/com/google/gitiles/templates/PathDetail.soy
@@ -0,0 +1,89 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Detail page for a path within a tree.
+ *
+ * @param title human-readable name of this path.
+ * @param repositoryName name of this repository.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for custom styling.
+ * @param breadcrumbs breadcrumbs for this page.
+ * @param type path type, matching one of the constant names defined in
+ *         org.eclipse.jgit.lib.FileMode.
+ * @param data path data, matching the params for one of .treeDetail,
+ *     .blobDetail, .symlinkDetail, or .gitlinkDetail as appropriate.
+ */
+{template .pathDetail}
+{if $type == 'REGULAR_FILE' or $type == 'EXECUTABLE_FILE'}
+  {call .header data="all"}
+    {param css: [gitiles.PRETTIFY_CSS_URL] /}
+  {/call}
+{elseif $data.readmeHtml}
+  {call .header data="all"}
+    {param css: [gitiles.DOC_CSS_URL, gitiles.PRETTIFY_CSS_URL] /}
+  {/call}
+{else}
+  {call .header data="all" /}
+{/if}
+
+{switch $type}
+  {case 'TREE'}{call .treeDetail data="$data" /}
+  {case 'SYMLINK'}{call .symlinkDetail data="$data" /}
+  {case 'REGULAR_FILE'}{call .blobDetail data="$data" /}
+  {case 'EXECUTABLE_FILE'}{call .blobDetail data="$data" /}
+  {case 'GITLINK'}{call .gitlinkDetail data="$data" /}
+  {default}
+    <div class="error">
+      {msg desc="Error message for an unknown object type"}Object has unknown type.{/msg}
+    </div>
+{/switch}
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
+
+/**
+ * Detail for a symbolic link.
+ *
+ * @param target target of this symlink.
+ * @param? targetUrl optional URL for the target, if it is within this repo.
+ */
+{template .symlinkDetail}
+<div class="symlink-detail">
+  {msg desc="Lead-in text for the symbolic link target."}Symbolic link to{/msg}
+  {sp}{if $targetUrl}<a href="{$targetUrl}">{$target}</a>{else}{$target}{/if}
+</div>
+{/template}
+
+/**
+ * Detail for a git submodule link.
+ *
+ * @param sha submodule commit SHA.
+ * @param remoteUrl URL of the remote repository.
+ * @param? httpUrl optional HTTP URL pointing to a web-browser-compatible URL of
+ *     the remote repository.
+ */
+{template .gitlinkDetail}
+<div class="gitlink-detail">
+  {msg desc="Lead-in text for the git link URL"}Submodule link to {$sha} of{/msg}
+  {if $remoteUrl}
+    {sp}{if $httpUrl}<a href="{$httpUrl}">{$remoteUrl}</a>{else}{$remoteUrl}{/if}
+  {else}
+    {sp}{msg desc="Text describing an unknown submodule URL"}a submodule with an unknown URL{/msg}
+  {/if}
+</div>
+{/template}
diff --git a/resources/com/google/gitiles/templates/RefList.soy b/resources/com/google/gitiles/templates/RefList.soy
new file mode 100644
index 0000000..be9c94a
--- /dev/null
+++ b/resources/com/google/gitiles/templates/RefList.soy
@@ -0,0 +1,72 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+
+/**
+ * List of all refs in a repository.
+ *
+ * @param repositoryName name of this repository.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for custom styling.
+ * @param breadcrumbs breadcrumbs for this page.
+ * @param branches list of branch objects with url, name, and isHead keys.
+ * @param tags list of tag objects with url and name keys.
+ */
+{template .refsDetail}
+{call .header}
+  {param title: 'Refs' /}
+  {param repositoryName: $repositoryName /}
+  {param menuEntries: $menuEntries /}
+  {param customVariant: $customVariant /}
+  {param breadcrumbs: $breadcrumbs /}
+{/call}
+
+<div class="Refs">
+  {if length($branches)}
+    {call .refList}
+      {param type: 'Branches' /}
+      {param refs: $branches /}
+    {/call}
+  {/if}
+
+  {if length($tags)}
+    {call .refList}
+      {param type: 'Tags' /}
+      {param refs: $tags /}
+    {/call}
+  {/if}
+</div>
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
+
+/**
+ * List of a single type of refs
+ *
+ * @param type name of this type of refs, e.g. "Branches"
+ * @param refs list of branch objects with url, name, and optional isHead keys.
+ */
+{template .refList}
+  <div class="RefList">
+    <h3 class="RefList-title">{$type}</h3>
+    <ul class="RefList-items">
+    {foreach $ref in $refs}
+      <li class="RefList-item"><a href="{$ref.url}">{$ref.name}</a></li>
+    {/foreach}
+    </ul>
+  </div>
+{/template}
diff --git a/resources/com/google/gitiles/templates/RepositoryIndex.soy b/resources/com/google/gitiles/templates/RepositoryIndex.soy
new file mode 100644
index 0000000..78f5b79
--- /dev/null
+++ b/resources/com/google/gitiles/templates/RepositoryIndex.soy
@@ -0,0 +1,132 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Index page for a repository.
+ *
+ * @param repositoryName name of this repository.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for custom styling.
+ * @param breadcrumbs breadcrumbs for this page.
+ * @param cloneUrl clone URL for this repository.
+ * @param description description text of the repository.
+ * @param? mirroredFromUrl URL this repository is mirrored from.
+ * @param branches list of branch objects with url, name, and isHead keys.
+ * @param? moreBranchesUrl URL to show more branches, if necessary.
+ * @param tags list of tag objects with url and name keys.
+ * @param? moreTagsUrl URL to show more branches, if necessary.
+ * @param hasLog whether a log should be shown for HEAD.
+ * @param? readmeHtml optional rendered README.md contents.
+ */
+{template .repositoryIndex}
+{if $readmeHtml}
+  {call .header data="all"}
+    {param title: $repositoryName /}
+    {param repositoryName: null /}
+    {param menuEntries: $menuEntries /}
+    {param customVariant: $customVariant /}
+    {param breadcrumbs: $breadcrumbs /}
+    {param css: [gitiles.DOC_CSS_URL] /}
+  {/call}
+{else}
+  {call .header}
+    {param title: $repositoryName /}
+    {param repositoryName: null /}
+    {param menuEntries: $menuEntries /}
+    {param customVariant: $customVariant /}
+    {param breadcrumbs: $breadcrumbs /}
+  {/call}
+{/if}
+
+{if $description}
+  <h2 class="RepoDescription">{$description}</h2>
+{/if}
+
+{if $mirroredFromUrl}
+  <div class="RepoMirroredFrom">
+    {msg desc="Informational text describing source of repository"}
+      Mirrored from <a href="{$mirroredFromUrl}">{$mirroredFromUrl}</a>
+    {/msg}
+  </div>
+{/if}
+
+<div class="CloneRepo">
+  <div class="CloneRepo-title">Clone this repo:</div>
+  <input type="text" class="u-monospace CloneRepo-command"
+         onclick="this.focus();if(this.selectionStart==this.selectionEnd){lb}this.select(){rb}"
+         readonly="readonly" value="git clone {$cloneUrl}">
+</div>
+
+{if $hasLog and (length($branches) or length($tags))}
+  <div class="RepoShortlog">
+    <div class="RepoShortlog-refs">
+      {call .branches_ data="all" /}
+      {call .tags_ data="all" /}
+    </div>
+    <div class="RepoShortlog-log">
+      {call .streamingPlaceholder /}
+      {if $readmeHtml}
+        <div class="doc RepoIndexDoc">{$readmeHtml}</div>
+      {/if}
+    </div>
+  </div>
+
+{elseif $hasLog}
+  {call .streamingPlaceholder /}
+{elseif length($branches) or length($tags)}
+  {call .branches_ data="all" /}
+  {call .tags_ data="all" /}
+{/if}
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
+
+/**
+ * List of branches.
+ *
+ * @param? branches list of branch objects with url and name keys.
+ * @param? moreBranchesUrl URL to show more branches, if necessary.
+ */
+{template .branches_ visibility="private"}
+  {if length($branches)}
+    {call .refList}
+      {param type: 'Branches' /}
+      {param refs: $branches /}
+    {/call}
+    {if $moreBranchesUrl}
+      <a href="{$moreBranchesUrl}">{msg desc="link to view more branches"}More...{/msg}</a>
+    {/if}
+  {/if}
+{/template}
+
+/**
+ * List of tags.
+ *
+ * @param? tags list of branch objects with url and name keys.
+ * @param? moreTagsUrl URL to show more tags, if necessary.
+ */
+{template .tags_ visibility="private"}
+  {if length($tags)}
+    {call .refList}
+      {param type: 'Tags' /}
+      {param refs: $tags /}
+    {/call}
+    {if $moreTagsUrl}
+      <a href="{$moreTagsUrl}">{msg desc="link to view more tags"}More...{/msg}</a>
+    {/if}
+  {/if}
+{/template}
diff --git a/resources/com/google/gitiles/templates/RevisionDetail.soy b/resources/com/google/gitiles/templates/RevisionDetail.soy
new file mode 100644
index 0000000..2bfabe0
--- /dev/null
+++ b/resources/com/google/gitiles/templates/RevisionDetail.soy
@@ -0,0 +1,65 @@
+// Copyright 2012 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.
+{namespace gitiles autoescape="strict"}
+
+/**
+ * Detail page about a single revision.
+ *
+ * @param title human-readable revision name.
+ * @param repositoryName name of this repository.
+ * @param? menuEntries menu entries.
+ * @param? customVariant variant name for styling.
+ * @param breadcrumbs breadcrumbs for this page.
+ * @param? hasBlob set to true if the revision or its peeled value is a blob.
+ * @param? hasReadme set to true if the treeDetail has readmeHtml.
+ * @param objects list of objects encountered when peeling this object. Each
+ *     object has a "type" key with one of the
+ *     org.eclipse.jgit.lib.Contants.TYPE_* constant strings, and a "data" key
+ *     with an object whose keys correspond to the appropriate object detail
+ *     template from ObjectDetail.soy.
+ */
+{template .revisionDetail}
+{if $hasBlob}
+  {call .header data="all"}
+    {param css: [gitiles.PRETTIFY_CSS_URL] /}
+  {/call}
+{elseif $hasReadme}
+  {call .header data="all"}
+    {param css: [gitiles.DOC_CSS_URL, gitiles.PRETTIFY_CSS_URL] /}
+  {/call}
+{else}
+  {call .header data="all" /}
+{/if}
+
+{foreach $object in $objects}
+  {switch $object.type}
+    {case 'commit'}
+      {call .commitDetail data="$object.data" /}
+    {case 'tree'}
+      {call .treeDetail data="$object.data" /}
+    {case 'blob'}
+      {call .blobDetail data="$object.data" /}
+    {case 'tag'}
+      {call .tagDetail data="$object.data" /}
+    {default}
+      <div class="error">
+        {msg desc="Error message for an unknown object type"}Object has unknown type.{/msg}
+      </div>
+  {/switch}
+{/foreach}
+
+{call .footer}
+  {param customVariant: $customVariant /}
+{/call}
+{/template}
diff --git a/resources/web.xml b/resources/web.xml
new file mode 100644
index 0000000..bffacb5
--- /dev/null
+++ b/resources/web.xml
@@ -0,0 +1,40 @@
+<!DOCTYPE web-app PUBLIC
+ "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+ "http://java.sun.com/dtd/web-app_2_3.dtd" >
+<!--
+  Copyright 2012 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.
+-->
+<web-app>
+  <display-name>Gitiles</display-name>
+
+  <servlet>
+    <servlet-name>gitiles</servlet-name>
+    <servlet-class>com.google.gitiles.GitilesServlet</servlet-class>
+    <init-param>
+      <param-name>configPath</param-name>
+      <param-value>gitiles.config</param-value>
+    </init-param>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>default</servlet-name>
+    <url-pattern>/+static/*</url-pattern>
+  </servlet-mapping>
+
+  <servlet-mapping>
+    <servlet-name>gitiles</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+</web-app>