blob: 0c57e69e48a623b3a49136b522819cfe73ecb571 [file] [log] [blame] [raw]
// 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.
package com.google.gitiles;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gitiles.CommitData.Field;
import com.google.template.soy.data.LoggingAdvisingAppendable;
import com.google.template.soy.jbcsrc.api.SoySauce;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
public class LogSoyData {
private static final ImmutableSet<Field> FIELDS =
Sets.immutableEnumSet(
Field.ABBREV_SHA,
Field.SHA,
Field.URL,
Field.SHORT_MESSAGE,
Field.MESSAGE,
Field.AUTHOR,
Field.COMMITTER,
Field.BRANCHES,
Field.TAGS);
private static final ImmutableSet<Field> VERBOSE_FIELDS = Field.setOf(FIELDS, Field.DIFF_TREE);
/** Behavior for the footer link when rendering streaming log data. */
public enum FooterBehavior {
/** "Next" link that skips commits in the current view. */
NEXT,
/** "More" link that starts from HEAD. */
LOG_HEAD;
}
private final HttpServletRequest req;
private final GitilesView view;
private final Set<Field> fields;
private final String variant;
private CommitSoyData csd;
public LogSoyData(HttpServletRequest req, GitilesAccess access, String pretty)
throws IOException {
this.req = checkNotNull(req);
this.view = checkNotNull(ViewFilter.getView(req));
checkNotNull(pretty);
Config config = access.getConfig();
fields = config.getBoolean("logFormat", pretty, "verbose", false) || pretty.equals("fuller") ? VERBOSE_FIELDS : FIELDS;
variant = firstNonNull(config.getString("logFormat", pretty, "variant"), pretty);
}
private void renderHtml(SoySauce.Renderer renderer, LoggingAdvisingAppendable out)
throws IOException {
if (!renderer.renderHtml(out).result().isDone()) {
throw new IOException("failed to render HTML");
}
}
public void renderStreaming(
Paginator paginator,
@Nullable String revision,
Renderer renderer,
Writer writer,
DateFormatter df,
FooterBehavior footerBehavior)
throws IOException {
LoggingAdvisingAppendable out = LoggingAdvisingAppendable.delegating(writer);
renderHtml(
renderer
.newRenderer("com.google.gitiles.templates.LogDetail.logEntriesHeader")
.setData(toHeaderSoyData(paginator, revision)),
out);
SoySauce.Renderer entryRenderer =
renderer.newRenderer("com.google.gitiles.templates.LogDetail.logEntryWrapper");
boolean renderedEntries = false;
for (RevCommit c : paginator) {
renderHtml(entryRenderer.setData(toEntrySoyData(paginator, c, df)), out);
renderedEntries = true;
}
if (!renderedEntries) {
renderHtml(renderer.newRenderer("com.google.gitiles.templates.LogDetail.emptyLog"), out);
}
renderHtml(
renderer
.newRenderer("com.google.gitiles.templates.LogDetail.logEntriesFooter")
.setData(toFooterSoyData(paginator, revision, footerBehavior)),
out);
}
private Map<String, Object> toHeaderSoyData(Paginator paginator, @Nullable String revision) {
Map<String, Object> data = Maps.newHashMapWithExpectedSize(1);
ObjectId prev = paginator.getPreviousStart();
if (prev != null) {
GitilesView.Builder prevView = copyAndCanonicalizeView(revision);
if (!prevView.getRevision().getId().equals(prev)) {
prevView.replaceParam(LogServlet.START_PARAM, prev.name());
}
data.put("previousUrl", prevView.toUrl());
}
return data;
}
private Map<String, Object> toEntrySoyData(Paginator paginator, RevCommit c, DateFormatter df)
throws IOException {
if (csd == null) {
csd = new CommitSoyData();
}
Map<String, Object> entry = csd.toSoyData(req, paginator.getWalk(), c, fields, df);
DiffEntry rename = paginator.getRename(c);
if (rename != null) {
entry.put("rename", toRenameSoyData(rename));
}
return ImmutableMap.of(
"variant", variant,
"entry", entry);
}
private @Nullable Map<String, Object> toRenameSoyData(DiffEntry entry) {
if (entry == null) {
return null;
}
ChangeType type = entry.getChangeType();
if (type != ChangeType.RENAME && type != ChangeType.COPY) {
return null;
}
return ImmutableMap.<String, Object>of(
"changeType", type.toString(),
"oldPath", entry.getOldPath(),
"newPath", entry.getNewPath(),
"score", entry.getScore());
}
private Map<String, Object> toFooterSoyData(
Paginator paginator, @Nullable String revision, FooterBehavior behavior) {
switch (behavior) {
case NEXT:
ObjectId next = paginator.getNextStart();
if (next == null) {
return ImmutableMap.of();
}
return ImmutableMap.of(
"nextUrl",
copyAndCanonicalizeView(revision)
.replaceParam(LogServlet.START_PARAM, next.name())
.toUrl(),
"nextText",
"Next");
case LOG_HEAD:
return ImmutableMap.of(
"nextUrl", GitilesView.log().copyFrom(view).toUrl(), "nextText", "More");
default:
throw new IllegalStateException("unknown footer behavior: " + behavior);
}
}
private GitilesView.Builder copyAndCanonicalizeView(String revision) {
// Canonicalize the view by using full SHAs.
GitilesView.Builder copy = GitilesView.log().copyFrom(view);
if (!Revision.isNull(view.getRevision())) {
copy.setRevision(view.getRevision());
} else if (revision != null) {
copy.setRevision(Revision.named(revision));
} else {
copy.setRevision(Revision.NULL);
}
if (!Revision.isNull(view.getOldRevision())) {
copy.setOldRevision(view.getOldRevision());
}
return copy;
}
}