| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 1 | // 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 | |
| 15 | package com.google.gitiles; |
| 16 | |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 17 | import static com.google.common.base.Preconditions.checkState; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 18 | import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; |
| 19 | |
| Luca Milanesio | beb12ac | 2019-11-18 12:14:38 -0800 | [diff] [blame] | 20 | import com.google.common.annotations.VisibleForTesting; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 21 | import com.google.common.base.Strings; |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 22 | import com.google.common.collect.ImmutableList; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 23 | import com.google.common.collect.Maps; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 24 | import com.google.template.soy.data.SoyListData; |
| 25 | import com.google.template.soy.data.SoyMapData; |
| Dave Borowitz | 3b744b1 | 2016-08-19 16:11:10 -0400 | [diff] [blame] | 26 | import java.io.IOException; |
| 27 | import java.util.List; |
| 28 | import java.util.Map; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 29 | import org.eclipse.jgit.diff.RawText; |
| 30 | import org.eclipse.jgit.errors.LargeObjectException; |
| 31 | import org.eclipse.jgit.errors.MissingObjectException; |
| 32 | import org.eclipse.jgit.lib.Constants; |
| 33 | import org.eclipse.jgit.lib.ObjectId; |
| 34 | import org.eclipse.jgit.lib.ObjectLoader; |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 35 | import org.eclipse.jgit.lib.ObjectReader; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 36 | import org.eclipse.jgit.util.RawParseUtils; |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 37 | import org.slf4j.Logger; |
| 38 | import org.slf4j.LoggerFactory; |
| Shawn Pearce | c10cc99 | 2015-02-09 00:22:33 -0800 | [diff] [blame] | 39 | import prettify.parser.Prettify; |
| 40 | import syntaxhighlight.ParseResult; |
| 41 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 42 | /** Soy data converter for git blobs. */ |
| 43 | public class BlobSoyData { |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 44 | private static final Logger log = LoggerFactory.getLogger(BlobSoyData.class); |
| 45 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 46 | /** |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 47 | * Maximum number of bytes to load from a supposed text file for display. Files larger than this |
| 48 | * will be displayed as binary files, even if the contents was text. For example really big XML |
| 49 | * files may be above this limit and will get displayed as binary. |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 50 | */ |
| Luca Milanesio | beb12ac | 2019-11-18 12:14:38 -0800 | [diff] [blame] | 51 | @VisibleForTesting static final int MAX_FILE_SIZE = 10 << 20; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 52 | |
| Ronald Bhuleskar | c9208e5 | 2019-07-10 17:44:08 -0700 | [diff] [blame] | 53 | /** |
| 54 | * Maximum number of lines to be displayed. Files larger than this will be displayed as binary |
| 55 | * files, even on a text content. For example really big XML files may be above this limit and |
| 56 | * will get displayed as binary. |
| 57 | */ |
| 58 | private static final int MAX_LINE_COUNT = 50000; |
| 59 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 60 | private final GitilesView view; |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 61 | private final ObjectReader reader; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 62 | |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 63 | public BlobSoyData(ObjectReader reader, GitilesView view) { |
| 64 | this.reader = reader; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 65 | this.view = view; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 66 | } |
| 67 | |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 68 | public Map<String, Object> toSoyData(ObjectId blobId) throws MissingObjectException, IOException { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 69 | return toSoyData(null, blobId); |
| 70 | } |
| 71 | |
| 72 | public Map<String, Object> toSoyData(String path, ObjectId blobId) |
| 73 | throws MissingObjectException, IOException { |
| 74 | Map<String, Object> data = Maps.newHashMapWithExpectedSize(4); |
| 75 | data.put("sha", ObjectId.toString(blobId)); |
| 76 | |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 77 | ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 78 | String content; |
| 79 | try { |
| 80 | byte[] raw = loader.getCachedBytes(MAX_FILE_SIZE); |
| Luca Milanesio | beb12ac | 2019-11-18 12:14:38 -0800 | [diff] [blame] | 81 | content = |
| 82 | (raw.length < MAX_FILE_SIZE && !RawText.isBinary(raw)) ? RawParseUtils.decode(raw) : null; |
| Ronald Bhuleskar | c9208e5 | 2019-07-10 17:44:08 -0700 | [diff] [blame] | 83 | if (isContentTooLargeForDisplay(content)) { |
| 84 | content = null; |
| 85 | } |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 86 | } catch (LargeObjectException.OutOfMemory e) { |
| 87 | throw e; |
| 88 | } catch (LargeObjectException e) { |
| 89 | content = null; |
| 90 | } |
| 91 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 92 | if (content != null) { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 93 | data.put("lines", prettify(path, content)); |
| Shawn Pearce | fdd98f6 | 2015-02-12 10:04:07 -0800 | [diff] [blame] | 94 | if (path != null && path.endsWith(".md")) { |
| 95 | data.put("docUrl", GitilesView.doc().copyFrom(view).toUrl()); |
| 96 | } |
| Dave Borowitz | 96a6f47 | 2014-11-04 16:38:20 -0800 | [diff] [blame] | 97 | } else { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 98 | data.put("lines", null); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 99 | data.put("size", Long.toString(loader.getSize())); |
| 100 | } |
| 101 | if (path != null && view.getRevision().getPeeledType() == OBJ_COMMIT) { |
| Dave Borowitz | 7745e7f | 2014-04-27 19:47:47 -0600 | [diff] [blame] | 102 | data.put("fileUrl", GitilesView.path().copyFrom(view).toUrl()); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 103 | data.put("logUrl", GitilesView.log().copyFrom(view).toUrl()); |
| Dave Borowitz | 6ec0c87 | 2014-01-29 13:59:37 -0800 | [diff] [blame] | 104 | data.put("blameUrl", GitilesView.blame().copyFrom(view).toUrl()); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 105 | } |
| 106 | return data; |
| 107 | } |
| 108 | |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 109 | private SoyListData prettify(String path, String content) { |
| 110 | List<ParseResult> results = parse(path, content); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 111 | SoyListData lines = new SoyListData(); |
| 112 | SoyListData line = new SoyListData(); |
| 113 | lines.add(line); |
| 114 | |
| 115 | int last = 0; |
| 116 | for (ParseResult r : results) { |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 117 | checkState( |
| 118 | r.getOffset() >= last, |
| 119 | "out-of-order ParseResult, expected %s >= %s", |
| 120 | r.getOffset(), |
| 121 | last); |
| David Pursehouse | 390888b | 2016-06-17 16:55:02 +0900 | [diff] [blame] | 122 | writeResult(lines, null, content, last, r.getOffset()); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 123 | last = r.getOffset() + r.getLength(); |
| David Pursehouse | 390888b | 2016-06-17 16:55:02 +0900 | [diff] [blame] | 124 | writeResult(lines, r.getStyleKeysString(), content, r.getOffset(), last); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 125 | } |
| 126 | if (last < content.length()) { |
| 127 | writeResult(lines, null, content, last, content.length()); |
| 128 | } |
| 129 | return lines; |
| 130 | } |
| 131 | |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 132 | private List<ParseResult> parse(String path, String content) { |
| Shawn Pearce | c10cc99 | 2015-02-09 00:22:33 -0800 | [diff] [blame] | 133 | String lang = extension(path, content); |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 134 | try { |
| Shawn Pearce | c10cc99 | 2015-02-09 00:22:33 -0800 | [diff] [blame] | 135 | return ThreadSafePrettifyParser.INSTANCE.parse(lang, content); |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 136 | } catch (StackOverflowError e) { |
| 137 | // TODO(dborowitz): Aaagh. Make prettify use RE2. Or replace it something |
| 138 | // else. Or something. |
| David Pursehouse | fb9a2a9 | 2020-02-19 10:45:07 +0900 | [diff] [blame] | 139 | log.warn("StackOverflowError prettifying {}", view.toUrl()); |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 140 | return ImmutableList.of( |
| 141 | new ParseResult(0, content.length(), ImmutableList.of(Prettify.PR_PLAIN))); |
| 142 | } |
| 143 | } |
| 144 | |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 145 | private static void writeResult(SoyListData lines, String classes, String s, int start, int end) { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 146 | SoyListData line = lines.getListData(lines.length() - 1); |
| 147 | while (true) { |
| 148 | int nl = nextLineBreak(s, start, end); |
| 149 | if (nl < 0) { |
| 150 | break; |
| 151 | } |
| 152 | addSpan(line, classes, s, start, nl); |
| 153 | |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 154 | start = nl + 1; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 155 | if (start == s.length()) { |
| David Pursehouse | 390888b | 2016-06-17 16:55:02 +0900 | [diff] [blame] | 156 | return; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 157 | } |
| 158 | line = new SoyListData(); |
| 159 | lines.add(line); |
| 160 | } |
| 161 | addSpan(line, classes, s, start, end); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 162 | } |
| 163 | |
| 164 | private static void addSpan(SoyListData line, String classes, String s, int start, int end) { |
| 165 | if (end - start > 0) { |
| 166 | if (Strings.isNullOrEmpty(classes)) { |
| 167 | classes = Prettify.PR_PLAIN; |
| 168 | } |
| 169 | line.add(new SoyMapData("classes", classes, "text", s.substring(start, end))); |
| 170 | } |
| 171 | } |
| 172 | |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 173 | private static int nextLineBreak(String s, int start, int end) { |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 174 | int n = s.indexOf('\n', start); |
| 175 | return n < end ? n : -1; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | private static String extension(String path, String content) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 179 | if (content.startsWith("#!/bin/sh") || content.startsWith("#!/bin/bash")) { |
| 180 | return "sh"; |
| 181 | } else if (content.startsWith("#!/usr/bin/perl")) { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 182 | return "pl"; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 183 | } else if (content.startsWith("#!/usr/bin/python")) { |
| 184 | return "py"; |
| 185 | } else if (path == null) { |
| 186 | return null; |
| 187 | } |
| 188 | |
| 189 | int slash = path.lastIndexOf('/'); |
| 190 | int dot = path.lastIndexOf('.'); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 191 | String ext = ((0 < dot) && (slash < dot)) ? path.substring(dot + 1) : null; |
| 192 | if ("txt".equalsIgnoreCase(ext)) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 193 | return null; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 194 | } else if ("mk".equalsIgnoreCase(ext)) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 195 | return "sh"; |
| 196 | } else if ("Makefile".equalsIgnoreCase(path) |
| 197 | || ((0 < slash) && "Makefile".equalsIgnoreCase(path.substring(slash + 1)))) { |
| 198 | return "sh"; |
| 199 | } else { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 200 | return ext; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 201 | } |
| 202 | } |
| Ronald Bhuleskar | c9208e5 | 2019-07-10 17:44:08 -0700 | [diff] [blame] | 203 | |
| 204 | private static boolean isContentTooLargeForDisplay(String content) { |
| 205 | if (content == null) { |
| 206 | return false; |
| 207 | } |
| 208 | |
| 209 | int lines = 0; |
| 210 | int nl = -1; |
| 211 | while (true) { |
| 212 | nl = nextLineBreak(content, nl + 1, content.length()); |
| 213 | if (nl < 0) { |
| 214 | return false; |
| 215 | } else if (++lines == MAX_LINE_COUNT) { |
| 216 | return true; |
| 217 | } |
| 218 | } |
| 219 | } |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 220 | } |