blob: ac81f800c3a23d842b1e71ad908496d766242bf2 [file] [log] [blame]
// 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.Preconditions.checkState;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.COPY;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.DELETE;
import static org.eclipse.jgit.diff.DiffEntry.ChangeType.RENAME;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gitiles.CommitData.DiffList;
import com.google.gitiles.CommitData.Field;
import com.google.template.soy.data.restricted.NullData;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.RelativeDateFormatter;
/** Soy data converter for git commits. */
public class CommitSoyData {
static final ImmutableSet<Field> DEFAULT_FIELDS =
Sets.immutableEnumSet(
Field.AUTHOR,
Field.COMMITTER,
Field.SHA,
Field.TREE,
Field.TREE_URL,
Field.PARENTS,
Field.MESSAGE,
Field.NOTES, // Optional Field
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 CommitData.Builder cdb;
private ArchiveFormat archiveFormat;
CommitSoyData setLinkifier(@Nullable Linkifier linkifier) {
this.linkifier = linkifier;
return this;
}
CommitSoyData setArchiveFormat(@Nullable ArchiveFormat archiveFormat) {
this.archiveFormat = archiveFormat;
return this;
}
Map<String, Object> toSoyData(
HttpServletRequest req, RevWalk walk, RevCommit c, Set<Field> fs, DateFormatter df)
throws IOException {
GitilesView view = ViewFilter.getView(req);
if (cdb == null) {
cdb = new CommitData.Builder();
}
CommitData cd = cdb.setArchiveFormat(archiveFormat).build(req, walk, c, fs);
Map<String, Object> data = Maps.newHashMapWithExpectedSize(fs.size());
if (cd.author != null) {
data.put("author", toSoyData(cd.author, df));
}
if (cd.committer != null) {
data.put("committer", toSoyData(cd.committer, df));
}
if (cd.sha != null) {
data.put("sha", cd.sha.name());
}
if (cd.abbrev != null) {
data.put("abbrevSha", cd.abbrev.name());
}
if (cd.url != null) {
data.put("url", cd.url);
}
if (cd.logUrl != null) {
data.put("logUrl", cd.logUrl);
}
if (cd.archiveUrl != null) {
data.put("archiveUrl", cd.archiveUrl);
}
if (cd.archiveType != null) {
data.put("archiveType", cd.archiveType.getShortName());
}
if (cd.tree != null) {
data.put("tree", cd.tree.name());
}
if (cd.treeUrl != null) {
data.put("treeUrl", cd.treeUrl);
}
if (cd.parents != null) {
data.put("parents", toSoyData(view, fs, cd.parents));
}
if (cd.shortMessage != null) {
data.put("shortMessage", cd.shortMessage);
}
if (cd.branches != null) {
data.put("branches", toSoyData(view, cd.branches, Constants.R_HEADS));
}
if (cd.tags != null) {
data.put("tags", toSoyData(view, cd.tags, Constants.R_TAGS));
}
if (cd.message != null) {
if (linkifier != null) {
data.put("message", linkifier.linkify(req, cd.message));
} else {
data.put("message", cd.message);
}
}
if (cd.diffEntries != null) {
data.put("diffTree", toSoyData(view, cd.diffEntries));
}
int diffSetSize = Sets.difference(fs, NESTED_FIELDS).size();
if (fs.contains(
Field.NOTES)) { // Since NOTES is optional this is required for checkState to pass
diffSetSize -= 1;
}
checkState(diffSetSize == data.size(), "bad commit data fields: %s != %s", fs, data.keySet());
if (cd.notes != null && !cd.notes.isEmpty()) {
data.put("notes", cd.notes);
}
return data;
}
Map<String, Object> toSoyData(
HttpServletRequest req, RevWalk walk, RevCommit commit, DateFormatter df) throws IOException {
return toSoyData(req, walk, commit, DEFAULT_FIELDS, df);
}
// TODO(dborowitz): Extract this.
public static Map<String, String> toSoyData(PersonIdent ident, DateFormatter df) {
return ImmutableMap.of(
"name", ident.getName(),
"email", ident.getEmailAddress(),
"time", df.format(ident),
// TODO(dborowitz): Switch from relative to absolute at some threshold.
"relativeTime", RelativeDateFormatter.format(ident.getWhen()));
}
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) {
parentName = view.getRevision().getName() + "^";
} else {
parentName = view.getRevision().getName() + "^" + (i++);
}
diff.setOldRevision(parentName, parent);
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.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;
}
private static Object toSoyData(GitilesView view, DiffList dl) {
if (dl.oldRevision == null) {
return NullData.INSTANCE;
}
GitilesView.Builder diffUrl =
GitilesView.diff()
.copyFrom(view)
.setOldRevision(dl.oldRevision)
.setRevision(dl.revision)
.setPathPart("");
List<Object> result = Lists.newArrayListWithCapacity(dl.entries.size());
for (DiffEntry e : dl.entries) {
Map<String, Object> entry = Maps.newHashMapWithExpectedSize(5);
ChangeType type = e.getChangeType();
if (type != DELETE) {
entry.put("path", e.getNewPath());
entry.put(
"url",
GitilesView.path()
.copyFrom(view)
.setRevision(dl.revision)
.setPathPart(e.getNewPath())
.toUrl());
} else {
entry.put("path", e.getOldPath());
entry.put(
"url",
GitilesView.path()
.copyFrom(view)
.setRevision(dl.oldRevision)
.setPathPart(e.getOldPath())
.toUrl());
}
entry.put("diffUrl", diffUrl.setAnchor("F" + result.size()).toUrl());
entry.put("changeType", e.getChangeType().toString());
if (type == COPY || type == RENAME) {
entry.put("oldPath", e.getOldPath());
}
result.add(entry);
}
return result;
}
private static List<Map<String, String>> toSoyData(
GitilesView view, List<Ref> refs, String prefix) {
List<Map<String, String>> result = Lists.newArrayListWithCapacity(refs.size());
for (Ref ref : refs) {
if (ref.getName().startsWith(prefix)) {
result.add(
ImmutableMap.of(
"name", ref.getName().substring(prefix.length()),
"url",
GitilesView.revision()
.copyFrom(view)
.setRevision(Revision.unpeeled(ref.getName(), ref.getObjectId()))
.toUrl()));
}
}
return result;
}
}