blob: 6c7ec06274548a8d680fb52ed6da2764b9431097 [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 Borowitz9de65952012-08-13 16:09:45 -070017import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
18
19import com.google.common.annotations.VisibleForTesting;
20import com.google.common.base.Charsets;
21import com.google.common.collect.Lists;
22import com.google.common.collect.Maps;
Dave Borowitz9de65952012-08-13 16:09:45 -070023import com.google.gitiles.PathServlet.FileType;
24
25import org.eclipse.jgit.errors.MissingObjectException;
26import org.eclipse.jgit.lib.ObjectId;
27import org.eclipse.jgit.revwalk.RevWalk;
28import org.eclipse.jgit.treewalk.TreeWalk;
29
30import java.io.IOException;
31import java.util.List;
32import java.util.Map;
Dave Borowitz9de65952012-08-13 16:09:45 -070033
34/** Soy data converter for git trees. */
35public class TreeSoyData {
36 /**
37 * Number of characters to display for a symlink target. Targets longer than
38 * this are abbreviated for display in a tree listing.
39 */
40 private static final int MAX_SYMLINK_TARGET_LENGTH = 72;
41
42 /**
43 * Maximum number of bytes to load from a blob that claims to be a symlink. If
44 * the blob is larger than this byte limit it will be displayed as a binary
45 * file instead of as a symlink.
46 */
47 static final int MAX_SYMLINK_SIZE = 16 << 10;
48
49 static String resolveTargetUrl(GitilesView view, String target) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -070050 String resolved = Paths.simplifyPathUpToRoot(target, view.getPathPart());
Dave Borowitzbcd753d2013-02-08 11:10:19 -080051 if (resolved == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -070052 return null;
53 }
Dave Borowitz9de65952012-08-13 16:09:45 -070054 return GitilesView.path()
55 .copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -070056 .setPathPart(resolved)
Dave Borowitz9de65952012-08-13 16:09:45 -070057 .toUrl();
58 }
59
60 @VisibleForTesting
61 static String getTargetDisplayName(String target) {
62 if (target.length() <= MAX_SYMLINK_TARGET_LENGTH) {
63 return target;
64 } else {
65 int lastSlash = target.lastIndexOf('/');
66 // TODO(dborowitz): Doesn't abbreviate a long last path component.
67 return lastSlash >= 0 ? "..." + target.substring(lastSlash) : target;
68 }
69 }
70
71 private final RevWalk rw;
72 private final GitilesView view;
73
74 public TreeSoyData(RevWalk rw, GitilesView view) {
75 this.rw = rw;
76 this.view = view;
77 }
78
79 public Map<String, Object> toSoyData(ObjectId treeId, TreeWalk tw) throws MissingObjectException,
80 IOException {
81 List<Object> entries = Lists.newArrayList();
82 GitilesView.Builder urlBuilder = GitilesView.path().copyFrom(view);
83 while (tw.next()) {
84 FileType type = FileType.forEntry(tw);
85 String name = tw.getNameString();
86
87 switch (view.getType()) {
88 case PATH:
Dave Borowitzdd3c3d92013-03-11 16:38:41 -070089 urlBuilder.setPathPart(view.getPathPart() + "/" + name);
Dave Borowitz9de65952012-08-13 16:09:45 -070090 break;
91 case REVISION:
92 // Got here from a tag pointing at a tree.
Dave Borowitzdd3c3d92013-03-11 16:38:41 -070093 urlBuilder.setPathPart(name);
Dave Borowitz9de65952012-08-13 16:09:45 -070094 break;
95 default:
96 throw new IllegalStateException(String.format(
97 "Cannot render TreeSoyData from %s view", view.getType()));
98 }
99
100 String url = urlBuilder.toUrl();
101 if (type == FileType.TREE) {
102 name += "/";
103 url += "/";
104 }
105 Map<String, String> entry = Maps.newHashMapWithExpectedSize(4);
106 entry.put("type", type.toString());
107 entry.put("name", name);
108 entry.put("url", url);
109 if (type == FileType.SYMLINK) {
110 String target = new String(
111 rw.getObjectReader().open(tw.getObjectId(0)).getCachedBytes(),
112 Charsets.UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -0700113 entry.put("targetName", getTargetDisplayName(target));
114 String targetUrl = resolveTargetUrl(view, target);
115 if (targetUrl != null) {
116 entry.put("targetUrl", targetUrl);
117 }
118 }
119 entries.add(entry);
120 }
121
122 Map<String, Object> data = Maps.newHashMapWithExpectedSize(3);
123 data.put("sha", treeId.name());
124 data.put("entries", entries);
125
126 if (view.getType() == GitilesView.Type.PATH
127 && view.getRevision().getPeeledType() == OBJ_COMMIT) {
128 data.put("logUrl", GitilesView.log().copyFrom(view).toUrl());
129 }
130
131 return data;
132 }
133
134 public Map<String, Object> toSoyData(ObjectId treeId) throws MissingObjectException, IOException {
135 TreeWalk tw = new TreeWalk(rw.getObjectReader());
136 tw.addTree(treeId);
137 tw.setRecursive(false);
138 return toSoyData(treeId, tw);
139 }
140}