blob: dd399ba96969e71d0dd0cece4bcf377ad8bfcb6e [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 Borowitzded109a2014-03-03 15:25:39 -0500109 private final GitilesAccess.Factory accessFactory;
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800110 private final GitilesUrls urls;
111
Dave Borowitzded109a2014-03-03 15:25:39 -0500112 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
113 super(renderer);
114 this.accessFactory = checkNotNull(accessFactory, "accessFactory");
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800115 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700116 }
117
118 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700119 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700120 GitilesView view = ViewFilter.getView(req);
121 Repository repo = ServletUtils.getRepository(req);
122
123 RevWalk rw = new RevWalk(repo);
124 try {
Dave Borowitz387dd792014-03-14 20:21:35 -0700125 RevTree root = getRoot(view, rw);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800126 TreeWalk tw = new TreeWalk(rw.getObjectReader());
127 tw.addTree(root);
128 tw.setRecursive(false);
Dave Borowitz9de65952012-08-13 16:09:45 -0700129 FileType type;
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700130 String path = view.getPathPart();
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800131 List<Boolean> hasSingleTree;
132
Dave Borowitz9de65952012-08-13 16:09:45 -0700133 if (path.isEmpty()) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700134 type = FileType.TREE;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800135 hasSingleTree = ImmutableList.<Boolean> of();
Dave Borowitz9de65952012-08-13 16:09:45 -0700136 } else {
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800137 hasSingleTree = walkToPath(tw, path);
138 if (hasSingleTree == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 res.setStatus(SC_NOT_FOUND);
140 return;
141 }
142 type = FileType.forEntry(tw);
Dave Borowitz9de65952012-08-13 16:09:45 -0700143 }
144
145 switch (type) {
146 case TREE:
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800147 ObjectId treeId;
148 if (path.isEmpty()) {
149 treeId = root;
150 } else {
151 treeId = tw.getObjectId(0);
152 tw.enterSubtree();
153 tw.setRecursive(false);
154 }
155 showTree(req, res, rw, tw, treeId, hasSingleTree);
Dave Borowitz9de65952012-08-13 16:09:45 -0700156 break;
157 case SYMLINK:
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800158 showSymlink(req, res, rw, tw, hasSingleTree);
Dave Borowitz9de65952012-08-13 16:09:45 -0700159 break;
160 case REGULAR_FILE:
161 case EXECUTABLE_FILE:
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800162 showFile(req, res, rw, tw, hasSingleTree);
Dave Borowitz9de65952012-08-13 16:09:45 -0700163 break;
164 case GITLINK:
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700165 showGitlink(req, res, tw, root);
Dave Borowitz9de65952012-08-13 16:09:45 -0700166 break;
167 default:
Dave Borowitzfd25c3a2013-01-11 14:37:11 -0800168 log.error("Bad file type: {}", type);
Dave Borowitz9de65952012-08-13 16:09:45 -0700169 res.setStatus(SC_NOT_FOUND);
170 break;
171 }
172 } catch (LargeObjectException e) {
173 res.setStatus(SC_INTERNAL_SERVER_ERROR);
174 } finally {
175 rw.release();
176 }
177 }
178
Dave Borowitz387dd792014-03-14 20:21:35 -0700179 @Override
180 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
181 GitilesView view = ViewFilter.getView(req);
182 Repository repo = ServletUtils.getRepository(req);
183
184 RevWalk rw = new RevWalk(repo);
185 try {
186 RevTree root = getRoot(view, rw);
187 TreeWalk tw = new TreeWalk(rw.getObjectReader());
188 tw.addTree(root);
189 tw.setRecursive(false);
190
191 String path = view.getPathPart();
192 if (path.isEmpty()) {
193 res.setStatus(SC_NOT_FOUND);
194 return;
195 }
196 if (walkToPath(tw, path) == null) {
197 res.setStatus(SC_NOT_FOUND);
198 return;
199 }
200
201 switch (FileType.forEntry(tw)) {
202 case SYMLINK:
203 case REGULAR_FILE:
204 case EXECUTABLE_FILE:
205 // Write base64 as plain text without modifying any other headers,
206 // under the assumption that any hint we can give to a browser that
207 // this is base64 data might cause it to try to decode it and render
208 // as HTML, which would be bad.
209 res.setHeader("X-Gitiles-Path-Mode", String.format("%06o", tw.getRawMode(0)));
210 try (OutputStream out = BaseEncoding.base64().encodingStream(startRenderText(req, res))) {
211 rw.getObjectReader().open(tw.getObjectId(0)).copyTo(out);
212 }
Dave Borowitz679210d2014-03-17 15:27:43 -0700213 break;
Dave Borowitz387dd792014-03-14 20:21:35 -0700214 default:
215 renderTextError(req, res, SC_NOT_FOUND, "Not a file");
216 break;
217 }
218 } catch (LargeObjectException e) {
219 res.setStatus(SC_INTERNAL_SERVER_ERROR);
220 } finally {
221 rw.release();
222 }
223 }
224
225 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
226 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
227 switch (obj.getType()) {
228 case OBJ_COMMIT:
229 return ((RevCommit) obj).getTree();
230 case OBJ_TREE:
231 return (RevTree) obj;
232 default:
233 return null;
234 }
235 }
236
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800237 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700238 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800239 List<Boolean> hasSingleTree;
240
241 private final byte[] pathRaw;
242 private int count;
243 private boolean done;
244
245 AutoDiveFilter(String pathStr) {
246 hasSingleTree = Lists.newArrayList();
247 pathRaw = Constants.encode(pathStr);
248 }
249
250 @Override
251 public boolean include(TreeWalk tw) throws MissingObjectException,
252 IncorrectObjectTypeException, IOException {
253 count++;
254 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
255 if (cmp > 0) {
256 throw StopWalkException.INSTANCE;
257 }
258 boolean include;
259 if (cmp == 0) {
260 if (!isDone(tw)) {
261 hasSingleTree.add(hasSingleTreeEntry(tw));
262 }
263 include = true;
264 } else {
265 include = false;
266 }
267 if (tw.isSubtree()) {
268 count = 0;
269 }
270 return include;
271 }
272
273 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
274 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
275 return false;
276 }
277 CanonicalTreeParser p = new CanonicalTreeParser();
278 p.reset(tw.getObjectReader(), tw.getObjectId(0));
279 p.next();
280 return p.eof();
281 }
282
283 @Override
284 public boolean shouldBeRecursive() {
285 return Bytes.indexOf(pathRaw, (byte)'/') >= 0;
286 }
287
288 @Override
289 public TreeFilter clone() {
290 return this;
291 }
292
293 private boolean isDone(TreeWalk tw) {
294 if (!done) {
295 done = pathRaw.length == tw.getPathLength();
296 }
297 return done;
298 }
299 }
300
301 private List<Boolean> walkToPath(TreeWalk tw, String pathString) throws IOException {
302 AutoDiveFilter f = new AutoDiveFilter(pathString);
303 tw.setFilter(f);
304 while (tw.next()) {
305 if (f.isDone(tw)) {
306 return f.hasSingleTree;
307 } else if (tw.isSubtree()) {
308 tw.enterSubtree();
309 }
310 }
311 return null;
312 }
313
Dave Borowitz9de65952012-08-13 16:09:45 -0700314 private void showTree(HttpServletRequest req, HttpServletResponse res, RevWalk rw, TreeWalk tw,
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800315 ObjectId id, List<Boolean> hasSingleTree) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700316 GitilesView view = ViewFilter.getView(req);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800317 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
318 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700319 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800320 CanonicalTreeParser child = getOnlyChildSubtree(rw, id, path);
321 if (child != null) {
322 while (true) {
323 path = new byte[child.getEntryPathLength()];
324 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
325 CanonicalTreeParser next = getOnlyChildSubtree(rw, child.getEntryObjectId(), path);
326 if (next == null) {
327 break;
328 }
329 child = next;
330 }
331 res.sendRedirect(GitilesView.path().copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700332 .setPathPart(
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800333 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
334 .toUrl());
335 return;
336 }
337 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700338 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800339 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700340 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800341 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700342 "type", FileType.TREE.toString(),
Dave Borowitzc782ebe2013-11-11 11:43:29 -0800343 "data", new TreeSoyData(rw, view)
Dave Borowitzded109a2014-03-03 15:25:39 -0500344 .setArchiveFormat(getArchiveFormat(accessFactory.forRequest(req)))
Dave Borowitzc782ebe2013-11-11 11:43:29 -0800345 .toSoyData(id, tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700346 }
347
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800348 private CanonicalTreeParser getOnlyChildSubtree(RevWalk rw, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700349 throws IOException {
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800350 CanonicalTreeParser p = new CanonicalTreeParser(prefix, rw.getObjectReader(), id);
351 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
352 return null;
353 }
354 p.next(1);
355 return p.eof() ? p : null;
356 }
357
358 private void showFile(HttpServletRequest req, HttpServletResponse res, RevWalk rw, TreeWalk tw,
359 List<Boolean> hasSingleTree) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700360 GitilesView view = ViewFilter.getView(req);
361 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800362 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700363 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800364 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700365 "type", FileType.forEntry(tw).toString(),
366 "data", new BlobSoyData(rw, view).toSoyData(tw.getPathString(), tw.getObjectId(0))));
367 }
368
369 private void showSymlink(HttpServletRequest req, HttpServletResponse res, RevWalk rw,
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800370 TreeWalk tw, List<Boolean> hasSingleTree) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700371 GitilesView view = ViewFilter.getView(req);
372 ObjectId id = tw.getObjectId(0);
373 Map<String, Object> data = Maps.newHashMap();
374
375 ObjectLoader loader = rw.getObjectReader().open(id, OBJ_BLOB);
376 String target;
377 try {
378 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
379 } catch (LargeObjectException.OutOfMemory e) {
380 throw e;
381 } catch (LargeObjectException e) {
382 data.put("sha", ObjectId.toString(id));
383 data.put("data", null);
384 data.put("size", Long.toString(loader.getSize()));
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800385 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700386 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800387 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700388 "type", FileType.REGULAR_FILE.toString(),
389 "data", data));
390 return;
391 }
392
393 String url = resolveTargetUrl(
394 GitilesView.path()
395 .copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700396 .setPathPart(dirname(view.getPathPart()))
Dave Borowitz9de65952012-08-13 16:09:45 -0700397 .build(),
398 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700399 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700400 data.put("target", target);
401 if (url != null) {
402 data.put("targetUrl", url);
403 }
404
405 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800406 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700407 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800408 "breadcrumbs", view.getBreadcrumbs(hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700409 "type", FileType.SYMLINK.toString(),
410 "data", data));
411 }
412
413 private static String dirname(String path) {
414 while (path.charAt(path.length() - 1) == '/') {
415 path = path.substring(0, path.length() - 1);
416 }
417 int lastSlash = path.lastIndexOf('/');
418 if (lastSlash > 0) {
419 return path.substring(0, lastSlash - 1);
420 } else if (lastSlash == 0) {
421 return "/";
422 } else {
423 return ".";
424 }
425 }
426
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700427 private void showGitlink(HttpServletRequest req, HttpServletResponse res, TreeWalk tw,
428 RevTree root) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700429 GitilesView view = ViewFilter.getView(req);
430 SubmoduleWalk sw = SubmoduleWalk.forPath(ServletUtils.getRepository(req), root,
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700431 view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700432
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800433 String modulesUrl;
434 String remoteUrl = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700435 try {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800436 modulesUrl = sw.getModulesUrl();
437 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
438 String moduleRepo = Paths.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
439 if (moduleRepo != null) {
440 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
441 }
442 } else {
443 remoteUrl = sw.getRemoteUrl();
444 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700445 } catch (ConfigInvalidException e) {
446 throw new IOException(e);
447 } finally {
448 sw.release();
449 }
450
451 Map<String, Object> data = Maps.newHashMap();
452 data.put("sha", ObjectId.toString(tw.getObjectId(0)));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800453 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700454
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800455 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
456 String httpUrl = resolveHttpUrl(remoteUrl);
457 if (httpUrl != null) {
458 data.put("httpUrl", httpUrl);
459 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700460
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800461 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
462 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700463 "title", view.getPathPart(),
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800464 "type", FileType.GITLINK.toString(),
465 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700466 }
467
468 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800469 if (remoteUrl == null) {
470 return null;
471 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700472 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
473 }
474}