Merge "Adjust to upstream buck and new bucklet version"
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/CommitData.java b/gitiles-servlet/src/main/java/com/google/gitiles/CommitData.java
index 0f614e6..76f5273 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/CommitData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/CommitData.java
@@ -66,6 +66,7 @@
LOG_URL,
MESSAGE,
PARENTS,
+ PARENT_BLAME_URL,
SHA,
SHORT_MESSAGE,
TAGS,
@@ -144,6 +145,7 @@
result.tree = c.getTree().copy();
}
if (fs.contains(Field.TREE_URL)) {
+ // Tree always implies the root tree.
result.treeUrl = GitilesView.path().copyFrom(view).setPathPart("/").toUrl();
}
if (fs.contains(Field.PARENTS)) {
@@ -175,12 +177,12 @@
}
}
- private static String urlFromView(GitilesView view, RevCommit commit,
- GitilesView.Builder builder) {
+ private static String urlFromView(GitilesView view, RevCommit commit, GitilesView.Builder builder) {
Revision rev = view.getRevision();
return builder.copyFrom(view)
+ .setOldRevision(Revision.NULL)
.setRevision(rev.getId().equals(commit) ? rev.getName() : commit.name(), commit)
- .setPathPart(null)
+ .setPathPart(view.getPathPart())
.toUrl();
}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java b/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java
index bd03eb2..8127d6d 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/CommitSoyData.java
@@ -52,6 +52,9 @@
Field.COMMITTER, Field.SHA, Field.TREE, Field.TREE_URL, Field.PARENTS, Field.MESSAGE,
Field.LOG_URL, Field.ARCHIVE_URL, Field.ARCHIVE_TYPE);
+ private static final ImmutableSet<Field> NESTED_FIELDS = Sets.immutableEnumSet(
+ Field.PARENT_BLAME_URL);
+
private Linkifier linkifier;
private RevWalk walk;
private ArchiveFormat archiveFormat;
@@ -111,7 +114,7 @@
data.put("treeUrl", cd.treeUrl);
}
if (cd.parents != null) {
- data.put("parents", toSoyData(view, cd.parents));
+ data.put("parents", toSoyData(view, fs, cd.parents));
}
if (cd.shortMessage != null) {
data.put("shortMessage", cd.shortMessage);
@@ -132,7 +135,8 @@
if (cd.diffEntries != null) {
data.put("diffTree", toSoyData(view, cd.diffEntries));
}
- checkState(fs.size() == data.size(), "bad commit data fields: %s != %s", fs, data.keySet());
+ checkState(Sets.difference(fs, NESTED_FIELDS).size() == data.size(),
+ "bad commit data fields: %s != %s", fs, data.keySet());
return ImmutableMap.copyOf(data);
}
@@ -151,13 +155,16 @@
"relativeTime", RelativeDateFormatter.format(ident.getWhen()));
}
- private List<Map<String, String>> toSoyData(GitilesView view, List<RevCommit> parents) {
+ private List<Map<String, String>> toSoyData(GitilesView view, Set<Field> fs,
+ List<RevCommit> parents) {
List<Map<String, String>> result = Lists.newArrayListWithCapacity(parents.size());
int i = 1;
// TODO(dborowitz): Render something slightly different when we're actively
// viewing a diff against one of the parents.
for (RevCommit parent : parents) {
String name = parent.name();
+ // Clear path on parent diff view, since this parent may not have a diff
+ // for the path in question.
GitilesView.Builder diff = GitilesView.diff().copyFrom(view).setPathPart("");
String parentName;
if (parents.size() == 1) {
@@ -165,13 +172,21 @@
} else {
parentName = view.getRevision().getName() + "^" + (i++);
}
- result.add(ImmutableMap.of(
- "sha", name,
- "url", GitilesView.revision()
- .copyFrom(view)
- .setRevision(parentName, parent)
- .toUrl(),
- "diffUrl", diff.setOldRevision(parentName, parent).toUrl()));
+ Map<String, String> e = Maps.newHashMapWithExpectedSize(4);
+ e.put("sha", name);
+ e.put("url", GitilesView.revision()
+ .copyFrom(view)
+ .setRevision(parentName, parent)
+ .toUrl());
+ e.put("diffUrl", diff.setOldRevision(parentName, parent).toUrl());
+ if (fs.contains(Field.PARENT_BLAME_URL)) {
+ // Assumes caller has ensured path is a file.
+ e.put("blameUrl", GitilesView.blame()
+ .copyFrom(view)
+ .setRevision(Revision.peeled(parentName, parent))
+ .toUrl());
+ }
+ result.add(e);
}
return result;
}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java
index 2a18afb..ebe89e2 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/DiffServlet.java
@@ -18,11 +18,13 @@
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import com.google.common.base.Charsets;
+import com.google.gitiles.CommitData.Field;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.http.server.ServletUtils;
+import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -30,6 +32,7 @@
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.GitDateFormatter;
import org.eclipse.jgit.util.GitDateFormatter.Format;
@@ -38,6 +41,7 @@
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Map;
+import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -62,19 +66,17 @@
RevWalk walk = new RevWalk(repo);
try {
- boolean showCommit;
+ boolean showCommit, isFile;
AbstractTreeIterator oldTree;
AbstractTreeIterator newTree;
try {
// If we are viewing the diff between a commit and one of its parents,
// include the commit detail in the rendered page.
showCommit = isParentOf(walk, view.getOldRevision(), view.getRevision());
+ isFile = showCommit ? isFile(walk, view) : false;
oldTree = getTreeIterator(walk, view.getOldRevision().getId());
newTree = getTreeIterator(walk, view.getRevision().getId());
- } catch (MissingObjectException e) {
- res.setStatus(SC_NOT_FOUND);
- return;
- } catch (IncorrectObjectTypeException e) {
+ } catch (MissingObjectException | IncorrectObjectTypeException e) {
res.setStatus(SC_NOT_FOUND);
return;
}
@@ -82,11 +84,15 @@
Map<String, Object> data = getData(req);
data.put("title", "Diff - " + view.getRevisionRange());
if (showCommit) {
+ Set<Field> fs = CommitSoyData.DEFAULT_FIELDS;
+ if (isFile) {
+ fs = Field.setOf(fs, Field.PARENT_BLAME_URL);
+ }
GitDateFormatter df = new GitDateFormatter(Format.DEFAULT);
data.put("commit", new CommitSoyData()
.setLinkifier(linkifier)
.setArchiveFormat(getArchiveFormat(getAccess(req)))
- .toSoyData(req, walk.parseCommit(view.getRevision().getId()), df));
+ .toSoyData(req, walk.parseCommit(view.getRevision().getId()), fs, df));
}
if (!data.containsKey("repositoryName") && (view.getRepositoryName() != null)) {
data.put("repositoryName", view.getRepositoryName());
@@ -104,7 +110,7 @@
OutputStream out = res.getOutputStream();
try {
out.write(html[0].getBytes(Charsets.UTF_8));
- formatHtmlDiff(out, repo, oldTree, newTree, view.getPathPart());
+ formatHtmlDiff(out, view, repo, oldTree, newTree, view.getPathPart());
out.write(html[1].getBytes(Charsets.UTF_8));
} finally {
out.close();
@@ -124,6 +130,21 @@
}
}
+ private static boolean isFile(RevWalk walk, GitilesView view) throws IOException {
+ if (view.getPathPart().equals("")) {
+ return false;
+ }
+ TreeWalk tw = TreeWalk.forPath(
+ walk.getObjectReader(),
+ view.getPathPart(),
+ walk.parseTree(view.getRevision().getId()));
+ try {
+ return (tw.getRawMode(0) & FileMode.TYPE_FILE) > 0;
+ } finally {
+ tw.release();
+ }
+ }
+
private String[] renderAndSplit(Map<String, Object> data) {
String html = renderer.newRenderer("gitiles.diffDetail")
.setData(data)
@@ -138,11 +159,11 @@
return new String[] {html.substring(0, lt), html.substring(gt + 1)};
}
- private void formatHtmlDiff(OutputStream out,
+ private void formatHtmlDiff(OutputStream out, GitilesView view,
Repository repo, AbstractTreeIterator oldTree,
AbstractTreeIterator newTree, String path)
throws IOException {
- DiffFormatter diff = new HtmlDiffFormatter(renderer, out);
+ DiffFormatter diff = new HtmlDiffFormatter(renderer, view, out);
try {
if (!path.equals("")) {
diff.setPathFilter(PathFilter.create(path));
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/HtmlDiffFormatter.java b/gitiles-servlet/src/main/java/com/google/gitiles/HtmlDiffFormatter.java
index 993197d..98a8be3 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/HtmlDiffFormatter.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/HtmlDiffFormatter.java
@@ -15,12 +15,15 @@
package com.google.gitiles;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.eclipse.jgit.util.QuotedString.GIT_PATH;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringEscapeUtils;
import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.patch.FileHeader;
@@ -30,6 +33,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
+import java.util.Map;
/** Formats a unified format patch as UTF-8 encoded HTML. */
final class HtmlDiffFormatter extends DiffFormatter {
@@ -45,17 +49,21 @@
private static final byte[] LINE_END = "</span>\n".getBytes(Charsets.UTF_8);
private final Renderer renderer;
+ private final GitilesView view;
private int fileIndex;
+ private DiffEntry entry;
- HtmlDiffFormatter(Renderer renderer, OutputStream out) {
+ HtmlDiffFormatter(Renderer renderer, GitilesView view, OutputStream out) {
super(out);
this.renderer = checkNotNull(renderer, "renderer");
+ this.view = checkNotNull(view, "view");
}
@Override
public void format(List<? extends DiffEntry> entries) throws IOException {
for (fileIndex = 0; fileIndex < entries.size(); fileIndex++) {
- format(entries.get(fileIndex));
+ entry = entries.get(fileIndex);
+ format(entry);
}
}
@@ -79,21 +87,43 @@
private void renderHeader(String header)
throws IOException {
int lf = header.indexOf('\n');
- String first;
- String rest;
- if (0 <= lf) {
- first = header.substring(0, lf);
- rest = header.substring(lf + 1);
+ String rest = 0 <= lf ? header.substring(lf + 1) : "";
+
+ // Based on DiffFormatter.formatGitDiffFirstHeaderLine.
+ List<Map<String, String>> parts = Lists.newArrayListWithCapacity(3);
+ parts.add(ImmutableMap.of("text", "diff --git"));
+ if (entry.getChangeType() != ChangeType.ADD) {
+ parts.add(ImmutableMap.of(
+ "text", GIT_PATH.quote(getOldPrefix() + entry.getOldPath()),
+ "url", revisionUrl(view.getOldRevision(), entry.getOldPath())));
} else {
- first = header;
- rest = "";
+ parts.add(ImmutableMap.of(
+ "text", GIT_PATH.quote(getOldPrefix() + entry.getNewPath())));
}
+ if (entry.getChangeType() != ChangeType.DELETE) {
+ parts.add(ImmutableMap.of(
+ "text", GIT_PATH.quote(getNewPrefix() + entry.getNewPath()),
+ "url", revisionUrl(view.getRevision(), entry.getNewPath())));
+ } else {
+ parts.add(ImmutableMap.of(
+ "text", GIT_PATH.quote(getNewPrefix() + entry.getOldPath())));
+ }
+
getOutputStream().write(renderer.newRenderer("gitiles.diffHeader")
- .setData(ImmutableMap.of("first", first, "rest", rest, "fileIndex", fileIndex))
+ .setData(ImmutableMap.of("firstParts", parts, "rest", rest, "fileIndex", fileIndex))
.render()
.getBytes(Charsets.UTF_8));
}
+ private String revisionUrl(Revision rev, String path) {
+ return GitilesView.path()
+ .copyFrom(view)
+ .setOldRevision(Revision.NULL)
+ .setRevision(Revision.named(rev.getId().name()))
+ .setPathPart(path)
+ .toUrl();
+ }
+
@Override
protected void writeHunkHeader(int aStartLine, int aEndLine,
int bStartLine, int bEndLine) throws IOException {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java
index 3d88ffc..8259a9d 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java
@@ -130,14 +130,22 @@
abbrevSha = reader.abbreviate(r.getSourceCommit()).name();
abbrevShas.put(r.getSourceCommit(), abbrevSha);
}
- result.add(ImmutableMap.of(
- "abbrevSha", abbrevSha,
- "url", GitilesView.blame().copyFrom(view)
- .setRevision(r.getSourceCommit().name())
- .setPathPart(r.getSourcePath())
- .toUrl(),
- "author", CommitSoyData.toSoyData(r.getSourceAuthor(), df),
- "count", r.getCount()));
+ Map<String, Object> e = Maps.newHashMapWithExpectedSize(6);
+ e.put("abbrevSha", abbrevSha);
+ e.put("blameUrl", GitilesView.blame().copyFrom(view)
+ .setRevision(r.getSourceCommit().name())
+ .setPathPart(r.getSourcePath())
+ .toUrl());
+ e.put("commitUrl", GitilesView.revision().copyFrom(view)
+ .setRevision(r.getSourceCommit().name())
+ .toUrl());
+ e.put("diffUrl", GitilesView.diff().copyFrom(view)
+ .setRevision(r.getSourceCommit().name())
+ .setPathPart(r.getSourcePath())
+ .toUrl());
+ e.put("author", CommitSoyData.toSoyData(r.getSourceAuthor(), df));
+ e.put("count", r.getCount());
+ result.add(e);
}
}
return result;
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 19668ce..0dbd477 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
@@ -343,10 +343,12 @@
padding: 0;
}
#regions {
- padding-top: 0px;
+ padding-top: 2px;
padding-right: 2px;
padding-bottom: 5px;
padding-left: 2px;
+ white-space: nowrap;
+ list-style-type: none;
/* Matching pre.git-blob below. */
font-family: monospace;
@@ -370,6 +372,31 @@
border-top: 0;
}
+#regions li {
+ position: relative;
+}
+#regions li.line:hover {
+ background-color: #eee;
+}
+#regions li .regionMenu {
+ position: absolute;
+ top: 13px;
+ left: 0;
+ visibility: hidden;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 8px;
+ padding-right: 8px;
+
+ background-color: #eee;
+ font-size: 8pt;
+ font-family: arial,sans-serif;
+ z-index: 101;
+}
+#regions li:hover .regionMenu {
+ visibility: visible;
+}
+
/* Override some styles from the default prettify.css. */
/* Line numbers on all lines. */
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy
index 52486cf..0118d92 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy
@@ -26,11 +26,13 @@
* following keys:
* abbrevSha: abbreviated SHA-1 of revision for this line; if missing,
* assume blame info is missing.
- * url: URL for detail about the revision
* author: author information with at least "name" and "relativeTime"
* keys.
* relativeTime: relative time of the revision
* count: line count
+ * 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
*/
{template .blameDetail}
{if $regions}
@@ -48,35 +50,32 @@
<table id="blame">
<tr>
<td>
- <pre id="regions">
- <ul>
- {foreach $region in $regions}
- <li>
- {if $region.abbrevSha}
- <a href="{$region.url}">
- <span class="sha1">{$region.abbrevSha}</span>
- {sp}<span class="author">{$region.author.name}</span>
- {sp}<span class="time">- {$region.author.relativeTime}</span>
- </a>
- {else}
-
- {/if}
- </li>
- {for $i in range($region.count - 1)}
- <li> </li>
- {/for}
- {/foreach}
- </ul>
- </pre>
+ <ul id="regions">
+ {foreach $region in $regions}
+ <li class="line">
+ {if $region.abbrevSha}
+ <a href="{$region.commitUrl}">
+ <span class="sha1">{$region.abbrevSha}</span>
+ {sp}<span class="author">{$region.author.name}</span>
+ {sp}<span class="time">- {$region.author.relativeTime}</span>
+ </a>
+ <div class="regionMenu">
+ [<a href="{$region.commitUrl}">commit</a>]
+ {sp}[<a href="{$region.diffUrl}">diff</a>]
+ {sp}[<a href="{$region.blameUrl}">blame</a>]
+ </div>
+ {else}
+
+ {/if}
+ </li>
+ {for $i in range($region.count - 1)}
+ <li> </li>
+ {/for}
+ {/foreach}
+ </ul>
</td>
<td>
- {call .blobBox}
- {param data: $data.data /}
- {param size: $data.size /}
- // No pretty printing; line spacing is different with/without, and
- // JS rendering is a performance pain point.
- {param lang: null /}
- {/call}
+ {call .blobBox data="$data" /}
</td>
</tr>
</table>
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy
index d888d0a..5222fa0 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/DiffDetail.soy
@@ -38,13 +38,22 @@
/**
* File header for a single unified diff patch.
*
- * @param first the first line of the header, with no trailing LF.
+ * @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="diff-header">
-<a name="F{$fileIndex}" class="diff-git">{$first}</a>{\n}
+<a name="F{$fileIndex}" class="diff-git"></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/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy
index 78ea913..66a0d75 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy
@@ -25,6 +25,7 @@
* 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.
@@ -74,6 +75,9 @@
<a href="{$parent.url}">{$parent.sha}</a>
<span class="diff-link">
[<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>
@@ -234,10 +238,10 @@
{template .blobBox}
{if $data != null}
{if $data}
- {if $lang != null}
+ {if $lang}
<pre class="git-blob prettyprint linenums lang-{$lang}">{$data}</pre>
{else}
- <pre class="git-blob">{$data}</pre>
+ <pre class="git-blob prettyprint linenums">{$data}</pre>
{/if}
{else}
<div class="file-empty">Empty file</div>