blob: 76f52730d14ecf07d7857b0b3e7a4cb318b70ece [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;
19
20import com.google.common.base.Function;
21import com.google.common.base.Objects;
22import 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 {
83 Revision oldRevision;
84 List<DiffEntry> entries;
85 }
86
87 static class Builder {
88 private RevWalk walk;
89 private ArchiveFormat archiveFormat;
90 private Map<AnyObjectId, Set<Ref>> refsById;
91
92 Builder setRevWalk(@Nullable RevWalk walk) {
93 this.walk = walk;
94 return this;
95 }
96
97 Builder setArchiveFormat(@Nullable ArchiveFormat archiveFormat) {
98 this.archiveFormat = archiveFormat;
99 return this;
100 }
101
102 CommitData build(HttpServletRequest req, RevCommit c, Set<Field> fs)
103 throws IOException {
104 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)) {
121 ObjectReader reader = repo.getObjectDatabase().newReader();
122 try {
123 result.abbrev = reader.abbreviate(c);
124 } finally {
125 reader.release();
126 }
127 }
128 if (fs.contains(Field.URL)) {
129 result.url = GitilesView.revision()
130 .copyFrom(view)
131 .setRevision(c)
132 .toUrl();
133 }
134 if (fs.contains(Field.LOG_URL)) {
135 result.logUrl = urlFromView(view, c, GitilesView.log());
136 }
137 if (fs.contains(Field.ARCHIVE_URL)) {
138 result.archiveUrl = urlFromView(view, c,
139 GitilesView.archive().setExtension(archiveFormat.getDefaultSuffix()));
140 }
141 if (fs.contains(Field.ARCHIVE_TYPE)) {
142 result.archiveType = archiveFormat;
143 }
144 if (fs.contains(Field.TREE)) {
145 result.tree = c.getTree().copy();
146 }
147 if (fs.contains(Field.TREE_URL)) {
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700148 // Tree always implies the root tree.
Dave Borowitz01551272014-03-16 13:43:16 -0700149 result.treeUrl = GitilesView.path().copyFrom(view).setPathPart("/").toUrl();
150 }
151 if (fs.contains(Field.PARENTS)) {
152 result.parents = Arrays.asList(c.getParents());
153 }
154 if (fs.contains(Field.SHORT_MESSAGE)) {
155 result.shortMessage = c.getShortMessage();
156 }
157 if (fs.contains(Field.BRANCHES)) {
158 result.branches = getRefsById(repo, c, Constants.R_HEADS);
159 }
160 if (fs.contains(Field.TAGS)) {
161 result.tags = getRefsById(repo, c, Constants.R_TAGS);
162 }
163 if (fs.contains(Field.MESSAGE)) {
164 result.message = c.getFullMessage();
165 }
166 if (fs.contains(Field.DIFF_TREE)) {
167 result.diffEntries = computeDiffEntries(repo, view, c);
168 }
169
170 return result;
171 }
172
173 private void checkFields(Set<Field> fs) {
174 checkState(!fs.contains(Field.DIFF_TREE) || walk != null, "RevWalk required for diffTree");
175 if (fs.contains(Field.ARCHIVE_URL) || fs.contains(Field.ARCHIVE_TYPE)) {
176 checkState(archiveFormat != null, "archive format required");
177 }
178 }
179
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700180 private static String urlFromView(GitilesView view, RevCommit commit, GitilesView.Builder builder) {
Dave Borowitz01551272014-03-16 13:43:16 -0700181 Revision rev = view.getRevision();
182 return builder.copyFrom(view)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700183 .setOldRevision(Revision.NULL)
Dave Borowitz01551272014-03-16 13:43:16 -0700184 .setRevision(rev.getId().equals(commit) ? rev.getName() : commit.name(), commit)
Dave Borowitz359ad6d2014-04-21 16:07:59 -0700185 .setPathPart(view.getPathPart())
Dave Borowitz01551272014-03-16 13:43:16 -0700186 .toUrl();
187 }
188
189 private List<Ref> getRefsById(Repository repo, ObjectId id, final String prefix) {
190 if (refsById == null) {
191 refsById = repo.getAllRefsByPeeledObjectId();
192 }
193 return FluentIterable.from(Objects.firstNonNull(refsById.get(id), ImmutableSet.<Ref> of()))
194 .filter(new Predicate<Ref>() {
195 @Override
196 public boolean apply(Ref ref) {
197 return ref.getName().startsWith(prefix);
198 }
199 }).toSortedList(Ordering.natural().onResultOf(new Function<Ref, String>() {
200 @Override
201 public String apply(Ref ref) {
202 return ref.getName();
203 }
204 }));
205 }
206
207 private AbstractTreeIterator getTreeIterator(RevCommit commit) throws IOException {
208 CanonicalTreeParser p = new CanonicalTreeParser();
209 p.reset(walk.getObjectReader(), walk.parseTree(walk.parseCommit(commit).getTree()));
210 return p;
211 }
212
213 private DiffList computeDiffEntries(Repository repo, GitilesView view, RevCommit commit)
214 throws IOException {
215 DiffList result = new DiffList();
216 AbstractTreeIterator oldTree;
217 switch (commit.getParentCount()) {
218 case 0:
219 result.oldRevision = Revision.NULL;
220 oldTree = new EmptyTreeIterator();
221 break;
222 case 1:
223 result.oldRevision =
224 Revision.peeled(view.getRevision().getName() + "^", commit.getParent(0));
225 oldTree = getTreeIterator(commit.getParent(0));
226 break;
227 default:
228 // TODO(dborowitz): handle merges
229 return result;
230 }
231 AbstractTreeIterator newTree = getTreeIterator(commit);
232
233 DiffFormatter diff = new DiffFormatter(NullOutputStream.INSTANCE);
234 try {
235 diff.setRepository(repo);
236 diff.setDetectRenames(true);
237 result.entries = diff.scan(oldTree, newTree);
238 return result;
239 } finally {
240 diff.release();
241 }
242 }
243 }
244
245 ObjectId sha;
246 PersonIdent author;
247 PersonIdent committer;
248 AbbreviatedObjectId abbrev;
249 ObjectId tree;
250 List<RevCommit> parents;
251 String shortMessage;
252 String message;
253
254 List<Ref> branches;
255 List<Ref> tags;
256 DiffList diffEntries;
257
258 String url;
259 String logUrl;
260 String treeUrl;
261 String archiveUrl;
262 ArchiveFormat archiveType;
263}