blob: 0df7323109d920ec216e6282fcad106ebd31dcbb [file] [log] [blame]
Dave Borowitz209d0aa2012-12-28 14:28:53 -08001// 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
David Pursehouse55a49f42018-07-03 14:10:11 +090017import static com.google.common.base.Preconditions.checkArgument;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080018import static com.google.common.base.Preconditions.checkNotNull;
19
David Pursehouse55a49f42018-07-03 14:10:11 +090020import com.google.common.collect.ImmutableList;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080021import com.google.common.collect.ImmutableMap;
22import com.google.common.collect.Lists;
Dave Borowitze5fead02013-01-07 13:12:59 -080023import com.google.common.collect.Maps;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080024import com.google.common.collect.Ordering;
Dave Borowitz1d94e652014-07-30 12:45:09 -070025import com.google.common.primitives.Ints;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080026import com.google.common.util.concurrent.UncheckedExecutionException;
Masaya Suzuki5cecb862019-03-25 17:35:44 -070027import com.google.gitiles.GitilesRequestFailureException.FailureReason;
Kevin Graney77dbea92014-07-08 14:07:40 -040028import com.google.gson.reflect.TypeToken;
Dave Borowitz3b744b12016-08-19 16:11:10 -040029import java.io.IOException;
30import java.io.Writer;
31import java.util.Collection;
32import java.util.LinkedHashMap;
33import java.util.List;
34import java.util.Map;
35import javax.annotation.Nullable;
36import javax.servlet.http.HttpServletRequest;
37import javax.servlet.http.HttpServletResponse;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080038import org.eclipse.jgit.http.server.ServletUtils;
Dave Borowitzd0b7e182013-01-11 15:55:09 -080039import org.eclipse.jgit.lib.AnyObjectId;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080040import org.eclipse.jgit.lib.Constants;
41import org.eclipse.jgit.lib.Ref;
42import org.eclipse.jgit.lib.RefComparator;
43import org.eclipse.jgit.lib.RefDatabase;
44import org.eclipse.jgit.revwalk.RevWalk;
Dave Borowitzd0b7e182013-01-11 15:55:09 -080045import org.eclipse.jgit.transport.RefAdvertiser;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080046
Dave Borowitz209d0aa2012-12-28 14:28:53 -080047/** Serves an HTML page with all the refs in a repository. */
48public class RefServlet extends BaseServlet {
49 private static final long serialVersionUID = 1L;
50
51 private final TimeCache timeCache;
52
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020053 protected RefServlet(
54 GitilesAccess.Factory accessFactory, Renderer renderer, TimeCache timeCache) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -070055 super(renderer, accessFactory);
Dave Borowitz209d0aa2012-12-28 14:28:53 -080056 this.timeCache = checkNotNull(timeCache, "timeCache");
57 }
58
59 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020060 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -070061 if (!ViewFilter.getView(req).getPathPart().isEmpty()) {
Masaya Suzuki5cecb862019-03-25 17:35:44 -070062 throw new GitilesRequestFailureException(FailureReason.INCORECT_PARAMETER);
Dave Borowitzd0b7e182013-01-11 15:55:09 -080063 }
Dave Borowitze5fead02013-01-07 13:12:59 -080064 List<Map<String, Object>> tags;
Shawn Pearceb5ad0a02015-05-24 20:33:17 -070065 try (RevWalk walk = new RevWalk(ServletUtils.getRepository(req))) {
Dave Borowitzd0b7e182013-01-11 15:55:09 -080066 tags = getTagsSoyData(req, timeCache, walk, 0);
Dave Borowitz209d0aa2012-12-28 14:28:53 -080067 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020068 renderHtml(
69 req,
70 res,
Sven Selbergd99004c2022-01-31 10:24:08 +010071 "com.google.gitiles.templates.RefList.refsDetail",
Dave Borowitzd0b7e182013-01-11 15:55:09 -080072 ImmutableMap.of("branches", getBranchesSoyData(req, 0), "tags", tags));
Dave Borowitz209d0aa2012-12-28 14:28:53 -080073 }
74
Dave Borowitzd0b7e182013-01-11 15:55:09 -080075 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020076 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitzd0b7e182013-01-11 15:55:09 -080077 GitilesView view = ViewFilter.getView(req);
David Pursehouse55a49f42018-07-03 14:10:11 +090078 RefsResult refs = getRefs(ServletUtils.getRepository(req).getRefDatabase(), view.getPathPart());
Dave Borowitzd0b7e182013-01-11 15:55:09 -080079 TextRefAdvertiser adv = new TextRefAdvertiser(startRenderText(req, res));
80 adv.setDerefTags(true);
David Pursehouse55a49f42018-07-03 14:10:11 +090081 adv.send(refs.refs);
Dave Borowitzd0b7e182013-01-11 15:55:09 -080082 adv.end();
83 }
84
Kevin Graney77dbea92014-07-08 14:07:40 -040085 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020086 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
Kevin Graney77dbea92014-07-08 14:07:40 -040087 GitilesView view = ViewFilter.getView(req);
David Pursehouse55a49f42018-07-03 14:10:11 +090088 RefsResult refs = getRefs(ServletUtils.getRepository(req).getRefDatabase(), view.getPathPart());
Dave Borowitz96a6f472014-11-04 16:38:20 -080089 Map<String, RefJsonData> jsonRefs = new LinkedHashMap<>();
David Pursehouse55a49f42018-07-03 14:10:11 +090090 int prefixLen = refs.prefix.length();
91 for (Ref ref : refs.refs) {
92 jsonRefs.put(ref.getName().substring(prefixLen), new RefJsonData(ref));
Kevin Graney77dbea92014-07-08 14:07:40 -040093 }
94 renderJson(req, res, jsonRefs, new TypeToken<Map<String, RefJsonData>>() {}.getType());
95 }
96
Dave Borowitzd0b7e182013-01-11 15:55:09 -080097 static List<Map<String, Object>> getBranchesSoyData(HttpServletRequest req, int limit)
Dave Borowitz209d0aa2012-12-28 14:28:53 -080098 throws IOException {
Dave Borowitze5fead02013-01-07 13:12:59 -080099 RefDatabase refdb = ServletUtils.getRepository(req).getRefDatabase();
Jonathan Niedercbe013f2016-05-26 15:28:12 -0700100 Ref head = refdb.exactRef(Constants.HEAD);
Dave Borowitze5fead02013-01-07 13:12:59 -0800101 Ref headLeaf = head != null && head.isSymbolic() ? head.getLeaf() : null;
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800102 return getRefsSoyData(
Dave Borowitze5fead02013-01-07 13:12:59 -0800103 refdb,
104 ViewFilter.getView(req),
105 Constants.R_HEADS,
106 branchComparator(headLeaf),
107 headLeaf,
108 limit);
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800109 }
110
Dave Borowitze5fead02013-01-07 13:12:59 -0800111 private static Ordering<Ref> branchComparator(Ref headLeaf) {
112 if (headLeaf == null) {
113 return Ordering.from(RefComparator.INSTANCE);
114 }
115 final String headLeafName = headLeaf.getName();
116 return new Ordering<Ref>() {
117 @Override
118 public int compare(@Nullable Ref left, @Nullable Ref right) {
119 int l = isHead(left) ? 1 : 0;
120 int r = isHead(right) ? 1 : 0;
Dave Borowitz5d11e2d2013-01-08 10:03:58 -0800121 return r - l;
Dave Borowitze5fead02013-01-07 13:12:59 -0800122 }
123
David Pursehousee3d3ec82016-06-15 22:10:48 +0900124 private boolean isHead(Ref ref) {
Dave Borowitze5fead02013-01-07 13:12:59 -0800125 return ref != null && ref.getName().equals(headLeafName);
126 }
127 }.compound(RefComparator.INSTANCE);
128 }
129
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200130 static List<Map<String, Object>> getTagsSoyData(
131 HttpServletRequest req, TimeCache timeCache, RevWalk walk, int limit) throws IOException {
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800132 return getRefsSoyData(
Dave Borowitze5fead02013-01-07 13:12:59 -0800133 ServletUtils.getRepository(req).getRefDatabase(),
134 ViewFilter.getView(req),
135 Constants.R_TAGS,
136 tagComparator(timeCache, walk),
137 null,
138 limit);
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800139 }
140
David Pursehousedcde0af2016-10-04 15:24:59 +0900141 private static Long getTime(RevWalk walk, TimeCache timeCache, Ref ref) {
142 try {
143 return timeCache.getTime(walk, ref.getObjectId());
144 } catch (IOException e) {
145 throw new UncheckedExecutionException(e);
146 }
147 }
148
149 private static Ordering<Ref> tagComparator(TimeCache timeCache, RevWalk walk) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200150 return Ordering.natural()
David Pursehousedcde0af2016-10-04 15:24:59 +0900151 .onResultOf((Ref r) -> getTime(walk, timeCache, r))
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200152 .reverse()
153 .compound(RefComparator.INSTANCE);
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800154 }
155
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800156 private static List<Map<String, Object>> getRefsSoyData(
Dave Borowitze5fead02013-01-07 13:12:59 -0800157 RefDatabase refdb,
158 GitilesView view,
159 String prefix,
160 Ordering<Ref> ordering,
161 @Nullable Ref headLeaf,
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200162 int limit)
163 throws IOException {
David Pursehouse55a49f42018-07-03 14:10:11 +0900164 checkArgument(prefix.endsWith("/"), "ref hierarchy prefix should end with /: %s", prefix);
165 Collection<Ref> refs = refdb.getRefsByPrefix(prefix);
Dave Borowitz1d94e652014-07-30 12:45:09 -0700166 refs = ordering.leastOf(refs, limit > 0 ? Ints.saturatedCast(limit + 1L) : refs.size());
Dave Borowitze5fead02013-01-07 13:12:59 -0800167 List<Map<String, Object>> result = Lists.newArrayListWithCapacity(refs.size());
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800168
169 for (Ref ref : refs) {
170 String name = ref.getName().substring(prefix.length());
Ivan Frade92b84722019-02-12 14:03:10 -0800171 Map<String, Object> value = Maps.newHashMapWithExpectedSize(3);
172 value.put(
173 "url",
174 GitilesView.revision()
175 .copyFrom(view)
176 .setRevision(Revision.unpeeled(ref.getName(), ref.getObjectId()))
177 .toUrl());
178 value.put("name", name);
179 if (headLeaf != null) {
180 value.put("isHead", headLeaf.equals(ref));
Dave Borowitze5fead02013-01-07 13:12:59 -0800181 }
Ivan Frade92b84722019-02-12 14:03:10 -0800182 result.add(value);
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800183 }
Ivan Frade92b84722019-02-12 14:03:10 -0800184
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800185 return result;
186 }
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800187
Dave Borowitzba9c1182013-03-13 14:16:43 -0700188 static String sanitizeRefForText(String refName) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200189 return refName.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800190 }
191
David Pursehouse55a49f42018-07-03 14:10:11 +0900192 private static class RefsResult {
193 String prefix;
194 List<Ref> refs;
195
196 RefsResult(String prefix, List<Ref> refs) {
197 this.prefix = prefix;
198 this.refs = refs;
199 }
200 }
201
202 private static RefsResult getRefs(RefDatabase refdb, String path) throws IOException {
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800203 path = GitilesView.maybeTrimLeadingAndTrailingSlash(path);
204 if (path.isEmpty()) {
David Pursehouse55a49f42018-07-03 14:10:11 +0900205 return new RefsResult(path, refdb.getRefs());
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800206 }
207 path = Constants.R_REFS + path;
Jonathan Niedercbe013f2016-05-26 15:28:12 -0700208 Ref singleRef = refdb.exactRef(path);
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800209 if (singleRef != null) {
Alice Kober-Sotzek53794aa2018-11-07 14:29:47 +0100210 return new RefsResult("", ImmutableList.of(singleRef));
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800211 }
David Pursehouse55a49f42018-07-03 14:10:11 +0900212 path = path + '/';
213 return new RefsResult(path, refdb.getRefsByPrefix(path));
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800214 }
215
216 private static class TextRefAdvertiser extends RefAdvertiser {
Dave Borowitz673d1982014-05-02 12:30:49 -0700217 private final Writer writer;
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800218
Dave Borowitz673d1982014-05-02 12:30:49 -0700219 private TextRefAdvertiser(Writer writer) {
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800220 this.writer = writer;
221 }
222
223 @Override
224 public void advertiseId(AnyObjectId id, String refName) throws IOException {
225 super.advertiseId(id, sanitizeRefForText(refName));
226 }
227
228 @Override
229 protected void writeOne(CharSequence line) throws IOException {
Dave Borowitz673d1982014-05-02 12:30:49 -0700230 writer.append(line);
Dave Borowitzd0b7e182013-01-11 15:55:09 -0800231 }
232
233 @Override
234 public void end() throws IOException {
235 writer.close();
236 }
237 }
Kevin Graney77dbea92014-07-08 14:07:40 -0400238
Dave Borowitz32ec5b92014-07-30 07:43:28 -0700239 static class RefJsonData {
240 RefJsonData(Ref ref) {
Kevin Graney77dbea92014-07-08 14:07:40 -0400241 value = ref.getObjectId().getName();
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200242 if (ref.getPeeledObjectId() != null) {
Kevin Graney77dbea92014-07-08 14:07:40 -0400243 peeled = ref.getPeeledObjectId().getName();
244 }
245 if (ref.isSymbolic()) {
246 target = ref.getTarget().getName();
247 }
248 }
249
Dave Borowitz32ec5b92014-07-30 07:43:28 -0700250 String value;
251 String peeled;
252 String target;
Kevin Graney77dbea92014-07-08 14:07:40 -0400253 }
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800254}