| // Copyright (C) 2014 The Android Open Source Project |
| // |
| // 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.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static java.util.Comparator.comparing; |
| import static java.util.stream.Collectors.toList; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import java.io.IOException; |
| import java.util.Arrays; |
| 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.DiffFormatter; |
| import org.eclipse.jgit.http.server.ServletUtils; |
| import org.eclipse.jgit.lib.AbbreviatedObjectId; |
| import org.eclipse.jgit.lib.AnyObjectId; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.notes.NoteMap; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.treewalk.AbstractTreeIterator; |
| import org.eclipse.jgit.treewalk.CanonicalTreeParser; |
| import org.eclipse.jgit.treewalk.EmptyTreeIterator; |
| import org.eclipse.jgit.util.io.NullOutputStream; |
| |
| /** Format-independent data about a single commit. */ |
| class CommitData { |
| enum Field { |
| ABBREV_SHA, |
| ARCHIVE_TYPE, |
| ARCHIVE_URL, |
| AUTHOR, |
| BRANCHES, |
| COMMITTER, |
| DIFF_TREE, |
| LOG_URL, |
| MESSAGE, |
| NOTES, |
| PARENTS, |
| PARENT_BLAME_URL, |
| SHA, |
| SHORT_MESSAGE, |
| TAGS, |
| TREE, |
| TREE_URL, |
| URL; |
| |
| static ImmutableSet<Field> setOf(Iterable<Field> base, Field... fields) { |
| return Sets.immutableEnumSet(Iterables.concat(base, Arrays.asList(fields))); |
| } |
| } |
| |
| static class DiffList { |
| Revision revision; |
| Revision oldRevision; |
| List<DiffEntry> entries; |
| } |
| |
| static class Builder { |
| private ArchiveFormat archiveFormat; |
| private Map<AnyObjectId, Set<Ref>> refsById; |
| private static final int MAX_NOTE_SIZE = 524288; |
| |
| Builder setArchiveFormat(@Nullable ArchiveFormat archiveFormat) { |
| this.archiveFormat = archiveFormat; |
| return this; |
| } |
| |
| CommitData build(HttpServletRequest req, RevWalk walk, RevCommit c, Set<Field> fs) |
| throws IOException { |
| checkFields(fs); |
| checkNotNull(req, "request"); |
| checkNotNull(walk, "walk"); |
| Repository repo = ServletUtils.getRepository(req); |
| GitilesView view = ViewFilter.getView(req); |
| |
| CommitData result = new CommitData(); |
| |
| if (fs.contains(Field.AUTHOR)) { |
| walk.parseBody(c); |
| result.author = c.getAuthorIdent(); |
| } |
| if (fs.contains(Field.COMMITTER)) { |
| walk.parseBody(c); |
| result.committer = c.getCommitterIdent(); |
| } |
| if (fs.contains(Field.SHA)) { |
| result.sha = c.copy(); |
| } |
| if (fs.contains(Field.ABBREV_SHA)) { |
| try (ObjectReader reader = repo.getObjectDatabase().newReader()) { |
| result.abbrev = reader.abbreviate(c); |
| } |
| } |
| if (fs.contains(Field.URL)) { |
| result.url = GitilesView.revision().copyFrom(view).setRevision(c).toUrl(); |
| } |
| if (fs.contains(Field.LOG_URL)) { |
| result.logUrl = urlFromView(view, c, GitilesView.log()); |
| } |
| if (fs.contains(Field.ARCHIVE_URL)) { |
| result.archiveUrl = |
| urlFromView( |
| view, c, GitilesView.archive().setExtension(archiveFormat.getDefaultSuffix())); |
| } |
| if (fs.contains(Field.ARCHIVE_TYPE)) { |
| result.archiveType = archiveFormat; |
| } |
| if (fs.contains(Field.TREE)) { |
| 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)) { |
| result.parents = Arrays.asList(c.getParents()); |
| } |
| if (fs.contains(Field.BRANCHES)) { |
| result.branches = getRefsById(repo, c, Constants.R_HEADS); |
| } |
| if (fs.contains(Field.TAGS)) { |
| result.tags = getRefsById(repo, c, Constants.R_TAGS); |
| } |
| if (fs.contains(Field.NOTES)) { |
| Ref notesRef = repo.getRefDatabase().exactRef(Constants.R_NOTES_COMMITS); |
| if (notesRef != null) { |
| try { |
| byte[] data = |
| NoteMap.read(walk.getObjectReader(), walk.parseCommit(notesRef.getObjectId())) |
| .getCachedBytes(c, MAX_NOTE_SIZE); |
| result.notes = new String(data, "utf-8"); |
| } catch (Exception e) { |
| result.notes = ""; |
| } |
| } |
| } |
| if (fs.contains(Field.MESSAGE)) { |
| walk.parseBody(c); |
| result.message = c.getFullMessage(); |
| } |
| if (fs.contains(Field.SHORT_MESSAGE)) { |
| walk.parseBody(c); |
| String msg = c.getShortMessage(); |
| if (msg.length() > 80) { |
| String ft = result.message; |
| if (ft == null) { |
| ft = c.getFullMessage(); |
| } |
| int lf = ft.indexOf('\n'); |
| if (lf > 0) { |
| msg = ft.substring(0, lf); |
| } |
| } |
| result.shortMessage = msg; |
| } |
| if (fs.contains(Field.DIFF_TREE)) { |
| result.diffEntries = computeDiffEntries(repo, view, walk, c); |
| } |
| return result; |
| } |
| |
| private void checkFields(Set<Field> fs) { |
| if (fs.contains(Field.ARCHIVE_URL) || fs.contains(Field.ARCHIVE_TYPE)) { |
| checkState(archiveFormat != null, "archive format required"); |
| } |
| } |
| |
| 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(view.getPathPart()) |
| .toUrl(); |
| } |
| |
| private List<Ref> getRefsById(Repository repo, ObjectId id, String prefix) throws IOException { |
| if (refsById == null) { |
| refsById = repo.getAllRefsByPeeledObjectId(); |
| } |
| Set<Ref> refs = refsById.get(id); |
| if (refs == null) { |
| return ImmutableList.of(); |
| } |
| return refs.stream() |
| .filter(r -> r.getName().startsWith(prefix)) |
| .sorted(comparing(Ref::getName)) |
| .collect(toList()); |
| } |
| |
| private AbstractTreeIterator getTreeIterator(RevWalk walk, RevCommit commit) |
| throws IOException { |
| CanonicalTreeParser p = new CanonicalTreeParser(); |
| p.reset(walk.getObjectReader(), walk.parseTree(walk.parseCommit(commit).getTree())); |
| return p; |
| } |
| |
| private DiffList computeDiffEntries( |
| Repository repo, GitilesView view, RevWalk walk, RevCommit commit) throws IOException { |
| DiffList result = new DiffList(); |
| result.revision = |
| view.getRevision().matches(commit) |
| ? view.getRevision() |
| : Revision.peeled(commit.name(), commit); |
| |
| AbstractTreeIterator oldTree; |
| switch (commit.getParentCount()) { |
| case 0: |
| result.oldRevision = Revision.NULL; |
| oldTree = new EmptyTreeIterator(); |
| break; |
| case 1: |
| result.oldRevision = |
| Revision.peeled(result.revision.getName() + "^", commit.getParent(0)); |
| oldTree = getTreeIterator(walk, commit.getParent(0)); |
| break; |
| default: |
| // TODO(dborowitz): handle merges |
| return result; |
| } |
| AbstractTreeIterator newTree = getTreeIterator(walk, commit); |
| |
| try (DiffFormatter diff = new DiffFormatter(NullOutputStream.INSTANCE)) { |
| diff.setRepository(repo); |
| diff.setDetectRenames(true); |
| result.entries = diff.scan(oldTree, newTree); |
| return result; |
| } |
| } |
| } |
| |
| ObjectId sha; |
| PersonIdent author; |
| PersonIdent committer; |
| AbbreviatedObjectId abbrev; |
| ObjectId tree; |
| List<RevCommit> parents; |
| String shortMessage; |
| String message; |
| String notes; |
| |
| List<Ref> branches; |
| List<Ref> tags; |
| DiffList diffEntries; |
| |
| String url; |
| String logUrl; |
| String treeUrl; |
| String archiveUrl; |
| ArchiveFormat archiveType; |
| } |