blob: 429acaea2a2aab6286d32d0a160be59c8a9b7fc4 [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
Dave Borowitzc410f962014-09-23 10:49:26 -070017import static com.google.common.base.MoreObjects.firstNonNull;
Dave Borowitz01551272014-03-16 13:43:16 -070018import static com.google.common.base.Preconditions.checkNotNull;
19import static com.google.common.base.Preconditions.checkState;
20
21import com.google.common.base.Function;
Dave Borowitz01551272014-03-16 13:43:16 -070022import com.google.common.base.Predicate;
23import com.google.common.collect.FluentIterable;
24import com.google.common.collect.ImmutableSet;
Dave Borowitz90d1db92014-03-16 14:16:15 -070025import com.google.common.collect.Iterables;
Dave Borowitz01551272014-03-16 13:43:16 -070026import com.google.common.collect.Ordering;
Dave Borowitz90d1db92014-03-16 14:16:15 -070027import com.google.common.collect.Sets;
Dave Borowitz01551272014-03-16 13:43:16 -070028
29import org.eclipse.jgit.diff.DiffEntry;
30import org.eclipse.jgit.diff.DiffFormatter;
31import org.eclipse.jgit.http.server.ServletUtils;
32import org.eclipse.jgit.lib.AbbreviatedObjectId;
33import org.eclipse.jgit.lib.AnyObjectId;
34import org.eclipse.jgit.lib.Constants;
35import org.eclipse.jgit.lib.ObjectId;
36import org.eclipse.jgit.lib.ObjectReader;
37import org.eclipse.jgit.lib.PersonIdent;
38import org.eclipse.jgit.lib.Ref;
39import org.eclipse.jgit.lib.Repository;
40import org.eclipse.jgit.revwalk.RevCommit;
41import org.eclipse.jgit.revwalk.RevWalk;
42import org.eclipse.jgit.treewalk.AbstractTreeIterator;
43import org.eclipse.jgit.treewalk.CanonicalTreeParser;
44import org.eclipse.jgit.treewalk.EmptyTreeIterator;
45import org.eclipse.jgit.util.io.NullOutputStream;
46
47import java.io.IOException;
48import java.util.Arrays;
49import java.util.List;
50import java.util.Map;
51import java.util.Set;
52
53import javax.annotation.Nullable;
54import javax.servlet.http.HttpServletRequest;
55
56/** Format-independent data about a single commit. */
57class CommitData {
David Pursehousee3d3ec82016-06-15 22:10:48 +090058 enum Field {
Dave Borowitz01551272014-03-16 13:43:16 -070059 ABBREV_SHA,
60 ARCHIVE_TYPE,
61 ARCHIVE_URL,
62 AUTHOR,
63 BRANCHES,
64 COMMITTER,
65 DIFF_TREE,
66 LOG_URL,
67 MESSAGE,
68 PARENTS,
Dave Borowitzf03fcea2014-04-21 17:20:33 -070069 PARENT_BLAME_URL,
Dave Borowitz01551272014-03-16 13:43:16 -070070 SHA,
71 SHORT_MESSAGE,
72 TAGS,
73 TREE,
74 TREE_URL,
75 URL;
Dave Borowitz90d1db92014-03-16 14:16:15 -070076
77 static ImmutableSet<Field> setOf(Iterable<Field> base, Field... fields) {
78 return Sets.immutableEnumSet(Iterables.concat(base, Arrays.asList(fields)));
79 }
Dave Borowitz01551272014-03-16 13:43:16 -070080 }
81
82 static class DiffList {
Dave Borowitz2298cef2014-09-23 10:29:46 -070083 Revision revision;
Dave Borowitz01551272014-03-16 13:43:16 -070084 Revision oldRevision;
85 List<DiffEntry> entries;
86 }
87
88 static class Builder {
89 private RevWalk walk;
90 private ArchiveFormat archiveFormat;
91 private Map<AnyObjectId, Set<Ref>> refsById;
92
93 Builder setRevWalk(@Nullable RevWalk walk) {
94 this.walk = walk;
95 return this;
96 }
97
98 Builder setArchiveFormat(@Nullable ArchiveFormat archiveFormat) {
99 this.archiveFormat = archiveFormat;
100 return this;
101 }
102
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200103 CommitData build(HttpServletRequest req, RevCommit c, Set<Field> fs) throws IOException {
Dave Borowitz01551272014-03-16 13:43:16 -0700104 checkFields(fs);
105 checkNotNull(req, "request");
106 Repository repo = ServletUtils.getRepository(req);
107 GitilesView view = ViewFilter.getView(req);
108
109 CommitData result = new CommitData();
110
111 if (fs.contains(Field.AUTHOR)) {
112 result.author = c.getAuthorIdent();
113 }
114 if (fs.contains(Field.COMMITTER)) {
115 result.committer = c.getCommitterIdent();
116 }
117 if (fs.contains(Field.SHA)) {
118 result.sha = c.copy();
119 }
120 if (fs.contains(Field.ABBREV_SHA)) {
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700121 try (ObjectReader reader = repo.getObjectDatabase().newReader()) {
Dave Borowitz01551272014-03-16 13:43:16 -0700122 result.abbrev = reader.abbreviate(c);
Dave Borowitz01551272014-03-16 13:43:16 -0700123 }
124 }
125 if (fs.contains(Field.URL)) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200126 result.url = GitilesView.revision().copyFrom(view).setRevision(c).toUrl();
Dave Borowitz01551272014-03-16 13:43:16 -0700127 }
128 if (fs.contains(Field.LOG_URL)) {
129 result.logUrl = urlFromView(view, c, GitilesView.log());
130 }
131 if (fs.contains(Field.ARCHIVE_URL)) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200132 result.archiveUrl =
133 urlFromView(
134 view, c, GitilesView.archive().setExtension(archiveFormat.getDefaultSuffix()));
Dave Borowitz01551272014-03-16 13:43:16 -0700135 }
136 if (fs.contains(Field.ARCHIVE_TYPE)) {
137 result.archiveType = archiveFormat;
138 }
139 if (fs.contains(Field.TREE)) {
140 result.tree = c.getTree().copy();
141 }
142 if (fs.contains(Field.TREE_URL)) {
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700143 // Tree always implies the root tree.
Dave Borowitz01551272014-03-16 13:43:16 -0700144 result.treeUrl = GitilesView.path().copyFrom(view).setPathPart("/").toUrl();
145 }
146 if (fs.contains(Field.PARENTS)) {
147 result.parents = Arrays.asList(c.getParents());
148 }
Dave Borowitz01551272014-03-16 13:43:16 -0700149 if (fs.contains(Field.BRANCHES)) {
150 result.branches = getRefsById(repo, c, Constants.R_HEADS);
151 }
152 if (fs.contains(Field.TAGS)) {
153 result.tags = getRefsById(repo, c, Constants.R_TAGS);
154 }
155 if (fs.contains(Field.MESSAGE)) {
156 result.message = c.getFullMessage();
157 }
Shawn Pearce680ab122015-05-11 23:15:06 -0700158 if (fs.contains(Field.SHORT_MESSAGE)) {
159 String msg = c.getShortMessage();
160 if (msg.length() > 80) {
161 String ft = result.message;
162 if (ft == null) {
163 ft = c.getFullMessage();
164 }
165 int lf = ft.indexOf('\n');
166 if (lf > 0) {
167 msg = ft.substring(0, lf);
168 }
169 }
170 result.shortMessage = msg;
171 }
Dave Borowitz01551272014-03-16 13:43:16 -0700172 if (fs.contains(Field.DIFF_TREE)) {
173 result.diffEntries = computeDiffEntries(repo, view, c);
174 }
175
176 return result;
177 }
178
179 private void checkFields(Set<Field> fs) {
180 checkState(!fs.contains(Field.DIFF_TREE) || walk != null, "RevWalk required for diffTree");
181 if (fs.contains(Field.ARCHIVE_URL) || fs.contains(Field.ARCHIVE_TYPE)) {
182 checkState(archiveFormat != null, "archive format required");
183 }
184 }
185
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200186 private static String urlFromView(
187 GitilesView view, RevCommit commit, GitilesView.Builder builder) {
Dave Borowitz01551272014-03-16 13:43:16 -0700188 Revision rev = view.getRevision();
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200189 return builder
190 .copyFrom(view)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700191 .setOldRevision(Revision.NULL)
Dave Borowitz01551272014-03-16 13:43:16 -0700192 .setRevision(rev.getId().equals(commit) ? rev.getName() : commit.name(), commit)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700193 .setPathPart(view.getPathPart())
Dave Borowitz01551272014-03-16 13:43:16 -0700194 .toUrl();
195 }
196
197 private List<Ref> getRefsById(Repository repo, ObjectId id, final String prefix) {
198 if (refsById == null) {
199 refsById = repo.getAllRefsByPeeledObjectId();
200 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200201 return FluentIterable.from(firstNonNull(refsById.get(id), ImmutableSet.<Ref>of()))
202 .filter(
203 new Predicate<Ref>() {
204 @Override
205 public boolean apply(Ref ref) {
206 return ref.getName().startsWith(prefix);
207 }
208 })
209 .toSortedList(
210 Ordering.natural()
211 .onResultOf(
212 new Function<Ref, String>() {
213 @Override
214 public String apply(Ref ref) {
215 return ref.getName();
216 }
217 }));
Dave Borowitz01551272014-03-16 13:43:16 -0700218 }
219
220 private AbstractTreeIterator getTreeIterator(RevCommit commit) throws IOException {
221 CanonicalTreeParser p = new CanonicalTreeParser();
222 p.reset(walk.getObjectReader(), walk.parseTree(walk.parseCommit(commit).getTree()));
223 return p;
224 }
225
226 private DiffList computeDiffEntries(Repository repo, GitilesView view, RevCommit commit)
227 throws IOException {
228 DiffList result = new DiffList();
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200229 result.revision =
230 view.getRevision().matches(commit)
231 ? view.getRevision()
232 : Revision.peeled(commit.name(), commit);
Dave Borowitz2298cef2014-09-23 10:29:46 -0700233
Dave Borowitz01551272014-03-16 13:43:16 -0700234 AbstractTreeIterator oldTree;
235 switch (commit.getParentCount()) {
236 case 0:
237 result.oldRevision = Revision.NULL;
238 oldTree = new EmptyTreeIterator();
239 break;
240 case 1:
Dave Borowitze360d5c2015-09-16 16:53:30 -0400241 result.oldRevision =
242 Revision.peeled(result.revision.getName() + "^", commit.getParent(0));
Dave Borowitz01551272014-03-16 13:43:16 -0700243 oldTree = getTreeIterator(commit.getParent(0));
244 break;
245 default:
246 // TODO(dborowitz): handle merges
247 return result;
248 }
249 AbstractTreeIterator newTree = getTreeIterator(commit);
250
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700251 try (DiffFormatter diff = new DiffFormatter(NullOutputStream.INSTANCE)) {
Dave Borowitz01551272014-03-16 13:43:16 -0700252 diff.setRepository(repo);
253 diff.setDetectRenames(true);
254 result.entries = diff.scan(oldTree, newTree);
255 return result;
Dave Borowitz01551272014-03-16 13:43:16 -0700256 }
257 }
258 }
259
260 ObjectId sha;
261 PersonIdent author;
262 PersonIdent committer;
263 AbbreviatedObjectId abbrev;
264 ObjectId tree;
265 List<RevCommit> parents;
266 String shortMessage;
267 String message;
268
269 List<Ref> branches;
270 List<Ref> tags;
271 DiffList diffEntries;
272
273 String url;
274 String logUrl;
275 String treeUrl;
276 String archiveUrl;
277 ArchiveFormat archiveType;
278}