blob: 66aa207497d0b2aa3a3020f4db9de53a889e2485 [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.gitiles.RevisionParser.PATH_SPLITTER;
18import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
19
20import com.google.common.annotations.VisibleForTesting;
21import com.google.common.base.Charsets;
Dave Borowitz558005d2012-12-20 15:48:08 -080022import com.google.common.base.Objects;
Dave Borowitz9de65952012-08-13 16:09:45 -070023import com.google.common.collect.Lists;
24import com.google.common.collect.Maps;
25import com.google.common.io.Files;
26import com.google.gitiles.PathServlet.FileType;
27
28import org.eclipse.jgit.errors.MissingObjectException;
29import org.eclipse.jgit.lib.ObjectId;
30import org.eclipse.jgit.revwalk.RevWalk;
31import org.eclipse.jgit.treewalk.TreeWalk;
32
33import java.io.IOException;
34import java.util.List;
35import java.util.Map;
36import java.util.StringTokenizer;
37
38/** Soy data converter for git trees. */
39public class TreeSoyData {
40 /**
41 * Number of characters to display for a symlink target. Targets longer than
42 * this are abbreviated for display in a tree listing.
43 */
44 private static final int MAX_SYMLINK_TARGET_LENGTH = 72;
45
46 /**
47 * Maximum number of bytes to load from a blob that claims to be a symlink. If
48 * the blob is larger than this byte limit it will be displayed as a binary
49 * file instead of as a symlink.
50 */
51 static final int MAX_SYMLINK_SIZE = 16 << 10;
52
53 static String resolveTargetUrl(GitilesView view, String target) {
54 if (target.startsWith("/")) {
55 return null;
56 }
57
58 // simplifyPath() normalizes "a/../../" to "a", so manually check whether
59 // the path leads above the git root.
Dave Borowitz558005d2012-12-20 15:48:08 -080060 String path = Objects.firstNonNull(view.getTreePath(), "");
61 int depth = new StringTokenizer(path, "/").countTokens();
Dave Borowitz9de65952012-08-13 16:09:45 -070062 for (String part : PATH_SPLITTER.split(target)) {
63 if (part.equals("..")) {
64 depth--;
65 if (depth < 0) {
66 return null;
67 }
68 } else if (!part.isEmpty() && !part.equals(".")) {
69 depth++;
70 }
71 }
72
Dave Borowitz558005d2012-12-20 15:48:08 -080073 path = Files.simplifyPath(view.getTreePath() + "/" + target);
Dave Borowitz9de65952012-08-13 16:09:45 -070074 return GitilesView.path()
75 .copyFrom(view)
76 .setTreePath(!path.equals(".") ? path : "")
77 .toUrl();
78 }
79
80 @VisibleForTesting
81 static String getTargetDisplayName(String target) {
82 if (target.length() <= MAX_SYMLINK_TARGET_LENGTH) {
83 return target;
84 } else {
85 int lastSlash = target.lastIndexOf('/');
86 // TODO(dborowitz): Doesn't abbreviate a long last path component.
87 return lastSlash >= 0 ? "..." + target.substring(lastSlash) : target;
88 }
89 }
90
91 private final RevWalk rw;
92 private final GitilesView view;
93
94 public TreeSoyData(RevWalk rw, GitilesView view) {
95 this.rw = rw;
96 this.view = view;
97 }
98
99 public Map<String, Object> toSoyData(ObjectId treeId, TreeWalk tw) throws MissingObjectException,
100 IOException {
101 List<Object> entries = Lists.newArrayList();
102 GitilesView.Builder urlBuilder = GitilesView.path().copyFrom(view);
103 while (tw.next()) {
104 FileType type = FileType.forEntry(tw);
105 String name = tw.getNameString();
106
107 switch (view.getType()) {
108 case PATH:
109 urlBuilder.setTreePath(view.getTreePath() + "/" + name);
110 break;
111 case REVISION:
112 // Got here from a tag pointing at a tree.
113 urlBuilder.setTreePath(name);
114 break;
115 default:
116 throw new IllegalStateException(String.format(
117 "Cannot render TreeSoyData from %s view", view.getType()));
118 }
119
120 String url = urlBuilder.toUrl();
121 if (type == FileType.TREE) {
122 name += "/";
123 url += "/";
124 }
125 Map<String, String> entry = Maps.newHashMapWithExpectedSize(4);
126 entry.put("type", type.toString());
127 entry.put("name", name);
128 entry.put("url", url);
129 if (type == FileType.SYMLINK) {
130 String target = new String(
131 rw.getObjectReader().open(tw.getObjectId(0)).getCachedBytes(),
132 Charsets.UTF_8);
133 // TODO(dborowitz): Merge Shawn's changes before copying these methods
134 // in.
135 entry.put("targetName", getTargetDisplayName(target));
136 String targetUrl = resolveTargetUrl(view, target);
137 if (targetUrl != null) {
138 entry.put("targetUrl", targetUrl);
139 }
140 }
141 entries.add(entry);
142 }
143
144 Map<String, Object> data = Maps.newHashMapWithExpectedSize(3);
145 data.put("sha", treeId.name());
146 data.put("entries", entries);
147
148 if (view.getType() == GitilesView.Type.PATH
149 && view.getRevision().getPeeledType() == OBJ_COMMIT) {
150 data.put("logUrl", GitilesView.log().copyFrom(view).toUrl());
151 }
152
153 return data;
154 }
155
156 public Map<String, Object> toSoyData(ObjectId treeId) throws MissingObjectException, IOException {
157 TreeWalk tw = new TreeWalk(rw.getObjectReader());
158 tw.addTree(treeId);
159 tw.setRecursive(false);
160 return toSoyData(treeId, tw);
161 }
162}