blob: d946f2bdfce226a52d718d1c6b604eff667c588d [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;
43import org.eclipse.jgit.lib.Repository;
44import org.eclipse.jgit.revwalk.RevCommit;
45import org.eclipse.jgit.revwalk.RevObject;
46import org.eclipse.jgit.revwalk.RevTree;
47import org.eclipse.jgit.revwalk.RevWalk;
48import org.eclipse.jgit.submodule.SubmoduleWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080049import org.eclipse.jgit.treewalk.CanonicalTreeParser;
Dave Borowitz9de65952012-08-13 16:09:45 -070050import org.eclipse.jgit.treewalk.TreeWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080051import org.eclipse.jgit.treewalk.filter.TreeFilter;
Dave Borowitz9de65952012-08-13 16:09:45 -070052import org.eclipse.jgit.util.RawParseUtils;
53import org.slf4j.Logger;
54import org.slf4j.LoggerFactory;
55
56import java.io.IOException;
Dave Borowitz387dd792014-03-14 20:21:35 -070057import java.io.OutputStream;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080058import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070059import java.util.Map;
60import java.util.regex.Pattern;
61
62import javax.servlet.http.HttpServletRequest;
63import javax.servlet.http.HttpServletResponse;
64
65/** Serves an HTML page with detailed information about a path within a tree. */
66// TODO(dborowitz): Handle non-UTF-8 names.
67public class PathServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080068 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070069 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
70
71 /**
72 * Submodule URLs where we know there is a web page if the user visits the
73 * repository URL verbatim in a web browser.
74 */
75 private static final Pattern VERBATIM_SUBMODULE_URL_PATTERN =
76 Pattern.compile("^(" + Joiner.on('|').join(
77 "https?://[^.]+.googlesource.com/.*",
78 "https?://[^.]+.googlecode.com/.*",
79 "https?://code.google.com/p/.*",
80 "https?://github.com/.*") + ")$", Pattern.CASE_INSENSITIVE);
81
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080082 static final String AUTODIVE_PARAM = "autodive";
83 static final String NO_AUTODIVE_VALUE = "0";
84
Dave Borowitz9de65952012-08-13 16:09:45 -070085 static enum FileType {
86 TREE(FileMode.TREE),
87 SYMLINK(FileMode.SYMLINK),
88 REGULAR_FILE(FileMode.REGULAR_FILE),
89 EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE),
90 GITLINK(FileMode.GITLINK);
91
92 private final FileMode mode;
93
94 private FileType(FileMode mode) {
95 this.mode = mode;
96 }
97
98 static FileType forEntry(TreeWalk tw) {
99 int mode = tw.getRawMode(0);
100 for (FileType type : values()) {
101 if (type.mode.equals(mode)) {
102 return type;
103 }
104 }
105 return null;
106 }
107 }
108
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800109 private final GitilesUrls urls;
110
Dave Borowitzded109a2014-03-03 15:25:39 -0500111 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700112 super(renderer, accessFactory);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800113 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700114 }
115
116 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700117 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700118 GitilesView view = ViewFilter.getView(req);
119 Repository repo = ServletUtils.getRepository(req);
120
121 RevWalk rw = new RevWalk(repo);
122 try {
Dave Borowitz387dd792014-03-14 20:21:35 -0700123 RevTree root = getRoot(view, rw);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800124 TreeWalk tw = new TreeWalk(rw.getObjectReader());
125 tw.addTree(root);
126 tw.setRecursive(false);
Dave Borowitz9de65952012-08-13 16:09:45 -0700127 FileType type;
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700128 String path = view.getPathPart();
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800129 List<Boolean> hasSingleTree;
130
Dave Borowitz9de65952012-08-13 16:09:45 -0700131 if (path.isEmpty()) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700132 type = FileType.TREE;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800133 hasSingleTree = ImmutableList.<Boolean> of();
Dave Borowitz9de65952012-08-13 16:09:45 -0700134 } else {
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800135 hasSingleTree = walkToPath(tw, path);
136 if (hasSingleTree == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700137 res.setStatus(SC_NOT_FOUND);
138 return;
139 }
140 type = FileType.forEntry(tw);
Dave Borowitz9de65952012-08-13 16:09:45 -0700141 }
142
143 switch (type) {
144 case TREE:
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800145 ObjectId treeId;
146 if (path.isEmpty()) {
147 treeId = root;
148 } else {
149 treeId = tw.getObjectId(0);
150 tw.enterSubtree();
151 tw.setRecursive(false);
152 }
153 showTree(req, res, rw, tw, treeId, hasSingleTree);
Dave Borowitz9de65952012-08-13 16:09:45 -0700154 break;
155 case SYMLINK:
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800156 showSymlink(req, res, rw, tw, hasSingleTree);
Dave Borowitz9de65952012-08-13 16:09:45 -0700157 break;
158 case REGULAR_FILE:
159 case EXECUTABLE_FILE:
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800160 showFile(req, res, rw, tw, hasSingleTree);
Dave Borowitz9de65952012-08-13 16:09:45 -0700161 break;
162 case GITLINK:
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700163 showGitlink(req, res, tw, root);
Dave Borowitz9de65952012-08-13 16:09:45 -0700164 break;
165 default:
Dave Borowitzfd25c3a2013-01-11 14:37:11 -0800166 log.error("Bad file type: {}", type);
Dave Borowitz9de65952012-08-13 16:09:45 -0700167 res.setStatus(SC_NOT_FOUND);
168 break;
169 }
170 } catch (LargeObjectException e) {
171 res.setStatus(SC_INTERNAL_SERVER_ERROR);
172 } finally {
173 rw.release();
174 }
175 }
176
Dave Borowitz387dd792014-03-14 20:21:35 -0700177 @Override
178 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
179 GitilesView view = ViewFilter.getView(req);
180 Repository repo = ServletUtils.getRepository(req);
181
182 RevWalk rw = new RevWalk(repo);
183 try {
184 RevTree root = getRoot(view, rw);
185 TreeWalk tw = new TreeWalk(rw.getObjectReader());
186 tw.addTree(root);
187 tw.setRecursive(false);
188
189 String path = view.getPathPart();
190 if (path.isEmpty()) {
191 res.setStatus(SC_NOT_FOUND);
192 return;
193 }
194 if (walkToPath(tw, path) == null) {
195 res.setStatus(SC_NOT_FOUND);
196 return;
197 }
198
199 switch (FileType.forEntry(tw)) {
200 case SYMLINK:
201 case REGULAR_FILE:
202 case EXECUTABLE_FILE:
203 // Write base64 as plain text without modifying any other headers,
204 // under the assumption that any hint we can give to a browser that
205 // this is base64 data might cause it to try to decode it and render
206 // as HTML, which would be bad.
207 res.setHeader("X-Gitiles-Path-Mode", String.format("%06o", tw.getRawMode(0)));
208 try (OutputStream out = BaseEncoding.base64().encodingStream(startRenderText(req, res))) {
209 rw.getObjectReader().open(tw.getObjectId(0)).copyTo(out);
210 }
Dave Borowitz679210d2014-03-17 15:27:43 -0700211 break;
Dave Borowitz387dd792014-03-14 20:21:35 -0700212 default:
213 renderTextError(req, res, SC_NOT_FOUND, "Not a file");
214 break;
215 }
216 } catch (LargeObjectException e) {
217 res.setStatus(SC_INTERNAL_SERVER_ERROR);
218 } finally {
219 rw.release();
220 }
221 }
222
223 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
224 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
225 switch (obj.getType()) {
226 case OBJ_COMMIT:
227 return ((RevCommit) obj).getTree();
228 case OBJ_TREE:
229 return (RevTree) obj;
230 default:
231 return null;
232 }
233 }
234
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800235 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700236 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800237 List<Boolean> hasSingleTree;
238
239 private final byte[] pathRaw;
240 private int count;
241 private boolean done;
242
243 AutoDiveFilter(String pathStr) {
244 hasSingleTree = Lists.newArrayList();
245 pathRaw = Constants.encode(pathStr);
246 }
247
248 @Override
249 public boolean include(TreeWalk tw) throws MissingObjectException,
250 IncorrectObjectTypeException, IOException {
251 count++;
252 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
253 if (cmp > 0) {
254 throw StopWalkException.INSTANCE;
255 }
256 boolean include;
257 if (cmp == 0) {
258 if (!isDone(tw)) {
259 hasSingleTree.add(hasSingleTreeEntry(tw));
260 }
261 include = true;
262 } else {
263 include = false;
264 }
265 if (tw.isSubtree()) {
266 count = 0;
267 }
268 return include;
269 }
270
271 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
272 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
273 return false;
274 }
275 CanonicalTreeParser p = new CanonicalTreeParser();
276 p.reset(tw.getObjectReader(), tw.getObjectId(0));
277 p.next();
278 return p.eof();
279 }
280
281 @Override
282 public boolean shouldBeRecursive() {
283 return Bytes.indexOf(pathRaw, (byte)'/') >= 0;
284 }
285
286 @Override
287 public TreeFilter clone() {
288 return this;
289 }
290
291 private boolean isDone(TreeWalk tw) {
292 if (!done) {
293 done = pathRaw.length == tw.getPathLength();
294 }
295 return done;
296 }
297 }
298
299 private List<Boolean> walkToPath(TreeWalk tw, String pathString) throws IOException {
300 AutoDiveFilter f = new AutoDiveFilter(pathString);
301 tw.setFilter(f);
302 while (tw.next()) {
303 if (f.isDone(tw)) {
304 return f.hasSingleTree;
305 } else if (tw.isSubtree()) {
306 tw.enterSubtree();
307 }
308 }
309 return null;
310 }
311
Dave Borowitz9de65952012-08-13 16:09:45 -0700312 private void showTree(HttpServletRequest req, HttpServletResponse res, RevWalk rw, TreeWalk tw,
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800313 ObjectId id, List<Boolean> hasSingleTree) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700314 GitilesView view = ViewFilter.getView(req);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800315 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
316 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700317 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800318 CanonicalTreeParser child = getOnlyChildSubtree(rw, id, path);
319 if (child != null) {
320 while (true) {
321 path = new byte[child.getEntryPathLength()];
322 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
323 CanonicalTreeParser next = getOnlyChildSubtree(rw, child.getEntryObjectId(), path);
324 if (next == null) {
325 break;
326 }
327 child = next;
328 }
329 res.sendRedirect(GitilesView.path().copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700330 .setPathPart(
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800331 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
332 .toUrl());
333 return;
334 }
335 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700336 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800337 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700338 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800339 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700340 "type", FileType.TREE.toString(),
Dave Borowitzc782ebe2013-11-11 11:43:29 -0800341 "data", new TreeSoyData(rw, view)
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700342 .setArchiveFormat(getArchiveFormat(getAccess(req)))
Dave Borowitzc782ebe2013-11-11 11:43:29 -0800343 .toSoyData(id, tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700344 }
345
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800346 private CanonicalTreeParser getOnlyChildSubtree(RevWalk rw, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700347 throws IOException {
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800348 CanonicalTreeParser p = new CanonicalTreeParser(prefix, rw.getObjectReader(), id);
349 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
350 return null;
351 }
352 p.next(1);
353 return p.eof() ? p : null;
354 }
355
356 private void showFile(HttpServletRequest req, HttpServletResponse res, RevWalk rw, TreeWalk tw,
357 List<Boolean> hasSingleTree) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700358 GitilesView view = ViewFilter.getView(req);
359 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800360 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700361 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800362 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700363 "type", FileType.forEntry(tw).toString(),
364 "data", new BlobSoyData(rw, view).toSoyData(tw.getPathString(), tw.getObjectId(0))));
365 }
366
367 private void showSymlink(HttpServletRequest req, HttpServletResponse res, RevWalk rw,
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800368 TreeWalk tw, List<Boolean> hasSingleTree) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700369 GitilesView view = ViewFilter.getView(req);
370 ObjectId id = tw.getObjectId(0);
371 Map<String, Object> data = Maps.newHashMap();
372
373 ObjectLoader loader = rw.getObjectReader().open(id, OBJ_BLOB);
374 String target;
375 try {
376 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
377 } catch (LargeObjectException.OutOfMemory e) {
378 throw e;
379 } catch (LargeObjectException e) {
380 data.put("sha", ObjectId.toString(id));
381 data.put("data", null);
382 data.put("size", Long.toString(loader.getSize()));
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800383 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700384 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800385 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700386 "type", FileType.REGULAR_FILE.toString(),
387 "data", data));
388 return;
389 }
390
391 String url = resolveTargetUrl(
392 GitilesView.path()
393 .copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700394 .setPathPart(dirname(view.getPathPart()))
Dave Borowitz9de65952012-08-13 16:09:45 -0700395 .build(),
396 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700397 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700398 data.put("target", target);
399 if (url != null) {
400 data.put("targetUrl", url);
401 }
402
403 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800404 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700405 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800406 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700407 "type", FileType.SYMLINK.toString(),
408 "data", data));
409 }
410
411 private static String dirname(String path) {
412 while (path.charAt(path.length() - 1) == '/') {
413 path = path.substring(0, path.length() - 1);
414 }
415 int lastSlash = path.lastIndexOf('/');
416 if (lastSlash > 0) {
417 return path.substring(0, lastSlash - 1);
418 } else if (lastSlash == 0) {
419 return "/";
420 } else {
421 return ".";
422 }
423 }
424
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700425 private void showGitlink(HttpServletRequest req, HttpServletResponse res, TreeWalk tw,
426 RevTree root) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700427 GitilesView view = ViewFilter.getView(req);
428 SubmoduleWalk sw = SubmoduleWalk.forPath(ServletUtils.getRepository(req), root,
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700429 view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700430
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800431 String modulesUrl;
432 String remoteUrl = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700433 try {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800434 modulesUrl = sw.getModulesUrl();
435 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
436 String moduleRepo = Paths.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
437 if (moduleRepo != null) {
438 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
439 }
440 } else {
441 remoteUrl = sw.getRemoteUrl();
442 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700443 } catch (ConfigInvalidException e) {
444 throw new IOException(e);
445 } finally {
446 sw.release();
447 }
448
449 Map<String, Object> data = Maps.newHashMap();
450 data.put("sha", ObjectId.toString(tw.getObjectId(0)));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800451 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700452
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800453 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
454 String httpUrl = resolveHttpUrl(remoteUrl);
455 if (httpUrl != null) {
456 data.put("httpUrl", httpUrl);
457 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700458
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800459 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
460 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700461 "title", view.getPathPart(),
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800462 "type", FileType.GITLINK.toString(),
463 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700464 }
465
466 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800467 if (remoteUrl == null) {
468 return null;
469 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700470 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
471 }
472}