blob: 4e2a579ef705957501bee9e06ac2419d4c21fe94 [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
David Pletcherd7bdaf32014-08-27 14:50:32 -070017import static java.nio.charset.StandardCharsets.UTF_8;
Dave Borowitz9de65952012-08-13 16:09:45 -070018import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
19
20import com.google.common.annotations.VisibleForTesting;
Dave Borowitz73269892013-11-13 14:23:50 -080021import com.google.common.base.Strings;
Dave Borowitz9de65952012-08-13 16:09:45 -070022import com.google.common.collect.Lists;
23import com.google.common.collect.Maps;
Dave Borowitz9de65952012-08-13 16:09:45 -070024import com.google.gitiles.PathServlet.FileType;
Shawn Pearce47fd6562016-05-28 14:15:15 -070025import com.google.gitiles.doc.MarkdownConfig;
Dave Borowitz3b744b12016-08-19 16:11:10 -040026import java.io.IOException;
27import java.util.List;
28import java.util.Map;
Matthias Sohnc156c962023-09-30 22:15:23 +020029import javax.annotation.Nullable;
Dave Borowitz9de65952012-08-13 16:09:45 -070030import org.eclipse.jgit.errors.MissingObjectException;
Shawn Pearce73e34532015-02-12 16:27:54 -080031import org.eclipse.jgit.lib.Config;
Dave Borowitz9de65952012-08-13 16:09:45 -070032import org.eclipse.jgit.lib.ObjectId;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070033import org.eclipse.jgit.lib.ObjectReader;
Shawn Pearce73e34532015-02-12 16:27:54 -080034import org.eclipse.jgit.revwalk.RevTree;
Dave Borowitz9de65952012-08-13 16:09:45 -070035import org.eclipse.jgit.treewalk.TreeWalk;
36
Dave Borowitz9de65952012-08-13 16:09:45 -070037/** Soy data converter for git trees. */
38public class TreeSoyData {
39 /**
Dave Borowitz40255d52016-08-19 16:16:22 -040040 * Number of characters to display for a symlink target. Targets longer than this are abbreviated
41 * for display in a tree listing.
Dave Borowitz9de65952012-08-13 16:09:45 -070042 */
43 private static final int MAX_SYMLINK_TARGET_LENGTH = 72;
44
Pontus Jaensson4c5c8d22021-11-10 12:46:16 +010045 private static final Map<String, Integer> TYPE_WEIGHT =
46 Map.of(
47 "TREE", 0,
48 "GITLINK", 1,
49 "SYMLINK", 2,
50 "REGULAR_FILE", 3,
51 "EXECUTABLE_FILE", 3);
52
Dave Borowitz9de65952012-08-13 16:09:45 -070053 /**
Dave Borowitz40255d52016-08-19 16:16:22 -040054 * Maximum number of bytes to load from a blob that claims to be a symlink. If the blob is larger
55 * than this byte limit it will be displayed as a binary file instead of as a symlink.
Dave Borowitz9de65952012-08-13 16:09:45 -070056 */
57 static final int MAX_SYMLINK_SIZE = 16 << 10;
58
Matthias Sohnc156c962023-09-30 22:15:23 +020059 static @Nullable String resolveTargetUrl(GitilesView view, String target) {
Dave Borowitzcfc1c532015-02-18 13:41:19 -080060 String resolved = PathUtil.simplifyPathUpToRoot(target, view.getPathPart());
Dave Borowitzbcd753d2013-02-08 11:10:19 -080061 if (resolved == null) {
Dave Borowitz9de65952012-08-13 16:09:45 -070062 return null;
63 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020064 return GitilesView.path().copyFrom(view).setPathPart(resolved).toUrl();
Dave Borowitz9de65952012-08-13 16:09:45 -070065 }
66
67 @VisibleForTesting
68 static String getTargetDisplayName(String target) {
69 if (target.length() <= MAX_SYMLINK_TARGET_LENGTH) {
70 return target;
Dave Borowitz9de65952012-08-13 16:09:45 -070071 }
David Pursehouseb3b630f2016-06-15 21:51:18 +090072 int lastSlash = target.lastIndexOf('/');
73 // TODO(dborowitz): Doesn't abbreviate a long last path component.
74 return lastSlash >= 0 ? "..." + target.substring(lastSlash) : target;
Dave Borowitz9de65952012-08-13 16:09:45 -070075 }
76
Pontus Jaenssonf08cb4a2021-11-11 14:06:57 +010077 static String stripEndingSolidus(String p) {
78 return p.endsWith("/") ? p.substring(0, p.length() - 1) : p;
79 }
80
81 static int sortByTypeAlpha(Map<String, String> m1, Map<String, String> m2) {
82 int weightDiff = TYPE_WEIGHT.get(m1.get("type")).compareTo(TYPE_WEIGHT.get(m2.get("type")));
83 if (weightDiff == 0) {
84 String s1 = m1.get("name");
85 String s2 = m2.get("name");
86 if (m1.get("type").equals("TREE")) {
87 s1 = stripEndingSolidus(s1);
88 s2 = stripEndingSolidus(s2);
89 }
90 return s1.compareToIgnoreCase(s2);
91 }
92 return weightDiff;
Pontus Jaensson4c5c8d22021-11-10 12:46:16 +010093 }
94
Dave Borowitz7c0a8332014-05-01 11:07:04 -070095 private final ObjectReader reader;
Dave Borowitz9de65952012-08-13 16:09:45 -070096 private final GitilesView view;
Shawn Pearce73e34532015-02-12 16:27:54 -080097 private final Config cfg;
98 private final RevTree rootTree;
Shawn Pearcec68ad0b2016-05-28 16:52:47 -070099 private final String requestUri;
Dave Borowitzc782ebe2013-11-11 11:43:29 -0800100 private ArchiveFormat archiveFormat;
Dave Borowitz9de65952012-08-13 16:09:45 -0700101
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700102 public TreeSoyData(
103 ObjectReader reader, GitilesView view, Config cfg, RevTree rootTree, String requestUri) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700104 this.reader = reader;
Dave Borowitz9de65952012-08-13 16:09:45 -0700105 this.view = view;
Shawn Pearce73e34532015-02-12 16:27:54 -0800106 this.cfg = cfg;
107 this.rootTree = rootTree;
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700108 this.requestUri = requestUri;
Dave Borowitz9de65952012-08-13 16:09:45 -0700109 }
110
Dave Borowitzc782ebe2013-11-11 11:43:29 -0800111 public TreeSoyData setArchiveFormat(ArchiveFormat archiveFormat) {
112 this.archiveFormat = archiveFormat;
113 return this;
114 }
115
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200116 public Map<String, Object> toSoyData(ObjectId treeId, TreeWalk tw)
117 throws MissingObjectException, IOException {
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700118 ReadmeHelper readme =
119 new ReadmeHelper(reader, view, MarkdownConfig.get(cfg), rootTree, requestUri);
Pontus Jaensson4c5c8d22021-11-10 12:46:16 +0100120 List<Map<String, String>> entries = Lists.newArrayList();
Dave Borowitz9de65952012-08-13 16:09:45 -0700121 GitilesView.Builder urlBuilder = GitilesView.path().copyFrom(view);
122 while (tw.next()) {
123 FileType type = FileType.forEntry(tw);
124 String name = tw.getNameString();
125
David Pursehousecb91aaf2016-06-15 22:05:24 +0900126 GitilesView.Type viewType = view.getType();
127 if (viewType == GitilesView.Type.PATH) {
128 urlBuilder.setPathPart(view.getPathPart() + "/" + name);
129 } else if (viewType == GitilesView.Type.REVISION) {
130 // Got here from a tag pointing at a tree.
131 urlBuilder.setPathPart(name);
132 } else {
133 throw new IllegalStateException(
134 String.format("Cannot render TreeSoyData from %s view", viewType));
Dave Borowitz9de65952012-08-13 16:09:45 -0700135 }
136
137 String url = urlBuilder.toUrl();
138 if (type == FileType.TREE) {
139 name += "/";
140 url += "/";
141 }
142 Map<String, String> entry = Maps.newHashMapWithExpectedSize(4);
143 entry.put("type", type.toString());
144 entry.put("name", name);
145 entry.put("url", url);
146 if (type == FileType.SYMLINK) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200147 String target = new String(reader.open(tw.getObjectId(0)).getCachedBytes(), UTF_8);
Dave Borowitz9de65952012-08-13 16:09:45 -0700148 entry.put("targetName", getTargetDisplayName(target));
149 String targetUrl = resolveTargetUrl(view, target);
150 if (targetUrl != null) {
151 entry.put("targetUrl", targetUrl);
152 }
Shawn Pearce45e83752015-02-20 17:59:05 -0800153 } else {
154 readme.considerEntry(tw);
Dave Borowitz9de65952012-08-13 16:09:45 -0700155 }
156 entries.add(entry);
157 }
158
Pontus Jaenssonf08cb4a2021-11-11 14:06:57 +0100159 entries.sort(TreeSoyData::sortByTypeAlpha);
Pontus Jaensson4c5c8d22021-11-10 12:46:16 +0100160
Dave Borowitz9de65952012-08-13 16:09:45 -0700161 Map<String, Object> data = Maps.newHashMapWithExpectedSize(3);
162 data.put("sha", treeId.name());
163 data.put("entries", entries);
164
165 if (view.getType() == GitilesView.Type.PATH
166 && view.getRevision().getPeeledType() == OBJ_COMMIT) {
167 data.put("logUrl", GitilesView.log().copyFrom(view).toUrl());
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200168 data.put(
169 "archiveUrl",
170 GitilesView.archive()
171 .copyFrom(view)
172 .setPathPart(Strings.emptyToNull(view.getPathPart()))
173 .setExtension(archiveFormat.getDefaultSuffix())
174 .toUrl());
Dave Borowitzc782ebe2013-11-11 11:43:29 -0800175 data.put("archiveType", archiveFormat.getShortName());
Dave Borowitz9de65952012-08-13 16:09:45 -0700176 }
177
Shawn Pearce45e83752015-02-20 17:59:05 -0800178 if (readme.isPresent()) {
179 data.put("readmePath", readme.getPath());
180 data.put("readmeHtml", readme.render());
Shawn Pearce73e34532015-02-12 16:27:54 -0800181 }
182
Dave Borowitz9de65952012-08-13 16:09:45 -0700183 return data;
184 }
185
186 public Map<String, Object> toSoyData(ObjectId treeId) throws MissingObjectException, IOException {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700187 TreeWalk tw = new TreeWalk(reader);
Dave Borowitz9de65952012-08-13 16:09:45 -0700188 tw.addTree(treeId);
189 tw.setRecursive(false);
190 return toSoyData(treeId, tw);
191 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700192}