blob: 031b667bfb1186664d68009a800acfa62f952ac2 [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
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.checkArgument;
18import static com.google.common.base.Preconditions.checkNotNull;
19import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
20import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
21
22import com.google.common.base.Optional;
23import com.google.common.base.Strings;
24import com.google.common.collect.Iterables;
25import com.google.common.collect.ListMultimap;
26import com.google.common.collect.Lists;
27import com.google.common.collect.Maps;
28import com.google.gitiles.CommitSoyData.KeySet;
29
30import org.eclipse.jgit.errors.IncorrectObjectTypeException;
31import org.eclipse.jgit.errors.MissingObjectException;
32import org.eclipse.jgit.errors.RevWalkException;
33import org.eclipse.jgit.http.server.ServletUtils;
34import org.eclipse.jgit.lib.AbbreviatedObjectId;
35import org.eclipse.jgit.lib.AnyObjectId;
36import org.eclipse.jgit.lib.ObjectId;
37import org.eclipse.jgit.lib.ObjectReader;
38import org.eclipse.jgit.lib.Ref;
39import org.eclipse.jgit.lib.Repository;
40import org.eclipse.jgit.revwalk.FollowFilter;
41import org.eclipse.jgit.revwalk.RevCommit;
42import org.eclipse.jgit.revwalk.RevObject;
43import org.eclipse.jgit.revwalk.RevTag;
44import org.eclipse.jgit.revwalk.RevWalk;
45import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
48import java.io.IOException;
49import java.util.Collection;
50import java.util.List;
51import java.util.Map;
52import java.util.Set;
53
54import javax.servlet.http.HttpServletRequest;
55import javax.servlet.http.HttpServletResponse;
56
57/** Serves an HTML page with a shortlog for commits and paths. */
58public class LogServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080059 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070060 private static final Logger log = LoggerFactory.getLogger(LogServlet.class);
61
62 private static final String START_PARAM = "s";
63
64 private final Linkifier linkifier;
65 private final int limit;
66
67 public LogServlet(Renderer renderer, Linkifier linkifier) {
68 this(renderer, linkifier, 100);
69 }
70
71 private LogServlet(Renderer renderer, Linkifier linkifier, int limit) {
72 super(renderer);
73 this.linkifier = checkNotNull(linkifier, "linkifier");
74 checkArgument(limit >= 0, "limit must be positive: %s", limit);
75 this.limit = limit;
76 }
77
78 @Override
79 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
80 GitilesView view = ViewFilter.getView(req);
81 Repository repo = ServletUtils.getRepository(req);
82 RevWalk walk = null;
83 try {
84 try {
85 walk = newWalk(repo, view);
86 } catch (IncorrectObjectTypeException e) {
87 res.setStatus(SC_NOT_FOUND);
88 return;
89 }
90
91 Optional<ObjectId> start = getStart(view.getParameters(), walk.getObjectReader());
92 if (start == null) {
93 res.setStatus(SC_NOT_FOUND);
94 return;
95 }
96
97 Map<String, Object> data = Maps.newHashMapWithExpectedSize(5);
98
99 if (!view.getRevision().nameIsId()) {
100 List<Map<String, Object>> tags = Lists.newArrayListWithExpectedSize(1);
101 for (RevObject o : RevisionServlet.listObjects(walk, view.getRevision().getId())) {
102 if (o instanceof RevTag) {
103 tags.add(new TagSoyData(linkifier, req).toSoyData((RevTag) o));
104 }
105 }
106 if (!tags.isEmpty()) {
107 data.put("tags", tags);
108 }
109 }
110
111 Paginator paginator = new Paginator(walk, limit, start.orNull());
112 Map<AnyObjectId, Set<Ref>> refsById = repo.getAllRefsByPeeledObjectId();
113 List<Map<String, Object>> entries = Lists.newArrayListWithCapacity(limit);
114 for (RevCommit c : paginator) {
115 entries.add(new CommitSoyData(null, req, repo, walk, view, refsById)
116 .toSoyData(c, KeySet.SHORTLOG));
117 }
118
119 String title = "Log - ";
120 if (view.getOldRevision() != Revision.NULL) {
121 title += view.getRevisionRange();
122 } else {
123 title += view.getRevision().getName();
124 }
125
126 data.put("title", title);
127 data.put("entries", entries);
128 ObjectId next = paginator.getNextStart();
129 if (next != null) {
130 data.put("nextUrl", copyAndCanonicalize(view)
131 .replaceParam(START_PARAM, next.name())
132 .toUrl());
133 }
134 ObjectId prev = paginator.getPreviousStart();
135 if (prev != null) {
136 GitilesView.Builder prevView = copyAndCanonicalize(view);
137 if (!prevView.getRevision().getId().equals(prev)) {
138 prevView.replaceParam(START_PARAM, prev.name());
139 }
140 data.put("previousUrl", prevView.toUrl());
141 }
142
143 render(req, res, "gitiles.logDetail", data);
144 } catch (RevWalkException e) {
145 log.warn("Error in rev walk", e);
146 res.setStatus(SC_INTERNAL_SERVER_ERROR);
147 return;
148 } finally {
149 if (walk != null) {
150 walk.release();
151 }
152 }
153 }
154
155 private static GitilesView.Builder copyAndCanonicalize(GitilesView view) {
156 // Canonicalize the view by using full SHAs.
157 GitilesView.Builder copy = GitilesView.log().copyFrom(view)
158 .setRevision(view.getRevision());
159 if (view.getOldRevision() != Revision.NULL) {
160 copy.setOldRevision(view.getOldRevision());
161 }
162 return copy;
163 }
164
165 private static Optional<ObjectId> getStart(ListMultimap<String, String> params,
166 ObjectReader reader) throws IOException {
167 List<String> values = params.get(START_PARAM);
168 switch (values.size()) {
169 case 0:
170 return Optional.absent();
171 case 1:
172 Collection<ObjectId> ids = reader.resolve(AbbreviatedObjectId.fromString(values.get(0)));
173 if (ids.size() != 1) {
174 return null;
175 }
176 return Optional.of(Iterables.getOnlyElement(ids));
177 default:
178 return null;
179 }
180 }
181
182 private static RevWalk newWalk(Repository repo, GitilesView view)
183 throws MissingObjectException, IncorrectObjectTypeException, IOException {
184 RevWalk walk = new RevWalk(repo);
185 walk.markStart(walk.parseCommit(view.getRevision().getId()));
186 if (view.getOldRevision() != Revision.NULL) {
187 walk.markUninteresting(walk.parseCommit(view.getOldRevision().getId()));
188 }
189 if (!Strings.isNullOrEmpty(view.getTreePath())) {
190 walk.setTreeFilter(FollowFilter.create(view.getTreePath()));
191 }
192 return walk;
193 }
194}