blob: d13446025285b8a58ae727e375c3d6416847f779 [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
Dave Borowitzbcd753d2013-02-08 11:10:19 -080017import static com.google.common.base.Preconditions.checkNotNull;
Dave Borowitz9de65952012-08-13 16:09:45 -070018import static com.google.gitiles.TreeSoyData.resolveTargetUrl;
19import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
20import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
21import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
22import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
23import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
24
25import com.google.common.base.Joiner;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080026import com.google.common.collect.ImmutableList;
Dave Borowitz9de65952012-08-13 16:09:45 -070027import com.google.common.collect.ImmutableMap;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080028import com.google.common.collect.Lists;
Dave Borowitz9de65952012-08-13 16:09:45 -070029import com.google.common.collect.Maps;
Dave Borowitz387dd792014-03-14 20:21:35 -070030import com.google.common.io.BaseEncoding;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080031import com.google.common.primitives.Bytes;
Dave Borowitz9de65952012-08-13 16:09:45 -070032
33import org.eclipse.jgit.errors.ConfigInvalidException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080034import org.eclipse.jgit.errors.IncorrectObjectTypeException;
Dave Borowitz9de65952012-08-13 16:09:45 -070035import org.eclipse.jgit.errors.LargeObjectException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080036import org.eclipse.jgit.errors.MissingObjectException;
37import org.eclipse.jgit.errors.StopWalkException;
Dave Borowitz9de65952012-08-13 16:09:45 -070038import org.eclipse.jgit.http.server.ServletUtils;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080039import org.eclipse.jgit.lib.Constants;
Dave Borowitz9de65952012-08-13 16:09:45 -070040import org.eclipse.jgit.lib.FileMode;
41import org.eclipse.jgit.lib.ObjectId;
42import org.eclipse.jgit.lib.ObjectLoader;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070043import org.eclipse.jgit.lib.ObjectReader;
Dave Borowitz9de65952012-08-13 16:09:45 -070044import org.eclipse.jgit.lib.Repository;
45import org.eclipse.jgit.revwalk.RevCommit;
46import org.eclipse.jgit.revwalk.RevObject;
47import org.eclipse.jgit.revwalk.RevTree;
48import org.eclipse.jgit.revwalk.RevWalk;
49import org.eclipse.jgit.submodule.SubmoduleWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080050import org.eclipse.jgit.treewalk.CanonicalTreeParser;
Dave Borowitz9de65952012-08-13 16:09:45 -070051import org.eclipse.jgit.treewalk.TreeWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080052import org.eclipse.jgit.treewalk.filter.TreeFilter;
Dave Borowitz9de65952012-08-13 16:09:45 -070053import org.eclipse.jgit.util.RawParseUtils;
54import org.slf4j.Logger;
55import org.slf4j.LoggerFactory;
56
57import java.io.IOException;
Dave Borowitz387dd792014-03-14 20:21:35 -070058import java.io.OutputStream;
Dave Borowitz4f568702014-05-01 19:54:57 -070059import java.io.PrintWriter;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080060import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070061import java.util.Map;
62import java.util.regex.Pattern;
63
64import javax.servlet.http.HttpServletRequest;
65import javax.servlet.http.HttpServletResponse;
66
67/** Serves an HTML page with detailed information about a path within a tree. */
68// TODO(dborowitz): Handle non-UTF-8 names.
69public class PathServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080070 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070071 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
72
Dave Borowitz4f568702014-05-01 19:54:57 -070073 static final String MODE_HEADER = "X-Gitiles-Path-Mode";
74
Dave Borowitz9de65952012-08-13 16:09:45 -070075 /**
76 * Submodule URLs where we know there is a web page if the user visits the
77 * repository URL verbatim in a web browser.
78 */
79 private static final Pattern VERBATIM_SUBMODULE_URL_PATTERN =
80 Pattern.compile("^(" + Joiner.on('|').join(
81 "https?://[^.]+.googlesource.com/.*",
82 "https?://[^.]+.googlecode.com/.*",
83 "https?://code.google.com/p/.*",
84 "https?://github.com/.*") + ")$", Pattern.CASE_INSENSITIVE);
85
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080086 static final String AUTODIVE_PARAM = "autodive";
87 static final String NO_AUTODIVE_VALUE = "0";
88
Dave Borowitz9de65952012-08-13 16:09:45 -070089 static enum FileType {
90 TREE(FileMode.TREE),
91 SYMLINK(FileMode.SYMLINK),
92 REGULAR_FILE(FileMode.REGULAR_FILE),
93 EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE),
94 GITLINK(FileMode.GITLINK);
95
96 private final FileMode mode;
97
98 private FileType(FileMode mode) {
99 this.mode = mode;
100 }
101
102 static FileType forEntry(TreeWalk tw) {
103 int mode = tw.getRawMode(0);
104 for (FileType type : values()) {
105 if (type.mode.equals(mode)) {
106 return type;
107 }
108 }
109 return null;
110 }
111 }
112
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800113 private final GitilesUrls urls;
114
Dave Borowitzded109a2014-03-03 15:25:39 -0500115 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700116 super(renderer, accessFactory);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800117 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700118 }
119
120 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700121 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700122 GitilesView view = ViewFilter.getView(req);
123 Repository repo = ServletUtils.getRepository(req);
124
125 RevWalk rw = new RevWalk(repo);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700126 WalkResult wr = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700127 try {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700128 wr = WalkResult.forPath(rw, view);
129 if (wr == null) {
130 res.setStatus(SC_NOT_FOUND);
131 return;
Dave Borowitz9de65952012-08-13 16:09:45 -0700132 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700133 switch (wr.type) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700134 case TREE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700135 showTree(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700136 break;
137 case SYMLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700138 showSymlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 break;
140 case REGULAR_FILE:
141 case EXECUTABLE_FILE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700142 showFile(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700143 break;
144 case GITLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700145 showGitlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700146 break;
147 default:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700148 log.error("Bad file type: {}", wr.type);
Dave Borowitz9de65952012-08-13 16:09:45 -0700149 res.setStatus(SC_NOT_FOUND);
150 break;
151 }
152 } catch (LargeObjectException e) {
153 res.setStatus(SC_INTERNAL_SERVER_ERROR);
154 } finally {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700155 if (wr != null) {
156 wr.release();
157 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700158 rw.release();
159 }
160 }
161
Dave Borowitz387dd792014-03-14 20:21:35 -0700162 @Override
163 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
164 GitilesView view = ViewFilter.getView(req);
165 Repository repo = ServletUtils.getRepository(req);
166
167 RevWalk rw = new RevWalk(repo);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700168 WalkResult wr = null;
Dave Borowitz387dd792014-03-14 20:21:35 -0700169 try {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700170 wr = WalkResult.forPath(rw, view);
171 if (wr == null) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700172 res.setStatus(SC_NOT_FOUND);
173 return;
174 }
175
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700176 switch (wr.type) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700177 case SYMLINK:
178 case REGULAR_FILE:
179 case EXECUTABLE_FILE:
180 // Write base64 as plain text without modifying any other headers,
181 // under the assumption that any hint we can give to a browser that
182 // this is base64 data might cause it to try to decode it and render
183 // as HTML, which would be bad.
Dave Borowitz4f568702014-05-01 19:54:57 -0700184 PrintWriter writer = startRenderText(req, res, null);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700185 res.setHeader(MODE_HEADER, String.format("%06o", wr.type.mode.getBits()));
Dave Borowitz4f568702014-05-01 19:54:57 -0700186 try (OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700187 rw.getObjectReader().open(wr.id).copyTo(out);
Dave Borowitz387dd792014-03-14 20:21:35 -0700188 }
Dave Borowitz679210d2014-03-17 15:27:43 -0700189 break;
Dave Borowitz387dd792014-03-14 20:21:35 -0700190 default:
191 renderTextError(req, res, SC_NOT_FOUND, "Not a file");
192 break;
193 }
194 } catch (LargeObjectException e) {
195 res.setStatus(SC_INTERNAL_SERVER_ERROR);
196 } finally {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700197 if (wr != null) {
198 wr.release();
199 }
Dave Borowitz387dd792014-03-14 20:21:35 -0700200 rw.release();
201 }
202 }
203
204 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
205 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
206 switch (obj.getType()) {
207 case OBJ_COMMIT:
208 return ((RevCommit) obj).getTree();
209 case OBJ_TREE:
210 return (RevTree) obj;
211 default:
212 return null;
213 }
214 }
215
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800216 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700217 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800218 List<Boolean> hasSingleTree;
219
220 private final byte[] pathRaw;
221 private int count;
222 private boolean done;
223
224 AutoDiveFilter(String pathStr) {
225 hasSingleTree = Lists.newArrayList();
226 pathRaw = Constants.encode(pathStr);
227 }
228
229 @Override
230 public boolean include(TreeWalk tw) throws MissingObjectException,
231 IncorrectObjectTypeException, IOException {
232 count++;
233 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
234 if (cmp > 0) {
235 throw StopWalkException.INSTANCE;
236 }
237 boolean include;
238 if (cmp == 0) {
239 if (!isDone(tw)) {
240 hasSingleTree.add(hasSingleTreeEntry(tw));
241 }
242 include = true;
243 } else {
244 include = false;
245 }
246 if (tw.isSubtree()) {
247 count = 0;
248 }
249 return include;
250 }
251
252 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
253 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
254 return false;
255 }
256 CanonicalTreeParser p = new CanonicalTreeParser();
257 p.reset(tw.getObjectReader(), tw.getObjectId(0));
258 p.next();
259 return p.eof();
260 }
261
262 @Override
263 public boolean shouldBeRecursive() {
264 return Bytes.indexOf(pathRaw, (byte)'/') >= 0;
265 }
266
267 @Override
268 public TreeFilter clone() {
269 return this;
270 }
271
272 private boolean isDone(TreeWalk tw) {
273 if (!done) {
274 done = pathRaw.length == tw.getPathLength();
275 }
276 return done;
277 }
278 }
279
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700280 /**
281 * Encapsulate the result of walking to a single tree.
282 * <p>
283 * Unlike {@link TreeWalk} itself, supports positioning at the root tree.
284 * Includes information to help the auto-dive routine as well.
285 */
286 private static class WalkResult {
287 private static WalkResult forPath(RevWalk rw, GitilesView view) throws IOException {
288 RevTree root = getRoot(view, rw);
289 String path = view.getPathPart();
290 TreeWalk tw = new TreeWalk(rw.getObjectReader());
291 try {
292 tw.addTree(root);
293 tw.setRecursive(false);
294 if (path.isEmpty()) {
295 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean> of());
296 }
297 AutoDiveFilter f = new AutoDiveFilter(path);
298 tw.setFilter(f);
299 while (tw.next()) {
300 if (f.isDone(tw)) {
301 FileType type = FileType.forEntry(tw);
302 ObjectId id = tw.getObjectId(0);
303 if (type == FileType.TREE) {
304 tw.enterSubtree();
305 tw.setRecursive(false);
306 }
307 return new WalkResult(tw, path, root, id, type, f.hasSingleTree);
308 } else if (tw.isSubtree()) {
309 tw.enterSubtree();
310 }
311 }
312 } catch (IOException | RuntimeException e) {
313 // Fallthrough.
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800314 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700315 tw.release();
316 return null;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800317 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700318
319 private final TreeWalk tw;
320 private final String path;
321 private final RevTree root;
322 private final ObjectId id;
323 private final FileType type;
324 private final List<Boolean> hasSingleTree;
325
326 private WalkResult(TreeWalk tw, String path, RevTree root, ObjectId objectId, FileType type,
327 List<Boolean> hasSingleTree) {
328 this.tw = tw;
329 this.path = path;
330 this.root = root;
331 this.id = objectId;
332 this.type = type;
333 this.hasSingleTree = hasSingleTree;
334 }
335
336 private ObjectReader getObjectReader() {
337 return tw.getObjectReader();
338 }
339
340 private void release() {
341 tw.release();
342 }
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800343 }
344
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700345 private void showTree(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
346 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700347 GitilesView view = ViewFilter.getView(req);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800348 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
349 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700350 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700351 ObjectReader reader = wr.getObjectReader();
352 CanonicalTreeParser child = getOnlyChildSubtree(reader, wr.id, path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800353 if (child != null) {
354 while (true) {
355 path = new byte[child.getEntryPathLength()];
356 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700357 CanonicalTreeParser next = getOnlyChildSubtree(reader, child.getEntryObjectId(), path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800358 if (next == null) {
359 break;
360 }
361 child = next;
362 }
363 res.sendRedirect(GitilesView.path().copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700364 .setPathPart(
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800365 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
366 .toUrl());
367 return;
368 }
369 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700370 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800371 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700372 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700373 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700374 "type", FileType.TREE.toString(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700375 "data", new TreeSoyData(wr.getObjectReader(), view)
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700376 .setArchiveFormat(getArchiveFormat(getAccess(req)))
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700377 .toSoyData(wr.id, wr.tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700378 }
379
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700380 private CanonicalTreeParser getOnlyChildSubtree(ObjectReader reader, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700381 throws IOException {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700382 CanonicalTreeParser p = new CanonicalTreeParser(prefix, reader, id);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800383 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
384 return null;
385 }
386 p.next(1);
387 return p.eof() ? p : null;
388 }
389
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700390 private void showFile(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
391 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700392 GitilesView view = ViewFilter.getView(req);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700393 Map<String, ?> data = new BlobSoyData(wr.getObjectReader(), view)
394 .toSoyData(wr.path, wr.id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700395 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800396 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700397 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700398 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
399 "type", wr.type.toString(),
400 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700401 }
402
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700403 private void showSymlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
404 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700405 GitilesView view = ViewFilter.getView(req);
Dave Borowitz9de65952012-08-13 16:09:45 -0700406 Map<String, Object> data = Maps.newHashMap();
407
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700408 ObjectLoader loader = wr.getObjectReader().open(wr.id, OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -0700409 String target;
410 try {
411 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
412 } catch (LargeObjectException.OutOfMemory e) {
413 throw e;
414 } catch (LargeObjectException e) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700415 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitz9de65952012-08-13 16:09:45 -0700416 data.put("data", null);
417 data.put("size", Long.toString(loader.getSize()));
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800418 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700419 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700420 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700421 "type", FileType.REGULAR_FILE.toString(),
422 "data", data));
423 return;
424 }
425
426 String url = resolveTargetUrl(
427 GitilesView.path()
428 .copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700429 .setPathPart(dirname(view.getPathPart()))
Dave Borowitz9de65952012-08-13 16:09:45 -0700430 .build(),
431 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700432 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700433 data.put("target", target);
434 if (url != null) {
435 data.put("targetUrl", url);
436 }
437
438 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800439 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700440 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700441 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700442 "type", FileType.SYMLINK.toString(),
443 "data", data));
444 }
445
446 private static String dirname(String path) {
447 while (path.charAt(path.length() - 1) == '/') {
448 path = path.substring(0, path.length() - 1);
449 }
450 int lastSlash = path.lastIndexOf('/');
451 if (lastSlash > 0) {
452 return path.substring(0, lastSlash - 1);
453 } else if (lastSlash == 0) {
454 return "/";
455 } else {
456 return ".";
457 }
458 }
459
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700460 private void showGitlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
461 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700462 GitilesView view = ViewFilter.getView(req);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700463 SubmoduleWalk sw = SubmoduleWalk.forPath(ServletUtils.getRepository(req), wr.root,
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700464 view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700465
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800466 String modulesUrl;
467 String remoteUrl = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700468 try {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800469 modulesUrl = sw.getModulesUrl();
470 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
471 String moduleRepo = Paths.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
472 if (moduleRepo != null) {
473 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
474 }
475 } else {
476 remoteUrl = sw.getRemoteUrl();
477 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700478 } catch (ConfigInvalidException e) {
479 throw new IOException(e);
480 } finally {
481 sw.release();
482 }
483
484 Map<String, Object> data = Maps.newHashMap();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700485 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800486 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700487
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800488 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
489 String httpUrl = resolveHttpUrl(remoteUrl);
490 if (httpUrl != null) {
491 data.put("httpUrl", httpUrl);
492 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700493
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800494 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
495 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700496 "title", view.getPathPart(),
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800497 "type", FileType.GITLINK.toString(),
498 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700499 }
500
501 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800502 if (remoteUrl == null) {
503 return null;
504 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700505 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
506 }
507}