blob: 7d7ac36dfc15f31f6e07f55e7fa1df67de3d286a [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.checkNotNull;
Shawn Pearce10e68e62016-01-02 09:37:58 -080018import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
Dave Borowitz9de65952012-08-13 16:09:45 -070019
Dave Borowitz9de65952012-08-13 16:09:45 -070020import com.google.common.base.Strings;
Dave Borowitz5db0eed2012-12-28 15:08:39 -080021import com.google.common.collect.ImmutableList;
Shawn Pearce45e83752015-02-20 17:59:05 -080022import com.google.common.collect.ImmutableMap;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080023import com.google.common.collect.Maps;
Dave Borowitz2b2f34b2014-04-29 16:47:20 -070024import com.google.gitiles.DateFormatter.Format;
Shawn Pearce47fd6562016-05-28 14:15:15 -070025import com.google.gitiles.doc.MarkdownConfig;
Kevin Graney77dbea92014-07-08 14:07:40 -040026import com.google.gson.reflect.TypeToken;
Shawn Pearce08e38f22016-05-24 16:40:18 -070027import com.google.template.soy.data.SanitizedContent;
Dave Borowitz3b744b12016-08-19 16:11:10 -040028import java.io.IOException;
29import java.io.OutputStream;
30import java.io.Writer;
31import java.util.List;
32import java.util.Map;
David Pursehouse7a7f5472016-10-14 09:59:20 +090033import java.util.Optional;
Dave Borowitz3b744b12016-08-19 16:11:10 -040034import javax.servlet.http.HttpServletRequest;
35import javax.servlet.http.HttpServletResponse;
Shawn Pearce45e83752015-02-20 17:59:05 -080036import org.eclipse.jgit.errors.IncorrectObjectTypeException;
Dave Borowitz9de65952012-08-13 16:09:45 -070037import org.eclipse.jgit.http.server.ServletUtils;
Shawn Pearce45e83752015-02-20 17:59:05 -080038import org.eclipse.jgit.lib.Config;
Dave Borowitz9de65952012-08-13 16:09:45 -070039import org.eclipse.jgit.lib.Constants;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080040import org.eclipse.jgit.lib.ObjectId;
41import org.eclipse.jgit.lib.Repository;
42import org.eclipse.jgit.revwalk.RevCommit;
43import org.eclipse.jgit.revwalk.RevObject;
Shawn Pearce45e83752015-02-20 17:59:05 -080044import org.eclipse.jgit.revwalk.RevTree;
Dave Borowitz14ce8282012-12-20 14:08:25 -080045import org.eclipse.jgit.revwalk.RevWalk;
Dave Borowitz9de65952012-08-13 16:09:45 -070046
Dave Borowitz9de65952012-08-13 16:09:45 -070047/** Serves the index page for a repository, if accessed directly by a browser. */
48public class RepositoryIndexServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080049 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070050
Dave Borowitz209d0aa2012-12-28 14:28:53 -080051 static final int REF_LIMIT = 10;
52 private static final int LOG_LIMIT = 20;
Shawn Pearce45e83752015-02-20 17:59:05 -080053 private static final int LOG_WITH_README_LIMIT = 5;
Dave Borowitz209d0aa2012-12-28 14:28:53 -080054
Dave Borowitz14ce8282012-12-20 14:08:25 -080055 private final TimeCache timeCache;
56
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020057 public RepositoryIndexServlet(
58 GitilesAccess.Factory accessFactory, Renderer renderer, TimeCache timeCache) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -070059 super(renderer, accessFactory);
Dave Borowitz14ce8282012-12-20 14:08:25 -080060 this.timeCache = checkNotNull(timeCache, "timeCache");
Dave Borowitz9de65952012-08-13 16:09:45 -070061 }
62
63 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020064 protected void doHead(HttpServletRequest req, HttpServletResponse res) throws IOException {
Shawn Pearce10e68e62016-01-02 09:37:58 -080065 // If the repository didn't exist a prior filter would have 404 replied.
66 Optional<FormatType> format = getFormat(req);
67 if (!format.isPresent()) {
68 res.sendError(SC_BAD_REQUEST);
69 return;
70 }
71 switch (format.get()) {
72 case HTML:
73 case JSON:
74 res.setStatus(HttpServletResponse.SC_OK);
75 res.setContentType(format.get().getMimeType());
76 break;
77 case TEXT:
David Pursehousecb91aaf2016-06-15 22:05:24 +090078 case DEFAULT:
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020079 default:
Shawn Pearce10e68e62016-01-02 09:37:58 -080080 res.sendError(SC_BAD_REQUEST);
81 break;
82 }
83 }
84
85 @Override
Kevin Graney77dbea92014-07-08 14:07:40 -040086 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitzf6dcf7a2014-07-30 10:26:58 -070087 GitilesView view = ViewFilter.getView(req);
88 Repository repo = ServletUtils.getRepository(req);
89 GitilesAccess access = getAccess(req);
90 RepositoryDescription desc = access.getRepositoryDescription();
91
Shawn Pearceb5ad0a02015-05-24 20:33:17 -070092 try (RevWalk walk = new RevWalk(repo)) {
93 Paginator paginator = null;
Dave Borowitzf6dcf7a2014-07-30 10:26:58 -070094 Map<String, Object> data = Maps.newHashMapWithExpectedSize(7);
95 List<Map<String, Object>> tags = RefServlet.getTagsSoyData(req, timeCache, walk, REF_LIMIT);
96 ObjectId headId = repo.resolve(Constants.HEAD);
97 if (headId != null) {
98 RevObject head = walk.parseAny(headId);
Shawn Pearce45e83752015-02-20 17:59:05 -080099 int limit = LOG_LIMIT;
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700100 Map<String, Object> readme = renderReadme(req, walk, view, access.getConfig(), head);
Shawn Pearce45e83752015-02-20 17:59:05 -0800101 if (readme != null) {
102 data.putAll(readme);
103 limit = LOG_WITH_README_LIMIT;
104 }
Dave Borowitzf6dcf7a2014-07-30 10:26:58 -0700105 // TODO(dborowitz): Handle non-commit or missing HEAD?
106 if (head.getType() == Constants.OBJ_COMMIT) {
107 walk.reset();
108 walk.markStart((RevCommit) head);
Shawn Pearce45e83752015-02-20 17:59:05 -0800109 paginator = new Paginator(walk, limit, null);
Dave Borowitzf6dcf7a2014-07-30 10:26:58 -0700110 }
111 }
112 if (!data.containsKey("entries")) {
113 data.put("entries", ImmutableList.of());
114 }
115 List<Map<String, Object>> branches = RefServlet.getBranchesSoyData(req, REF_LIMIT);
116
117 data.put("cloneUrl", desc.cloneUrl);
118 data.put("mirroredFromUrl", Strings.nullToEmpty(desc.mirroredFromUrl));
119 data.put("description", Strings.nullToEmpty(desc.description));
120 data.put("branches", trim(branches));
121 if (branches.size() > REF_LIMIT) {
122 data.put("moreBranchesUrl", GitilesView.refs().copyFrom(view).toUrl());
123 }
124 data.put("tags", trim(tags));
125 data.put("hasLog", paginator != null);
126 if (tags.size() > REF_LIMIT) {
127 data.put("moreTagsUrl", GitilesView.refs().copyFrom(view).toUrl());
128 }
129 GitilesConfig.putVariant(getAccess(req).getConfig(), "logEntry", "logEntryVariant", data);
130
131 if (paginator != null) {
132 DateFormatter df = new DateFormatter(access, Format.DEFAULT);
Dave Borowitze360d5c2015-09-16 16:53:30 -0400133 try (OutputStream out =
134 startRenderStreamingHtml(req, res, "gitiles.repositoryIndex", data)) {
Shawn Pearce4c2eb852014-08-26 15:35:33 -0700135 Writer w = newWriter(out, res);
Dave Borowitzf6dcf7a2014-07-30 10:26:58 -0700136 new LogSoyData(req, access, "oneline")
Dave Borowitz571b7a02018-02-09 15:18:10 -0500137 .renderStreaming(
138 paginator, "HEAD", renderer, w, df, LogSoyData.FooterBehavior.LOG_HEAD);
Shawn Pearce4c2eb852014-08-26 15:35:33 -0700139 w.flush();
Dave Borowitzf6dcf7a2014-07-30 10:26:58 -0700140 }
141 } else {
142 renderHtml(req, res, "gitiles.repositoryIndex", data);
143 }
Dave Borowitzf6dcf7a2014-07-30 10:26:58 -0700144 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700145 }
146
Kevin Graney77dbea92014-07-08 14:07:40 -0400147 @Override
148 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
149 GitilesAccess access = getAccess(req);
150 RepositoryDescription desc = access.getRepositoryDescription();
151 renderJson(req, res, desc, new TypeToken<RepositoryDescription>() {}.getType());
152 }
153
Dave Borowitz209d0aa2012-12-28 14:28:53 -0800154 private static <T> List<T> trim(List<T> list) {
155 return list.size() > REF_LIMIT ? list.subList(0, REF_LIMIT) : list;
Dave Borowitz14ce8282012-12-20 14:08:25 -0800156 }
Shawn Pearce45e83752015-02-20 17:59:05 -0800157
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200158 private static Map<String, Object> renderReadme(
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700159 HttpServletRequest req, RevWalk walk, GitilesView view, Config cfg, RevObject head)
160 throws IOException {
Shawn Pearce45e83752015-02-20 17:59:05 -0800161 RevTree rootTree;
162 try {
163 rootTree = walk.parseTree(head);
164 } catch (IncorrectObjectTypeException notTreeish) {
165 return null;
166 }
167
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200168 ReadmeHelper readme =
169 new ReadmeHelper(
170 walk.getObjectReader(),
171 GitilesView.path().copyFrom(view).setRevision(Revision.HEAD).setPathPart("/").build(),
Shawn Pearce47fd6562016-05-28 14:15:15 -0700172 MarkdownConfig.get(cfg),
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700173 rootTree,
174 req.getRequestURI());
Shawn Pearce45e83752015-02-20 17:59:05 -0800175 readme.scanTree(rootTree);
176 if (readme.isPresent()) {
Shawn Pearce08e38f22016-05-24 16:40:18 -0700177 SanitizedContent html = readme.render();
178 if (html != null) {
179 return ImmutableMap.<String, Object>of("readmeHtml", html);
180 }
Shawn Pearce45e83752015-02-20 17:59:05 -0800181 }
182 return null;
183 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700184}