blob: f457088595ec03a5e6a757a96bceede6f09fa8bc [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 {
58 static enum Field {
59 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
103 CommitData build(HttpServletRequest req, RevCommit c, Set<Field> fs)
104 throws IOException {
105 checkFields(fs);
106 checkNotNull(req, "request");
107 Repository repo = ServletUtils.getRepository(req);
108 GitilesView view = ViewFilter.getView(req);
109
110 CommitData result = new CommitData();
111
112 if (fs.contains(Field.AUTHOR)) {
113 result.author = c.getAuthorIdent();
114 }
115 if (fs.contains(Field.COMMITTER)) {
116 result.committer = c.getCommitterIdent();
117 }
118 if (fs.contains(Field.SHA)) {
119 result.sha = c.copy();
120 }
121 if (fs.contains(Field.ABBREV_SHA)) {
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700122 try (ObjectReader reader = repo.getObjectDatabase().newReader()) {
Dave Borowitz01551272014-03-16 13:43:16 -0700123 result.abbrev = reader.abbreviate(c);
Dave Borowitz01551272014-03-16 13:43:16 -0700124 }
125 }
126 if (fs.contains(Field.URL)) {
127 result.url = GitilesView.revision()
128 .copyFrom(view)
129 .setRevision(c)
130 .toUrl();
131 }
132 if (fs.contains(Field.LOG_URL)) {
133 result.logUrl = urlFromView(view, c, GitilesView.log());
134 }
135 if (fs.contains(Field.ARCHIVE_URL)) {
136 result.archiveUrl = urlFromView(view, c,
137 GitilesView.archive().setExtension(archiveFormat.getDefaultSuffix()));
138 }
139 if (fs.contains(Field.ARCHIVE_TYPE)) {
140 result.archiveType = archiveFormat;
141 }
142 if (fs.contains(Field.TREE)) {
143 result.tree = c.getTree().copy();
144 }
145 if (fs.contains(Field.TREE_URL)) {
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700146 // Tree always implies the root tree.
Dave Borowitz01551272014-03-16 13:43:16 -0700147 result.treeUrl = GitilesView.path().copyFrom(view).setPathPart("/").toUrl();
148 }
149 if (fs.contains(Field.PARENTS)) {
150 result.parents = Arrays.asList(c.getParents());
151 }
Dave Borowitz01551272014-03-16 13:43:16 -0700152 if (fs.contains(Field.BRANCHES)) {
153 result.branches = getRefsById(repo, c, Constants.R_HEADS);
154 }
155 if (fs.contains(Field.TAGS)) {
156 result.tags = getRefsById(repo, c, Constants.R_TAGS);
157 }
158 if (fs.contains(Field.MESSAGE)) {
159 result.message = c.getFullMessage();
160 }
Shawn Pearce680ab122015-05-11 23:15:06 -0700161 if (fs.contains(Field.SHORT_MESSAGE)) {
162 String msg = c.getShortMessage();
163 if (msg.length() > 80) {
164 String ft = result.message;
165 if (ft == null) {
166 ft = c.getFullMessage();
167 }
168 int lf = ft.indexOf('\n');
169 if (lf > 0) {
170 msg = ft.substring(0, lf);
171 }
172 }
173 result.shortMessage = msg;
174 }
Dave Borowitz01551272014-03-16 13:43:16 -0700175 if (fs.contains(Field.DIFF_TREE)) {
176 result.diffEntries = computeDiffEntries(repo, view, c);
177 }
178
179 return result;
180 }
181
182 private void checkFields(Set<Field> fs) {
183 checkState(!fs.contains(Field.DIFF_TREE) || walk != null, "RevWalk required for diffTree");
184 if (fs.contains(Field.ARCHIVE_URL) || fs.contains(Field.ARCHIVE_TYPE)) {
185 checkState(archiveFormat != null, "archive format required");
186 }
187 }
188
Dave Borowitze360d5c2015-09-16 16:53:30 -0400189 private static String urlFromView(GitilesView view, RevCommit commit,
190 GitilesView.Builder builder) {
Dave Borowitz01551272014-03-16 13:43:16 -0700191 Revision rev = view.getRevision();
192 return builder.copyFrom(view)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700193 .setOldRevision(Revision.NULL)
Dave Borowitz01551272014-03-16 13:43:16 -0700194 .setRevision(rev.getId().equals(commit) ? rev.getName() : commit.name(), commit)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700195 .setPathPart(view.getPathPart())
Dave Borowitz01551272014-03-16 13:43:16 -0700196 .toUrl();
197 }
198
199 private List<Ref> getRefsById(Repository repo, ObjectId id, final String prefix) {
200 if (refsById == null) {
201 refsById = repo.getAllRefsByPeeledObjectId();
202 }
Dave Borowitzc410f962014-09-23 10:49:26 -0700203 return FluentIterable.from(firstNonNull(refsById.get(id), ImmutableSet.<Ref> of()))
Dave Borowitz01551272014-03-16 13:43:16 -0700204 .filter(new Predicate<Ref>() {
205 @Override
206 public boolean apply(Ref ref) {
207 return ref.getName().startsWith(prefix);
208 }
209 }).toSortedList(Ordering.natural().onResultOf(new Function<Ref, String>() {
210 @Override
211 public String apply(Ref ref) {
212 return ref.getName();
213 }
214 }));
215 }
216
217 private AbstractTreeIterator getTreeIterator(RevCommit commit) throws IOException {
218 CanonicalTreeParser p = new CanonicalTreeParser();
219 p.reset(walk.getObjectReader(), walk.parseTree(walk.parseCommit(commit).getTree()));
220 return p;
221 }
222
223 private DiffList computeDiffEntries(Repository repo, GitilesView view, RevCommit commit)
224 throws IOException {
225 DiffList result = new DiffList();
Dave Borowitz2298cef2014-09-23 10:29:46 -0700226 result.revision = view.getRevision().matches(commit)
227 ? view.getRevision()
228 : Revision.peeled(commit.name(), commit);
229
Dave Borowitz01551272014-03-16 13:43:16 -0700230 AbstractTreeIterator oldTree;
231 switch (commit.getParentCount()) {
232 case 0:
233 result.oldRevision = Revision.NULL;
234 oldTree = new EmptyTreeIterator();
235 break;
236 case 1:
Dave Borowitze360d5c2015-09-16 16:53:30 -0400237 result.oldRevision =
238 Revision.peeled(result.revision.getName() + "^", commit.getParent(0));
Dave Borowitz01551272014-03-16 13:43:16 -0700239 oldTree = getTreeIterator(commit.getParent(0));
240 break;
241 default:
242 // TODO(dborowitz): handle merges
243 return result;
244 }
245 AbstractTreeIterator newTree = getTreeIterator(commit);
246
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700247 try (DiffFormatter diff = new DiffFormatter(NullOutputStream.INSTANCE)) {
Dave Borowitz01551272014-03-16 13:43:16 -0700248 diff.setRepository(repo);
249 diff.setDetectRenames(true);
250 result.entries = diff.scan(oldTree, newTree);
251 return result;
Dave Borowitz01551272014-03-16 13:43:16 -0700252 }
253 }
254 }
255
256 ObjectId sha;
257 PersonIdent author;
258 PersonIdent committer;
259 AbbreviatedObjectId abbrev;
260 ObjectId tree;
261 List<RevCommit> parents;
262 String shortMessage;
263 String message;
264
265 List<Ref> branches;
266 List<Ref> tags;
267 DiffList diffEntries;
268
269 String url;
270 String logUrl;
271 String treeUrl;
272 String archiveUrl;
273 ArchiveFormat archiveType;
274}