blob: 705240e7825fc3324c9aa93f0c8e7772879a934a [file] [log] [blame]
Dave Borowitz01551272014-03-16 13:43:16 -07001// Copyright (C) 2014 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles;
16
17import static com.google.common.base.Preconditions.checkNotNull;
18import static com.google.common.base.Preconditions.checkState;
Dave Borowitz3b4baab2016-10-04 09:44:10 -040019import static java.util.Comparator.comparing;
20import static java.util.stream.Collectors.toList;
Dave Borowitz01551272014-03-16 13:43:16 -070021
Dave Borowitz3b4baab2016-10-04 09:44:10 -040022import com.google.common.collect.ImmutableList;
Dave Borowitz01551272014-03-16 13:43:16 -070023import com.google.common.collect.ImmutableSet;
Dave Borowitz90d1db92014-03-16 14:16:15 -070024import com.google.common.collect.Iterables;
Dave Borowitz90d1db92014-03-16 14:16:15 -070025import com.google.common.collect.Sets;
Dave Borowitz3b744b12016-08-19 16:11:10 -040026import java.io.IOException;
27import java.util.Arrays;
28import java.util.List;
29import java.util.Map;
30import java.util.Set;
31import javax.annotation.Nullable;
32import javax.servlet.http.HttpServletRequest;
Dave Borowitz01551272014-03-16 13:43:16 -070033import org.eclipse.jgit.diff.DiffEntry;
34import org.eclipse.jgit.diff.DiffFormatter;
35import org.eclipse.jgit.http.server.ServletUtils;
36import org.eclipse.jgit.lib.AbbreviatedObjectId;
37import org.eclipse.jgit.lib.AnyObjectId;
38import org.eclipse.jgit.lib.Constants;
39import org.eclipse.jgit.lib.ObjectId;
40import org.eclipse.jgit.lib.ObjectReader;
41import org.eclipse.jgit.lib.PersonIdent;
42import org.eclipse.jgit.lib.Ref;
43import org.eclipse.jgit.lib.Repository;
44import org.eclipse.jgit.revwalk.RevCommit;
45import org.eclipse.jgit.revwalk.RevWalk;
46import org.eclipse.jgit.treewalk.AbstractTreeIterator;
47import org.eclipse.jgit.treewalk.CanonicalTreeParser;
48import org.eclipse.jgit.treewalk.EmptyTreeIterator;
49import org.eclipse.jgit.util.io.NullOutputStream;
50
Dave Borowitz01551272014-03-16 13:43:16 -070051/** Format-independent data about a single commit. */
52class CommitData {
David Pursehousee3d3ec82016-06-15 22:10:48 +090053 enum Field {
Dave Borowitz01551272014-03-16 13:43:16 -070054 ABBREV_SHA,
55 ARCHIVE_TYPE,
56 ARCHIVE_URL,
57 AUTHOR,
58 BRANCHES,
59 COMMITTER,
60 DIFF_TREE,
61 LOG_URL,
62 MESSAGE,
63 PARENTS,
Dave Borowitzf03fcea2014-04-21 17:20:33 -070064 PARENT_BLAME_URL,
Dave Borowitz01551272014-03-16 13:43:16 -070065 SHA,
66 SHORT_MESSAGE,
67 TAGS,
68 TREE,
69 TREE_URL,
70 URL;
Dave Borowitz90d1db92014-03-16 14:16:15 -070071
72 static ImmutableSet<Field> setOf(Iterable<Field> base, Field... fields) {
73 return Sets.immutableEnumSet(Iterables.concat(base, Arrays.asList(fields)));
74 }
Dave Borowitz01551272014-03-16 13:43:16 -070075 }
76
77 static class DiffList {
Dave Borowitz2298cef2014-09-23 10:29:46 -070078 Revision revision;
Dave Borowitz01551272014-03-16 13:43:16 -070079 Revision oldRevision;
80 List<DiffEntry> entries;
81 }
82
83 static class Builder {
Dave Borowitz01551272014-03-16 13:43:16 -070084 private ArchiveFormat archiveFormat;
85 private Map<AnyObjectId, Set<Ref>> refsById;
86
Dave Borowitz01551272014-03-16 13:43:16 -070087 Builder setArchiveFormat(@Nullable ArchiveFormat archiveFormat) {
88 this.archiveFormat = archiveFormat;
89 return this;
90 }
91
Jonathan Niederb49306a2019-03-07 14:10:57 -080092 CommitData build(HttpServletRequest req, RevWalk walk, RevCommit c, Set<Field> fs)
93 throws IOException {
Dave Borowitz01551272014-03-16 13:43:16 -070094 checkFields(fs);
95 checkNotNull(req, "request");
Jonathan Niederb49306a2019-03-07 14:10:57 -080096 checkNotNull(walk, "walk");
Dave Borowitz01551272014-03-16 13:43:16 -070097 Repository repo = ServletUtils.getRepository(req);
98 GitilesView view = ViewFilter.getView(req);
99
100 CommitData result = new CommitData();
101
102 if (fs.contains(Field.AUTHOR)) {
Jonathan Nieder4b6753d2019-03-07 14:25:13 -0800103 walk.parseBody(c);
Dave Borowitz01551272014-03-16 13:43:16 -0700104 result.author = c.getAuthorIdent();
105 }
106 if (fs.contains(Field.COMMITTER)) {
Jonathan Nieder4b6753d2019-03-07 14:25:13 -0800107 walk.parseBody(c);
Dave Borowitz01551272014-03-16 13:43:16 -0700108 result.committer = c.getCommitterIdent();
109 }
110 if (fs.contains(Field.SHA)) {
111 result.sha = c.copy();
112 }
113 if (fs.contains(Field.ABBREV_SHA)) {
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700114 try (ObjectReader reader = repo.getObjectDatabase().newReader()) {
Dave Borowitz01551272014-03-16 13:43:16 -0700115 result.abbrev = reader.abbreviate(c);
Dave Borowitz01551272014-03-16 13:43:16 -0700116 }
117 }
118 if (fs.contains(Field.URL)) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200119 result.url = GitilesView.revision().copyFrom(view).setRevision(c).toUrl();
Dave Borowitz01551272014-03-16 13:43:16 -0700120 }
121 if (fs.contains(Field.LOG_URL)) {
122 result.logUrl = urlFromView(view, c, GitilesView.log());
123 }
124 if (fs.contains(Field.ARCHIVE_URL)) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200125 result.archiveUrl =
126 urlFromView(
127 view, c, GitilesView.archive().setExtension(archiveFormat.getDefaultSuffix()));
Dave Borowitz01551272014-03-16 13:43:16 -0700128 }
129 if (fs.contains(Field.ARCHIVE_TYPE)) {
130 result.archiveType = archiveFormat;
131 }
132 if (fs.contains(Field.TREE)) {
133 result.tree = c.getTree().copy();
134 }
135 if (fs.contains(Field.TREE_URL)) {
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700136 // Tree always implies the root tree.
Dave Borowitz01551272014-03-16 13:43:16 -0700137 result.treeUrl = GitilesView.path().copyFrom(view).setPathPart("/").toUrl();
138 }
139 if (fs.contains(Field.PARENTS)) {
140 result.parents = Arrays.asList(c.getParents());
141 }
Dave Borowitz01551272014-03-16 13:43:16 -0700142 if (fs.contains(Field.BRANCHES)) {
143 result.branches = getRefsById(repo, c, Constants.R_HEADS);
144 }
145 if (fs.contains(Field.TAGS)) {
146 result.tags = getRefsById(repo, c, Constants.R_TAGS);
147 }
148 if (fs.contains(Field.MESSAGE)) {
Jonathan Nieder4b6753d2019-03-07 14:25:13 -0800149 walk.parseBody(c);
Dave Borowitz01551272014-03-16 13:43:16 -0700150 result.message = c.getFullMessage();
151 }
Shawn Pearce680ab122015-05-11 23:15:06 -0700152 if (fs.contains(Field.SHORT_MESSAGE)) {
Jonathan Nieder4b6753d2019-03-07 14:25:13 -0800153 walk.parseBody(c);
Shawn Pearce680ab122015-05-11 23:15:06 -0700154 String msg = c.getShortMessage();
155 if (msg.length() > 80) {
156 String ft = result.message;
157 if (ft == null) {
158 ft = c.getFullMessage();
159 }
160 int lf = ft.indexOf('\n');
161 if (lf > 0) {
162 msg = ft.substring(0, lf);
163 }
164 }
165 result.shortMessage = msg;
166 }
Dave Borowitz01551272014-03-16 13:43:16 -0700167 if (fs.contains(Field.DIFF_TREE)) {
Jonathan Niederb49306a2019-03-07 14:10:57 -0800168 result.diffEntries = computeDiffEntries(repo, view, walk, c);
Dave Borowitz01551272014-03-16 13:43:16 -0700169 }
170
171 return result;
172 }
173
174 private void checkFields(Set<Field> fs) {
Dave Borowitz01551272014-03-16 13:43:16 -0700175 if (fs.contains(Field.ARCHIVE_URL) || fs.contains(Field.ARCHIVE_TYPE)) {
176 checkState(archiveFormat != null, "archive format required");
177 }
178 }
179
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200180 private static String urlFromView(
181 GitilesView view, RevCommit commit, GitilesView.Builder builder) {
Dave Borowitz01551272014-03-16 13:43:16 -0700182 Revision rev = view.getRevision();
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200183 return builder
184 .copyFrom(view)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700185 .setOldRevision(Revision.NULL)
Dave Borowitz01551272014-03-16 13:43:16 -0700186 .setRevision(rev.getId().equals(commit) ? rev.getName() : commit.name(), commit)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700187 .setPathPart(view.getPathPart())
Dave Borowitz01551272014-03-16 13:43:16 -0700188 .toUrl();
189 }
190
David Pursehouse14293fe2016-10-04 15:55:22 +0900191 private List<Ref> getRefsById(Repository repo, ObjectId id, String prefix) {
Dave Borowitz01551272014-03-16 13:43:16 -0700192 if (refsById == null) {
193 refsById = repo.getAllRefsByPeeledObjectId();
194 }
Dave Borowitz3b4baab2016-10-04 09:44:10 -0400195 Set<Ref> refs = refsById.get(id);
196 if (refs == null) {
197 return ImmutableList.of();
198 }
199 return refs.stream()
David Pursehouse14293fe2016-10-04 15:55:22 +0900200 .filter(r -> r.getName().startsWith(prefix))
Dave Borowitz3b4baab2016-10-04 09:44:10 -0400201 .sorted(comparing(Ref::getName))
202 .collect(toList());
Dave Borowitz01551272014-03-16 13:43:16 -0700203 }
204
Jonathan Niederb49306a2019-03-07 14:10:57 -0800205 private AbstractTreeIterator getTreeIterator(RevWalk walk, RevCommit commit)
206 throws IOException {
Dave Borowitz01551272014-03-16 13:43:16 -0700207 CanonicalTreeParser p = new CanonicalTreeParser();
208 p.reset(walk.getObjectReader(), walk.parseTree(walk.parseCommit(commit).getTree()));
209 return p;
210 }
211
Jonathan Niederb49306a2019-03-07 14:10:57 -0800212 private DiffList computeDiffEntries(
213 Repository repo, GitilesView view, RevWalk walk, RevCommit commit) throws IOException {
Dave Borowitz01551272014-03-16 13:43:16 -0700214 DiffList result = new DiffList();
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200215 result.revision =
216 view.getRevision().matches(commit)
217 ? view.getRevision()
218 : Revision.peeled(commit.name(), commit);
Dave Borowitz2298cef2014-09-23 10:29:46 -0700219
Dave Borowitz01551272014-03-16 13:43:16 -0700220 AbstractTreeIterator oldTree;
221 switch (commit.getParentCount()) {
222 case 0:
223 result.oldRevision = Revision.NULL;
224 oldTree = new EmptyTreeIterator();
225 break;
226 case 1:
Dave Borowitze360d5c2015-09-16 16:53:30 -0400227 result.oldRevision =
228 Revision.peeled(result.revision.getName() + "^", commit.getParent(0));
Jonathan Niederb49306a2019-03-07 14:10:57 -0800229 oldTree = getTreeIterator(walk, commit.getParent(0));
Dave Borowitz01551272014-03-16 13:43:16 -0700230 break;
231 default:
232 // TODO(dborowitz): handle merges
233 return result;
234 }
Jonathan Niederb49306a2019-03-07 14:10:57 -0800235 AbstractTreeIterator newTree = getTreeIterator(walk, commit);
Dave Borowitz01551272014-03-16 13:43:16 -0700236
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700237 try (DiffFormatter diff = new DiffFormatter(NullOutputStream.INSTANCE)) {
Dave Borowitz01551272014-03-16 13:43:16 -0700238 diff.setRepository(repo);
239 diff.setDetectRenames(true);
240 result.entries = diff.scan(oldTree, newTree);
241 return result;
Dave Borowitz01551272014-03-16 13:43:16 -0700242 }
243 }
244 }
245
246 ObjectId sha;
247 PersonIdent author;
248 PersonIdent committer;
249 AbbreviatedObjectId abbrev;
250 ObjectId tree;
251 List<RevCommit> parents;
252 String shortMessage;
253 String message;
254
255 List<Ref> branches;
256 List<Ref> tags;
257 DiffList diffEntries;
258
259 String url;
260 String logUrl;
261 String treeUrl;
262 String archiveUrl;
263 ArchiveFormat archiveType;
264}