Merge branch 'stable-0.2' * stable-0.2: Correct license header for VisibilityCacheTest Check BLOB content size before trying to render it Change-Id: Ieff6110441bedad6f75fdeff28ce0233807c1e0b
diff --git a/WORKSPACE b/WORKSPACE index 883ec60..afff638 100644 --- a/WORKSPACE +++ b/WORKSPACE
@@ -58,8 +58,8 @@ maven_jar( name = "guava", - artifact = "com.google.guava:guava:27.1-jre", - sha1 = "e47b59c893079b87743cdcfb6f17ca95c08c592c", + artifact = "com.google.guava:guava:28.1-jre", + sha1 = "b0e91dcb6a44ffb6221b5027e12a5cb34b841145", ) maven_jar( @@ -129,8 +129,8 @@ maven_jar( name = "truth", - artifact = "com.google.truth:truth:0.44", - sha1 = "11eff954c0c14da7d43276d7b3bcf71463105368", + artifact = "com.google.truth:truth:1.0", + sha1 = "998e5fb3fa31df716574b4c9e8d374855e800451", ) # Indirect dependency of truth @@ -142,8 +142,8 @@ maven_jar( name = "soy", - artifact = "com.google.template:soy:2019-03-11", - sha1 = "119ac4b3eb0e2c638526ca99374013965c727097", + artifact = "com.google.template:soy:2019-10-08", + sha1 = "4518bf8bac2dbbed684849bc209c39c4cb546237", ) maven_jar( @@ -164,7 +164,7 @@ sha1 = "198ea005f41219f038f4291f0b0e9f3259730e92", ) -JGIT_VERS = "5.3.1.201904271842-r" +JGIT_VERS = "5.5.1.201910021850-r" JGIT_REPO = MAVEN_CENTRAL @@ -172,28 +172,28 @@ name = "jgit-lib", artifact = "org.eclipse.jgit:org.eclipse.jgit:" + JGIT_VERS, repository = JGIT_REPO, - sha1 = "dba85014483315fa426259bc1b8ccda9373a624b", + sha1 = "e0ba7a468e8c62da8521ca3a06a061d4dde95223", ) maven_jar( name = "jgit-servlet", artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + JGIT_VERS, repository = JGIT_REPO, - sha1 = "3287341fca859340a00b51cb5dd3b78b8e532b39", + sha1 = "dde1857a91504fadda0af4bb8958d11cfb14dcfe", ) maven_jar( name = "jgit-junit", artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + JGIT_VERS, repository = JGIT_REPO, - sha1 = "3d9ba7e610d6ab5d08dcb1e4ba448b592a34de77", + sha1 = "894f85c1615d1b47def1018bd98ca65dcaf5a8d5", ) maven_jar( name = "jgit-archive", artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + JGIT_VERS, repository = JGIT_REPO, - sha1 = "3585027e83fb44a5de2c10ae9ddbf976593bf080", + sha1 = "1dd6de0d52ad3055cee6ba9b34764b08d85a5238", ) maven_jar( @@ -206,17 +206,17 @@ # corresponding version maven_jar( name = "commons-compress", - artifact = "org.apache.commons:commons-compress:1.15", - sha1 = "b686cd04abaef1ea7bc5e143c080563668eec17e", + artifact = "org.apache.commons:commons-compress:1.18", + sha1 = "1191f9f2bc0c47a8cce69193feb1ff0a8bcb37d5", ) # Transitive dependency of commons_compress. Should only be # upgraded at the same time as commons_compress. maven_jar( name = "tukaani-xz", - artifact = "org.tukaani:xz:1.6", + artifact = "org.tukaani:xz:1.8", attach_source = False, - sha1 = "05b6f921f1810bdf90e25471968f741f87168b64", + sha1 = "c4f7d054303948eb6a4066194253886c8af07128", ) maven_jar( @@ -271,46 +271,78 @@ sha1 = "6975da39a7040257bd51d21a231b76c915872d38", ) -JETTY_VERSION = "9.4.12.v20180830" +JETTY_VERSION = "9.4.18.v20190429" maven_jar( name = "servlet", artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VERSION, - sha1 = "4c1149328eda9fa39a274262042420f66d9ffd5f", + sha1 = "290f7a88f351950d51ebc9fb4a794752c62d7de5", ) maven_jar( name = "security", artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VERSION, - sha1 = "299e0602a9c0b753ba232cc1c1dda72ddd9addcf", + sha1 = "01aceff3608ca1b223bfd275a497797cfe675ef4", ) maven_jar( name = "server", artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VERSION, - sha1 = "b0f25df0d32a445fd07d5f16fff1411c16b888fa", + sha1 = "b76ef50e04635f11d4d43bc6ccb7c4482a8384f0", ) maven_jar( name = "continuation", artifact = "org.eclipse.jetty:jetty-continuation:" + JETTY_VERSION, - sha1 = "5f6d6e06f95088a3a7118b9065bc49ce7c014b75", + sha1 = "3c421a3be5be5805e32b1a7f9c6046526524181d", ) maven_jar( name = "http", artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VERSION, - sha1 = "1341796dde4e16df69bca83f3e87688ba2e7d703", + sha1 = "c2e73db2db5c369326b717da71b6587b3da11e0e", ) maven_jar( name = "io", artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VERSION, - sha1 = "e93f5adaa35a9a6a85ba130f589c5305c6ecc9e3", + sha1 = "844af5efe58ab23fd0166a796efef123f4cb06b0", ) maven_jar( name = "util", artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VERSION, - sha1 = "cb4ccec9bd1fe4b10a04a0fb25d7053c1050188a", + sha1 = "13e6148bfda7ae511f69ae7e5e3ea898bc9b0e33", +) + +OW2_VERS = "7.0" + +maven_jar( + name = "ow2-asm", + artifact = "org.ow2.asm:asm:" + OW2_VERS, + sha1 = "d74d4ba0dee443f68fb2dcb7fcdb945a2cd89912", +) + +maven_jar( + name = "ow2-asm-analysis", + artifact = "org.ow2.asm:asm-analysis:" + OW2_VERS, + sha1 = "4b310d20d6f1c6b7197a75f1b5d69f169bc8ac1f", +) + +maven_jar( + name = "ow2-asm-commons", + artifact = "org.ow2.asm:asm-commons:" + OW2_VERS, + sha1 = "478006d07b7c561ae3a92ddc1829bca81ae0cdd1", +) + +maven_jar( + name = "ow2-asm-tree", + artifact = "org.ow2.asm:asm-tree:" + OW2_VERS, + sha1 = "29bc62dcb85573af6e62e5b2d735ef65966c4180", +) + +maven_jar( + name = "ow2-asm-util", + artifact = "org.ow2.asm:asm-util:" + OW2_VERS, + sha1 = "18d4d07010c24405129a6dbb0e92057f8779fb9d", )
diff --git a/java/com/google/gitiles/ArchiveFormat.java b/java/com/google/gitiles/ArchiveFormat.java index 76c4efc..8e2ad8e 100644 --- a/java/com/google/gitiles/ArchiveFormat.java +++ b/java/com/google/gitiles/ArchiveFormat.java
@@ -53,7 +53,9 @@ } } + @SuppressWarnings("ImmutableEnumChecker") // ArchiveCommand.Format is effectively immutable. private final ArchiveCommand.Format<?> format; + private final String mimeType; ArchiveFormat(String mimeType, ArchiveCommand.Format<?> format) {
diff --git a/java/com/google/gitiles/BaseServlet.java b/java/com/google/gitiles/BaseServlet.java index 02a3f1a..91c66bb 100644 --- a/java/com/google/gitiles/BaseServlet.java +++ b/java/com/google/gitiles/BaseServlet.java
@@ -145,6 +145,7 @@ * * @param req in-progress request. * @param res in-progress response. + * @throws IOException if there was an error rendering the result. */ protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException { throw new GitilesRequestFailureException(FailureReason.UNSUPPORTED_RESPONSE_FORMAT); @@ -155,6 +156,7 @@ * * @param req in-progress request. * @param res in-progress response. + * @throws IOException if there was an error rendering the result. */ protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException { throw new GitilesRequestFailureException(FailureReason.UNSUPPORTED_RESPONSE_FORMAT); @@ -165,6 +167,7 @@ * * @param req in-progress request. * @param res in-progress response. + * @throws IOException if there was an error rendering the result. */ protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException { throw new GitilesRequestFailureException(FailureReason.UNSUPPORTED_RESPONSE_FORMAT); @@ -201,7 +204,7 @@ protected void renderHtml( HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException { - renderer.render(req, res, templateName, startHtmlResponse(req, res, soyData)); + renderer.renderHtml(req, res, templateName, startHtmlResponse(req, res, soyData)); } /** @@ -224,7 +227,8 @@ HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException { req.setAttribute(STREAMING_ATTRIBUTE, true); - return renderer.renderStreaming(res, false, templateName, startHtmlResponse(req, res, soyData)); + return renderer.renderHtmlStreaming( + res, false, templateName, startHtmlResponse(req, res, soyData)); } /** @@ -256,7 +260,8 @@ res.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip"); gzip = true; } - return renderer.renderStreaming(res, gzip, templateName, startHtmlResponse(req, res, soyData)); + return renderer.renderHtmlStreaming( + res, gzip, templateName, startHtmlResponse(req, res, soyData)); } private Map<String, ?> startHtmlResponse(
diff --git a/java/com/google/gitiles/BlobSoyData.java b/java/com/google/gitiles/BlobSoyData.java index c906b4a..2051d2a 100644 --- a/java/com/google/gitiles/BlobSoyData.java +++ b/java/com/google/gitiles/BlobSoyData.java
@@ -50,6 +50,13 @@ */ @VisibleForTesting static final int MAX_FILE_SIZE = 10 << 20; + /** + * Maximum number of lines to be displayed. Files larger than this will be displayed as binary + * files, even on a text content. For example really big XML files may be above this limit and + * will get displayed as binary. + */ + private static final int MAX_LINE_COUNT = 50000; + private final GitilesView view; private final ObjectReader reader; @@ -73,6 +80,9 @@ byte[] raw = loader.getCachedBytes(MAX_FILE_SIZE); content = (raw.length < MAX_FILE_SIZE && !RawText.isBinary(raw)) ? RawParseUtils.decode(raw) : null; + if (isContentTooLargeForDisplay(content)) { + content = null; + } } catch (LargeObjectException.OutOfMemory e) { throw e; } catch (LargeObjectException e) { @@ -190,4 +200,21 @@ return ext; } } + + private static boolean isContentTooLargeForDisplay(String content) { + if (content == null) { + return false; + } + + int lines = 0; + int nl = -1; + while (true) { + nl = nextLineBreak(content, nl + 1, content.length()); + if (nl < 0) { + return false; + } else if (++lines == MAX_LINE_COUNT) { + return true; + } + } + } }
diff --git a/java/com/google/gitiles/DateFormatter.java b/java/com/google/gitiles/DateFormatter.java index b14483f..69e8a86 100644 --- a/java/com/google/gitiles/DateFormatter.java +++ b/java/com/google/gitiles/DateFormatter.java
@@ -14,6 +14,7 @@ package com.google.gitiles; +import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -63,7 +64,8 @@ private final Optional<TimeZone> fixedTz; private final Format format; - public DateFormatter(Optional<TimeZone> fixedTz, Format format) { + @VisibleForTesting + protected DateFormatter(Optional<TimeZone> fixedTz, Format format) { this.fixedTz = fixedTz; this.format = format; }
diff --git a/java/com/google/gitiles/DebugRenderer.java b/java/com/google/gitiles/DebugRenderer.java index 791067b..5e35e57 100644 --- a/java/com/google/gitiles/DebugRenderer.java +++ b/java/com/google/gitiles/DebugRenderer.java
@@ -21,7 +21,7 @@ import com.google.common.collect.Streams; import com.google.common.hash.HashCode; import com.google.template.soy.SoyFileSet; -import com.google.template.soy.tofu.SoyTofu; +import com.google.template.soy.jbcsrc.api.SoySauce; import java.io.File; import java.net.URISyntaxException; import java.net.URL; @@ -47,7 +47,7 @@ } @Override - protected SoyTofu getTofu() { + protected SoySauce getSauce() { SoyFileSet.Builder builder = SoyFileSet.builder().setCompileTimeGlobals(globals); for (URL template : templates.values()) { try { @@ -57,6 +57,6 @@ } builder.add(template); } - return builder.build().compileToTofu(); + return builder.build().compileTemplates(); } }
diff --git a/java/com/google/gitiles/DefaultErrorHandlingFilter.java b/java/com/google/gitiles/DefaultErrorHandlingFilter.java index 958b800..ec52bb9 100644 --- a/java/com/google/gitiles/DefaultErrorHandlingFilter.java +++ b/java/com/google/gitiles/DefaultErrorHandlingFilter.java
@@ -13,12 +13,12 @@ // limitations under the License. package com.google.gitiles; -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; -import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError; +import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.common.collect.ImmutableMap; +import com.google.gitiles.GitilesRequestFailureException.FailureReason; import java.io.IOException; +import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -36,28 +36,83 @@ /** HTTP header that indicates an error detail. */ public static final String GITILES_ERROR = "X-Gitiles-Error"; + private Renderer renderer; + + public DefaultErrorHandlingFilter(Renderer renderer) { + this.renderer = renderer; + } + @Override public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(req, res); } catch (GitilesRequestFailureException e) { - res.setHeader(GITILES_ERROR, e.getReason().toString()); - String publicMessage = e.getPublicErrorMessage(); - if (publicMessage != null) { - res.sendError(e.getReason().getHttpStatusCode(), publicMessage); - } else { - res.sendError(e.getReason().getHttpStatusCode()); + try { + res.setHeader(GITILES_ERROR, e.getReason().toString()); + renderHtml(req, res, e.getReason().getHttpStatusCode(), e.getPublicErrorMessage()); + } catch (IOException e2) { + e.addSuppressed(e2); + throw e; } } catch (RepositoryNotFoundException e) { - res.sendError(SC_NOT_FOUND); + try { + renderHtml(req, res, FailureReason.REPOSITORY_NOT_FOUND); + } catch (IOException e2) { + e.addSuppressed(e2); + throw e; + } } catch (AmbiguousObjectException e) { - res.sendError(SC_BAD_REQUEST); + try { + renderHtml(req, res, FailureReason.AMBIGUOUS_OBJECT); + } catch (IOException e2) { + e.addSuppressed(e2); + throw e; + } } catch (ServiceMayNotContinueException e) { - sendError(req, res, e.getStatusCode(), e.getMessage()); - } catch (IOException | ServletException err) { - log.warn("Internal server error", err); - res.sendError(SC_INTERNAL_SERVER_ERROR); + try { + renderHtml(req, res, e.getStatusCode(), e.getMessage()); + } catch (IOException e2) { + e.addSuppressed(e2); + throw e; + } + } catch (IOException | ServletException e) { + try { + log.warn("Internal server error", e); + renderHtml(req, res, FailureReason.INTERNAL_SERVER_ERROR); + } catch (IOException e2) { + e.addSuppressed(e2); + throw e; + } } } + + private void renderHtml(HttpServletRequest req, HttpServletResponse res, FailureReason reason) + throws IOException { + res.setHeader(GITILES_ERROR, reason.toString()); + renderHtml(req, res, reason.getHttpStatusCode(), reason.getMessage()); + } + + private void renderHtml( + HttpServletRequest req, HttpServletResponse res, int status, String message) + throws IOException { + res.setStatus(status); + renderHtml(req, res, "gitiles.error", ImmutableMap.of("title", message)); + } + + protected void renderHtml( + HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData) + throws IOException { + renderer.renderHtml(req, res, templateName, startHtmlResponse(req, res, soyData)); + } + + private Map<String, ?> startHtmlResponse( + HttpServletRequest req, HttpServletResponse res, Map<String, ?> soyData) { + res.setContentType(FormatType.HTML.getMimeType()); + res.setCharacterEncoding(UTF_8.name()); + BaseServlet.setNotCacheable(res); + Map<String, Object> allData = BaseServlet.getData(req); + allData.putAll(soyData); + return allData; + } }
diff --git a/java/com/google/gitiles/DefaultRenderer.java b/java/com/google/gitiles/DefaultRenderer.java index 297350e..0307862 100644 --- a/java/com/google/gitiles/DefaultRenderer.java +++ b/java/com/google/gitiles/DefaultRenderer.java
@@ -18,13 +18,13 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import com.google.template.soy.SoyFileSet; -import com.google.template.soy.tofu.SoyTofu; +import com.google.template.soy.jbcsrc.api.SoySauce; import java.net.URL; import java.util.Map; /** Renderer that precompiles Soy and uses static precompiled CSS. */ public class DefaultRenderer extends Renderer { - private final SoyTofu tofu; + private final SoySauce sauce; DefaultRenderer() { this("", ImmutableList.<URL>of(), ""); @@ -49,11 +49,11 @@ for (URL template : templates.values()) { builder.add(template); } - tofu = builder.build().compileToTofu(); + sauce = builder.build().compileTemplates(); } @Override - protected SoyTofu getTofu() { - return tofu; + protected SoySauce getSauce() { + return sauce; } }
diff --git a/java/com/google/gitiles/DiffServlet.java b/java/com/google/gitiles/DiffServlet.java index 5b1801d..5a9f07b 100644 --- a/java/com/google/gitiles/DiffServlet.java +++ b/java/com/google/gitiles/DiffServlet.java
@@ -152,7 +152,7 @@ if (newCommit.getParentCount() > 0) { return Arrays.asList(newCommit.getParents()).contains(oldRevision.getId()); } - return oldRevision == Revision.NULL; + return Revision.isNull(oldRevision); } private static boolean isFile(TreeWalk tw) {
diff --git a/java/com/google/gitiles/GitilesFilter.java b/java/com/google/gitiles/GitilesFilter.java index 2c810bb..79dcc4c 100644 --- a/java/com/google/gitiles/GitilesFilter.java +++ b/java/com/google/gitiles/GitilesFilter.java
@@ -383,9 +383,9 @@ if (visibilityCache == null) { if (config.getSubsections("cache").contains("visibility")) { visibilityCache = - new VisibilityCache(false, ConfigUtil.getCacheBuilder(config, "visibility")); + new VisibilityCache(ConfigUtil.getCacheBuilder(config, "visibility")); } else { - visibilityCache = new VisibilityCache(false); + visibilityCache = new VisibilityCache(); } } } @@ -420,7 +420,7 @@ private void setDefaultErrorHandler() { if (errorHandler == null) { - errorHandler = new DefaultErrorHandlingFilter(); + errorHandler = new DefaultErrorHandlingFilter(renderer); } }
diff --git a/java/com/google/gitiles/GitilesRequestFailureException.java b/java/com/google/gitiles/GitilesRequestFailureException.java index 316c023..dd990a8 100644 --- a/java/com/google/gitiles/GitilesRequestFailureException.java +++ b/java/com/google/gitiles/GitilesRequestFailureException.java
@@ -20,6 +20,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; +import java.util.Optional; import javax.annotation.Nullable; /** @@ -112,53 +113,64 @@ @Nullable public String getPublicErrorMessage() { - return publicErrorMessage; + return Optional.ofNullable(publicErrorMessage).orElse(reason.getMessage()); } /** The request failure reason. */ public enum FailureReason { /** The object specified by the URL is ambiguous and Gitiles cannot identify one object. */ - AMBIGUOUS_OBJECT(SC_BAD_REQUEST), + AMBIGUOUS_OBJECT( + SC_BAD_REQUEST, + "The object specified by the URL is ambiguous and Gitiles cannot identify one object"), /** There's nothing to show for blame (e.g. the file is empty). */ - BLAME_REGION_NOT_FOUND(SC_NOT_FOUND), + BLAME_REGION_NOT_FOUND(SC_NOT_FOUND, "There's nothing to show for blame"), /** Cannot parse URL as a Gitiles URL. */ - CANNOT_PARSE_GITILES_VIEW(SC_NOT_FOUND), + CANNOT_PARSE_GITILES_VIEW(SC_NOT_FOUND, "Cannot parse URL as a Gitiles URL"), /** URL parameters are not valid. */ - INCORECT_PARAMETER(SC_BAD_REQUEST), + INCORECT_PARAMETER(SC_BAD_REQUEST, "URL parameters are not valid"), /** * The object specified by the URL is not suitable for the view (e.g. trying to show a blob as a * tree). */ - INCORRECT_OBJECT_TYPE(SC_BAD_REQUEST), + INCORRECT_OBJECT_TYPE( + SC_BAD_REQUEST, "The object specified by the URL is not suitable for the view"), /** Markdown rendering is not enabled. */ - MARKDOWN_NOT_ENABLED(SC_NOT_FOUND), + MARKDOWN_NOT_ENABLED(SC_NOT_FOUND, "Markdown rendering is not enabled"), /** Request is not authorized. */ - NOT_AUTHORIZED(SC_UNAUTHORIZED), + NOT_AUTHORIZED(SC_UNAUTHORIZED, "Request is not authorized"), /** Object is not found. */ - OBJECT_NOT_FOUND(SC_NOT_FOUND), + OBJECT_NOT_FOUND(SC_NOT_FOUND, "Object is not found"), /** Object is too large to show. */ - OBJECT_TOO_LARGE(SC_INTERNAL_SERVER_ERROR), + OBJECT_TOO_LARGE(SC_INTERNAL_SERVER_ERROR, "Object is too large to show"), /** Repository is not found. */ - REPOSITORY_NOT_FOUND(SC_NOT_FOUND), + REPOSITORY_NOT_FOUND(SC_NOT_FOUND, "Repository is not found"), /** Gitiles is not enabled for the repository. */ - SERVICE_NOT_ENABLED(SC_FORBIDDEN), + SERVICE_NOT_ENABLED(SC_FORBIDDEN, "Gitiles is not enabled for the repository"), /** GitWeb URL cannot be converted to Gitiles URL. */ - UNSUPPORTED_GITWEB_URL(SC_GONE), + UNSUPPORTED_GITWEB_URL(SC_GONE, "GitWeb URL cannot be converted to Gitiles URL"), /** The specified object's type is not supported. */ - UNSUPPORTED_OBJECT_TYPE(SC_NOT_FOUND), + UNSUPPORTED_OBJECT_TYPE(SC_NOT_FOUND, "The specified object's type is not supported"), /** The specified format type is not supported. */ - UNSUPPORTED_RESPONSE_FORMAT(SC_BAD_REQUEST), + UNSUPPORTED_RESPONSE_FORMAT(SC_BAD_REQUEST, "The specified format type is not supported"), /** The specified revision names are not supported. */ - UNSUPPORTED_REVISION_NAMES(SC_BAD_REQUEST); + UNSUPPORTED_REVISION_NAMES(SC_BAD_REQUEST, "The specified revision names are not supported"), + /** Internal server error. */ + INTERNAL_SERVER_ERROR(SC_INTERNAL_SERVER_ERROR, "Internal server error"); private final int httpStatusCode; + private final String message; - FailureReason(int httpStatusCode) { + FailureReason(int httpStatusCode, String message) { this.httpStatusCode = httpStatusCode; + this.message = message; } public int getHttpStatusCode() { return httpStatusCode; } + + public String getMessage() { + return message; + } } }
diff --git a/java/com/google/gitiles/GitilesView.java b/java/com/google/gitiles/GitilesView.java index 02020ee..e721c26 100644 --- a/java/com/google/gitiles/GitilesView.java +++ b/java/com/google/gitiles/GitilesView.java
@@ -228,7 +228,7 @@ public Builder setOldRevision(Revision revision) { if (type != Type.DIFF && type != Type.LOG) { revision = firstNonNull(revision, Revision.NULL); - checkState(revision == Revision.NULL, "cannot set old revision on %s view", type); + checkState(Revision.isNull(revision), "cannot set old revision on %s view", type); } this.oldRevision = revision; return this; @@ -399,7 +399,7 @@ } private void checkRevision() { - checkView(revision != Revision.NULL, "missing revision on %s view", type); + checkView(!Revision.isNull(revision), "missing revision on %s view", type); checkRepositoryIndex(); } @@ -427,7 +427,7 @@ private void checkRootedDoc() { checkView(hostName != null, "missing hostName on %s view", type); checkView(servletPath != null, "missing hostName on %s view", type); - checkView(revision != Revision.NULL, "missing revision on %s view", type); + checkView(!Revision.isNull(revision), "missing revision on %s view", type); checkView(path != null, "missing path on %s view", type); } } @@ -561,7 +561,7 @@ } public String getRevisionRange() { - if (oldRevision == Revision.NULL) { + if (Revision.isNull(oldRevision)) { if (type == Type.LOG || type == Type.DIFF) { // For types that require two revisions, NULL indicates the empty // tree/commit. @@ -676,9 +676,9 @@ break; case LOG: url.append(repositoryName).append("/+log"); - if (revision != Revision.NULL) { + if (!Revision.isNull(revision)) { url.append('/'); - if (oldRevision != Revision.NULL) { + if (!Revision.isNull(oldRevision)) { url.append(oldRevision.getName()).append(".."); } url.append(revision.getName()); @@ -764,10 +764,10 @@ // separate links in "old..new". breadcrumbs.add(breadcrumb(getRevisionRange(), diff().copyFrom(this).setPathPart(""))); } else if (type == Type.LOG) { - if (revision != Revision.NULL) { + if (!Revision.isNull(revision)) { // TODO(dborowitz): Add something in the navigation area (probably not // a breadcrumb) to allow switching between /+log/ and /+/. - if (oldRevision == Revision.NULL) { + if (Revision.isNull(oldRevision)) { breadcrumbs.add(breadcrumb(revision.getName(), log().copyFrom(this).setPathPart(null))); } else { breadcrumbs.add(breadcrumb(getRevisionRange(), log().copyFrom(this).setPathPart(null))); @@ -776,7 +776,7 @@ breadcrumbs.add(breadcrumb(Constants.HEAD, log().copyFrom(this))); } path = Strings.emptyToNull(path); - } else if (revision != Revision.NULL) { + } else if (!Revision.isNull(revision)) { breadcrumbs.add(breadcrumb(revision.getName(), revision().copyFrom(this))); } if (path != null) { @@ -850,7 +850,7 @@ } private static boolean isFirstParent(Revision rev1, Revision rev2) { - return rev2 == Revision.NULL + return Revision.isNull(rev2) || rev2.getName().equals(rev1.getName() + "^") || rev2.getName().equals(rev1.getName() + "~1"); }
diff --git a/java/com/google/gitiles/HostIndexServlet.java b/java/com/google/gitiles/HostIndexServlet.java index 8b8a252..6851688 100644 --- a/java/com/google/gitiles/HostIndexServlet.java +++ b/java/com/google/gitiles/HostIndexServlet.java
@@ -17,12 +17,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.gitiles.GitilesRequestFailureException.FailureReason; import com.google.gson.reflect.TypeToken; -import com.google.template.soy.data.SoyListData; -import com.google.template.soy.data.SoyMapData; import com.google.template.soy.data.restricted.NullData; import java.io.IOException; import java.io.Writer; @@ -66,12 +65,15 @@ return descs; } - private SoyMapData toSoyMapData( + private Map<String, Object> toMapData( RepositoryDescription desc, @Nullable String prefix, GitilesView view) { - return new SoyMapData( - "name", stripPrefix(prefix, desc.name), - "description", Strings.nullToEmpty(desc.description), - "url", GitilesView.repositoryIndex().copyFrom(view).setRepositoryName(desc.name).toUrl()); + return ImmutableMap.<String, Object>builder() + .put("name", stripPrefix(prefix, desc.name)) + .put("description", Strings.nullToEmpty(desc.description)) + .put( + "url", + GitilesView.repositoryIndex().copyFrom(view).setRepositoryName(desc.name).toUrl()) + .build(); } @Override @@ -111,10 +113,10 @@ return; } - SoyListData repos = new SoyListData(); + ImmutableList.Builder<Map<String, Object>> repos = ImmutableList.builder(); for (RepositoryDescription desc : descs.values()) { if (prefix == null || desc.name.startsWith(prefix)) { - repos.add(toSoyMapData(desc, prefix, view)); + repos.add(toMapData(desc, prefix, view)); } } @@ -132,11 +134,11 @@ "hostName", hostName, "breadcrumbs", - breadcrumbs != null ? new SoyListData(breadcrumbs) : NullData.INSTANCE, + breadcrumbs != null ? breadcrumbs : NullData.INSTANCE, "prefix", prefix != null ? prefix + '/' : "", "repositories", - repos)); + repos.build())); } @Override
diff --git a/java/com/google/gitiles/HtmlDiffFormatter.java b/java/com/google/gitiles/HtmlDiffFormatter.java index 44d2c18..1467d42 100644 --- a/java/com/google/gitiles/HtmlDiffFormatter.java +++ b/java/com/google/gitiles/HtmlDiffFormatter.java
@@ -111,7 +111,9 @@ renderer .newRenderer("gitiles.diffHeader") .setData(ImmutableMap.of("firstParts", parts, "rest", rest, "fileIndex", fileIndex)) - .render() + .renderHtml() + .get() + .toString() .getBytes(UTF_8)); }
diff --git a/java/com/google/gitiles/LogServlet.java b/java/com/google/gitiles/LogServlet.java index a91aeab..c738f0e 100644 --- a/java/com/google/gitiles/LogServlet.java +++ b/java/com/google/gitiles/LogServlet.java
@@ -71,6 +71,9 @@ private static final String FOLLOW_PARAM = "follow"; private static final String NAME_STATUS_PARAM = "name-status"; private static final String PRETTY_PARAM = "pretty"; + private static final String TOPO_ORDER_PARAM = "topo-order"; + private static final String REVERSE_PARAM = "reverse"; + private static final String FIRST_PARENT_PARAM = "first-parent"; private static final int DEFAULT_LIMIT = 100; private static final int MAX_LIMIT = 10000; @@ -113,7 +116,7 @@ } String title = "Log - "; - if (view.getOldRevision() != Revision.NULL) { + if (!Revision.isNull(view.getOldRevision())) { title += view.getRevisionRange(); } else { title += view.getRevision().getName(); @@ -175,7 +178,7 @@ private static GitilesView getView(HttpServletRequest req, Repository repo) throws IOException { GitilesView view = ViewFilter.getView(req); - if (view.getRevision() != Revision.NULL) { + if (!Revision.isNull(view.getRevision())) { return view; } Ref headRef = repo.exactRef(Constants.HEAD); @@ -223,9 +226,18 @@ private static RevWalk newWalk(Repository repo, GitilesView view, GitilesAccess access) throws MissingObjectException, IOException { RevWalk walk = new RevWalk(repo); + if (isTrue(view, FIRST_PARENT_PARAM)) { + walk.setFirstParent(true); + } + if (isTrue(view, TOPO_ORDER_PARAM)) { + walk.sort(RevSort.TOPO, true); + } + if (isTrue(view, REVERSE_PARAM)) { + walk.sort(RevSort.REVERSE, true); + } try { walk.markStart(walk.parseCommit(view.getRevision().getId())); - if (view.getOldRevision() != Revision.NULL) { + if (!Revision.isNull(view.getOldRevision())) { walk.markUninteresting(walk.parseCommit(view.getOldRevision().getId())); } } catch (IncorrectObjectTypeException iote) { @@ -233,12 +245,6 @@ } setTreeFilter(walk, view, access); setRevFilter(walk, view); - if (isTrue(view, "topo-order")) { - walk.sort(RevSort.TOPO, true); - } - if (isTrue(view, "reverse")) { - walk.sort(RevSort.REVERSE, true); - } return walk; }
diff --git a/java/com/google/gitiles/LogSoyData.java b/java/com/google/gitiles/LogSoyData.java index 8bc243f..dc782ab 100644 --- a/java/com/google/gitiles/LogSoyData.java +++ b/java/com/google/gitiles/LogSoyData.java
@@ -22,7 +22,8 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gitiles.CommitData.Field; -import com.google.template.soy.tofu.SoyTofu; +import com.google.template.soy.data.LoggingAdvisingAppendable; +import com.google.template.soy.jbcsrc.api.SoySauce; import java.io.IOException; import java.io.Writer; import java.util.Map; @@ -74,35 +75,43 @@ variant = firstNonNull(config.getString("logFormat", pretty, "variant"), pretty); } + private void renderHtml(SoySauce.Renderer renderer, LoggingAdvisingAppendable out) + throws IOException { + if (!renderer.renderHtml(out).result().isDone()) { + throw new IOException("failed to render HTML"); + } + } + public void renderStreaming( Paginator paginator, @Nullable String revision, Renderer renderer, - Writer out, + Writer writer, DateFormatter df, FooterBehavior footerBehavior) throws IOException { - renderer - .newRenderer("gitiles.logEntriesHeader") - .setData(toHeaderSoyData(paginator, revision)) - .render(out); - out.flush(); + LoggingAdvisingAppendable out = LoggingAdvisingAppendable.delegating(writer); + renderHtml( + renderer + .newRenderer("gitiles.logEntriesHeader") + .setData(toHeaderSoyData(paginator, revision)), + out); - SoyTofu.Renderer entryRenderer = renderer.newRenderer("gitiles.logEntryWrapper"); + SoySauce.Renderer entryRenderer = renderer.newRenderer("gitiles.logEntryWrapper"); boolean renderedEntries = false; for (RevCommit c : paginator) { - entryRenderer.setData(toEntrySoyData(paginator, c, df)).render(out); - out.flush(); + renderHtml(entryRenderer.setData(toEntrySoyData(paginator, c, df)), out); renderedEntries = true; } if (!renderedEntries) { - renderer.newRenderer("gitiles.emptyLog").render(out); + renderHtml(renderer.newRenderer("gitiles.emptyLog"), out); } - renderer - .newRenderer("gitiles.logEntriesFooter") - .setData(toFooterSoyData(paginator, revision, footerBehavior)) - .render(out); + renderHtml( + renderer + .newRenderer("gitiles.logEntriesFooter") + .setData(toFooterSoyData(paginator, revision, footerBehavior)), + out); } private Map<String, Object> toHeaderSoyData(Paginator paginator, @Nullable String revision) { @@ -176,14 +185,14 @@ private GitilesView.Builder copyAndCanonicalizeView(String revision) { // Canonicalize the view by using full SHAs. GitilesView.Builder copy = GitilesView.log().copyFrom(view); - if (view.getRevision() != Revision.NULL) { + if (!Revision.isNull(view.getRevision())) { copy.setRevision(view.getRevision()); } else if (revision != null) { copy.setRevision(Revision.named(revision)); } else { copy.setRevision(Revision.NULL); } - if (view.getOldRevision() != Revision.NULL) { + if (!Revision.isNull(view.getOldRevision())) { copy.setOldRevision(view.getOldRevision()); } return copy;
diff --git a/java/com/google/gitiles/PathServlet.java b/java/com/google/gitiles/PathServlet.java index 34e0a76..df24062 100644 --- a/java/com/google/gitiles/PathServlet.java +++ b/java/com/google/gitiles/PathServlet.java
@@ -95,6 +95,7 @@ EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE), GITLINK(FileMode.GITLINK); + @SuppressWarnings("ImmutableEnumChecker") // FileMode is effectively immutable. private final FileMode mode; FileType(FileMode mode) {
diff --git a/java/com/google/gitiles/Renderer.java b/java/com/google/gitiles/Renderer.java index 9ead8bf..dbd5590 100644 --- a/java/com/google/gitiles/Renderer.java +++ b/java/com/google/gitiles/Renderer.java
@@ -29,7 +29,7 @@ import com.google.common.html.types.LegacyConversions; import com.google.common.io.ByteStreams; import com.google.common.net.HttpHeaders; -import com.google.template.soy.tofu.SoyTofu; +import com.google.template.soy.jbcsrc.api.SoySauce; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -60,6 +60,7 @@ "Common.soy", "DiffDetail.soy", "Doc.soy", + "Error.soy", "HostIndex.soy", "LogDetail.soy", "ObjectDetail.soy", @@ -146,16 +147,17 @@ return h.hash(); } - public String render(String templateName, Map<String, ?> soyData) { - return newRenderer(templateName).setData(soyData).render(); + public String renderHtml(String templateName, Map<String, ?> soyData) { + return newRenderer(templateName).setData(soyData).renderHtml().get().toString(); } - void render( + void renderHtml( HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException { res.setContentType("text/html"); res.setCharacterEncoding("UTF-8"); - byte[] data = newRenderer(templateName).setData(soyData).render().getBytes(UTF_8); + byte[] data = + newRenderer(templateName).setData(soyData).renderHtml().get().toString().getBytes(UTF_8); if (BaseServlet.acceptsGzipEncoding(req)) { res.addHeader(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING); res.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip"); @@ -165,15 +167,15 @@ res.getOutputStream().write(data); } - OutputStream renderStreaming(HttpServletResponse res, String templateName, Map<String, ?> soyData) - throws IOException { - return renderStreaming(res, false, templateName, soyData); + OutputStream renderHtmlStreaming( + HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException { + return renderHtmlStreaming(res, false, templateName, soyData); } - OutputStream renderStreaming( + OutputStream renderHtmlStreaming( HttpServletResponse res, boolean gzip, String templateName, Map<String, ?> soyData) throws IOException { - String html = newRenderer(templateName).setData(soyData).render(); + String html = newRenderer(templateName).setData(soyData).renderHtml().get().toString(); int id = html.indexOf(PLACEHOLDER); checkArgument(id >= 0, "Template must contain %s", PLACEHOLDER); @@ -210,17 +212,17 @@ }; } - SoyTofu.Renderer newRenderer(String templateName) { + SoySauce.Renderer newRenderer(String templateName) { ImmutableMap.Builder<String, Object> staticUrls = ImmutableMap.builder(); for (String key : STATIC_URL_GLOBALS.keySet()) { staticUrls.put( key.replaceFirst("^gitiles\\.", ""), LegacyConversions.riskilyAssumeTrustedResourceUrl(globals.get(key))); } - return getTofu() - .newRenderer(templateName) - .setIjData(ImmutableMap.of("staticUrls", staticUrls.build())); + return getSauce() + .renderTemplate(templateName) + .setIj(ImmutableMap.of("staticUrls", staticUrls.build())); } - protected abstract SoyTofu getTofu(); + protected abstract SoySauce getSauce(); }
diff --git a/java/com/google/gitiles/Revision.java b/java/com/google/gitiles/Revision.java index 3fc8d37..02a04f8 100644 --- a/java/com/google/gitiles/Revision.java +++ b/java/com/google/gitiles/Revision.java
@@ -88,6 +88,11 @@ this.peeledType = peeledType; } + @SuppressWarnings("ReferenceEquality") + public static boolean isNull(Revision r) { + return r == NULL; + } + public String getName() { return name; }
diff --git a/java/com/google/gitiles/RevisionParser.java b/java/com/google/gitiles/RevisionParser.java index 89311a3..b736f7f 100644 --- a/java/com/google/gitiles/RevisionParser.java +++ b/java/com/google/gitiles/RevisionParser.java
@@ -212,7 +212,10 @@ private static boolean isValidRevision(String revision) { // Disallow some uncommon but valid revision expressions that either we // don't support or we represent differently in our URLs. - return !revision.contains(":") && !revision.contains("^{") && !revision.contains("@"); + return !revision.contains(":") + && !revision.contains("^{") + && !revision.contains("@{") + && !revision.equals("@"); } private boolean isVisible(RevWalk walk, Result result) throws IOException { @@ -225,7 +228,7 @@ if (!cache.isVisible(repo, walk, access, id)) { return false; } - if (result.getOldRevision() != null && result.getOldRevision() != Revision.NULL) { + if (result.getOldRevision() != null && !Revision.isNull(result.getOldRevision())) { return cache.isVisible(repo, walk, access, result.getOldRevision().getId(), id); } return true;
diff --git a/java/com/google/gitiles/ViewFilter.java b/java/com/google/gitiles/ViewFilter.java index f400ff0..25d6d7e 100644 --- a/java/com/google/gitiles/ViewFilter.java +++ b/java/com/google/gitiles/ViewFilter.java
@@ -119,12 +119,12 @@ } private boolean normalize(GitilesView.Builder view, HttpServletResponse res) throws IOException { - if (view.getOldRevision() != Revision.NULL) { + if (!Revision.isNull(view.getOldRevision())) { return false; } Revision r = view.getRevision(); Revision nr = Revision.normalizeParentExpressions(r); - if (r != nr) { + if (!r.equals(nr)) { res.sendRedirect(view.setRevision(nr).toUrl()); return true; }
diff --git a/java/com/google/gitiles/VisibilityCache.java b/java/com/google/gitiles/VisibilityCache.java index fbb3a45..1c6b785 100644 --- a/java/com/google/gitiles/VisibilityCache.java +++ b/java/com/google/gitiles/VisibilityCache.java
@@ -22,6 +22,7 @@ import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.Constants.R_TAGS; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -34,17 +35,16 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevWalk; /** Cache of per-user object visibility. */ public class VisibilityCache { + private static class Key { private final Object user; private final String repositoryName; @@ -83,25 +83,48 @@ } private final Cache<Key, Boolean> cache; - private final boolean topoSort; + private final VisibilityChecker checker; public static CacheBuilder<Object, Object> defaultBuilder() { return CacheBuilder.newBuilder().maximumSize(1 << 10).expireAfterWrite(30, TimeUnit.MINUTES); } - public VisibilityCache(boolean topoSort) { - this(topoSort, defaultBuilder()); + public VisibilityCache() { + this(new VisibilityChecker(), defaultBuilder()); } - public VisibilityCache(boolean topoSort, CacheBuilder<Object, Object> builder) { + public VisibilityCache(CacheBuilder<Object, Object> builder) { + this(new VisibilityChecker(), builder); + } + + /** + * Use the constructors with a boolean parameter (e.g. {@link #VisibilityCache()}). The + * default visibility checker should cover all common use cases. + * + * <p>This constructor is useful to use a checker with additional logging or metrics collection, + * for example. + */ + public VisibilityCache(VisibilityChecker checker) { + this(checker, defaultBuilder()); + } + + /** + * Use the constructors with a boolean parameter (e.g. {@link #VisibilityCache()}). The + * default visibility checker should cover all common use cases. + * + * <p>This constructor is useful to use a checker with additional logging or metrics collection, + * for example. + */ + public VisibilityCache(VisibilityChecker checker, CacheBuilder<Object, Object> builder) { this.cache = builder.build(); - this.topoSort = topoSort; + this.checker = checker; } public Cache<?, Boolean> getCache() { return cache; } + @VisibleForTesting boolean isVisible( final Repository repo, final RevWalk walk, @@ -126,8 +149,7 @@ } } - private boolean isVisible( - Repository repo, RevWalk walk, ObjectId id, Collection<ObjectId> knownReachable) + boolean isVisible(Repository repo, RevWalk walk, ObjectId id, Collection<ObjectId> knownReachable) throws IOException { RevCommit commit; try { @@ -137,23 +159,18 @@ } RefDatabase refDb = repo.getRefDatabase(); - - // If any reference directly points at the requested object, permit display. Common for displays - // of pending patch sets in Gerrit Code Review, or bookmarks to the commit a tag points at. - for (Ref ref : repo.getRefDatabase().getRefs()) { - ref = repo.getRefDatabase().peel(ref); - if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) { - return true; - } + if (checker.isTipOfBranch(refDb, id)) { + return true; } // Check heads first under the assumption that most requests are for refs close to a head. Tags // tend to be much further back in history and just clutter up the priority queue in the common // case. - return isReachableFrom(walk, commit, knownReachable) - || isReachableFromRefs(walk, commit, refDb.getRefsByPrefix(R_HEADS).stream()) - || isReachableFromRefs(walk, commit, refDb.getRefsByPrefix(R_TAGS).stream()) - || isReachableFromRefs(walk, commit, refDb.getRefs().stream().filter(r -> otherRefs(r))); + return checker.isReachableFrom("knownReachable", walk, commit, knownReachable) + || isReachableFromRefs("heads", walk, commit, refDb.getRefsByPrefix(R_HEADS).stream()) + || isReachableFromRefs("tags", walk, commit, refDb.getRefsByPrefix(R_TAGS).stream()) + || isReachableFromRefs( + "other", walk, commit, refDb.getRefs().stream().filter(r -> otherRefs(r))); } private static boolean refStartsWith(Ref ref, String prefix) { @@ -166,43 +183,14 @@ || refStartsWith(r, "refs/changes/")); } - private boolean isReachableFromRefs(RevWalk walk, RevCommit commit, Stream<Ref> refs) + private boolean isReachableFromRefs(String desc, RevWalk walk, RevCommit commit, Stream<Ref> refs) throws IOException { return isReachableFrom( - walk, commit, refs.map(r -> firstNonNull(r.getPeeledObjectId(), r.getObjectId()))); + desc, walk, commit, refs.map(r -> firstNonNull(r.getPeeledObjectId(), r.getObjectId()))); } - private boolean isReachableFrom(RevWalk walk, RevCommit commit, Stream<ObjectId> ids) + private boolean isReachableFrom(String desc, RevWalk walk, RevCommit commit, Stream<ObjectId> ids) throws IOException { - return isReachableFrom(walk, commit, ids.collect(toList())); - } - - private boolean isReachableFrom(RevWalk walk, RevCommit commit, Collection<ObjectId> ids) - throws IOException { - if (ids.isEmpty()) { - return false; - } - walk.reset(); - if (topoSort) { - walk.sort(RevSort.TOPO); - } - walk.markStart(commit); - for (ObjectId id : ids) { - markUninteresting(walk, id); - } - // If the commit is reachable from any given tip, it will appear to be - // uninteresting to the RevWalk and no output will be produced. - return walk.next() == null; - } - - private static void markUninteresting(RevWalk walk, ObjectId id) throws IOException { - if (id == null) { - return; - } - try { - walk.markUninteresting(walk.parseCommit(id)); - } catch (IncorrectObjectTypeException | MissingObjectException e) { - // Do nothing, doesn't affect reachability. - } + return checker.isReachableFrom(desc, walk, commit, ids.collect(toList())); } }
diff --git a/java/com/google/gitiles/VisibilityChecker.java b/java/com/google/gitiles/VisibilityChecker.java new file mode 100644 index 0000000..899d1f1 --- /dev/null +++ b/java/com/google/gitiles/VisibilityChecker.java
@@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019, Google LLC. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.gitiles; + +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.Collection; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Checks for object visibility + * + * <p>Objects are visible if they are reachable from any of the references visible to the user. + */ +public class VisibilityChecker { + + /** + * Check if any of the refs in {@code refDb} points to the object {@code id}. + * + * @param refDb a reference database + * @param id object we are looking for + * @return true if the any of the references in the db points directly to the id + * @throws IOException the reference space cannot be accessed + */ + protected boolean isTipOfBranch(RefDatabase refDb, ObjectId id) throws IOException { + // If any reference directly points at the requested object, permit display. Common for displays + // of pending patch sets in Gerrit Code Review, or bookmarks to the commit a tag points at. + return !refDb.getTipsWithSha1(id).isEmpty(); + } + + /** + * Check if {@code commit} is reachable starting from {@code starters}. + * + * @param description Description of the ids (e.g. "heads"). Mainly for tracing. + * @param walk The walk to use for the reachability check + * @param commit The starting commit. It *MUST* come from the walk in use + * @param starters visible commits. Anything reachable from these commits is visible. Missing ids + * or ids referring to other kinds of objects are ignored. + * @return true if we can get to {@code commit} from the {@code starters} + * @throws IOException a pack file or loose object could not be read + */ + protected boolean isReachableFrom( + String description, RevWalk walk, RevCommit commit, Collection<ObjectId> starters) + throws IOException { + if (starters.isEmpty()) { + return false; + } + + ImmutableList<RevCommit> startCommits = objectIdsToCommits(walk, starters); + if (startCommits.isEmpty()) { + return false; + } + + return !walk.createReachabilityChecker() + .areAllReachable(ImmutableList.of(commit), startCommits) + .isPresent(); + } + + private static ImmutableList<RevCommit> objectIdsToCommits(RevWalk walk, Collection<ObjectId> ids) + throws IOException { + ImmutableList.Builder<RevCommit> commits = ImmutableList.builder(); + for (ObjectId id : ids) { + try { + commits.add(walk.parseCommit(id)); + } catch (MissingObjectException e) { + // TODO(ifrade): ResolveParser has already checked that the object exists in the repo. + // Report as AssertionError. + } catch (IncorrectObjectTypeException e) { + // Ignore, doesn't affect commit reachability + } + } + return commits.build(); + } +}
diff --git a/java/com/google/gitiles/blame/BlameServlet.java b/java/com/google/gitiles/blame/BlameServlet.java index 0bc061b..8614c6a 100644 --- a/java/com/google/gitiles/blame/BlameServlet.java +++ b/java/com/google/gitiles/blame/BlameServlet.java
@@ -35,8 +35,6 @@ import com.google.gitiles.blame.cache.Region; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import com.google.template.soy.data.SoyListData; -import com.google.template.soy.data.SoyMapData; import java.io.IOException; import java.util.List; import java.util.Map; @@ -205,21 +203,21 @@ private static final ImmutableList<String> CLASSES = ImmutableList.of("Blame-region--bg1", "Blame-region--bg2"); - private static final ImmutableList<SoyMapData> NULLS; + private static final ImmutableList<ImmutableMap<String, Object>> NULLS; static { - ImmutableList.Builder<SoyMapData> nulls = ImmutableList.builder(); + ImmutableList.Builder<ImmutableMap<String, Object>> nulls = ImmutableList.builder(); for (String clazz : CLASSES) { - nulls.add(new SoyMapData("class", clazz)); + nulls.add(ImmutableMap.of("class", clazz)); } NULLS = nulls.build(); } - private static SoyListData toSoyData( + private static List<ImmutableMap<String, Object>> toSoyData( GitilesView view, ObjectReader reader, List<Region> regions, DateFormatter df) throws IOException { Map<ObjectId, String> abbrevShas = Maps.newHashMap(); - SoyListData result = new SoyListData(); + ImmutableList.Builder<ImmutableMap<String, Object>> result = ImmutableList.builder(); for (int i = 0; i < regions.size(); i++) { Region r = regions.get(i); @@ -234,7 +232,7 @@ abbrevSha = reader.abbreviate(r.getSourceCommit()).name(); abbrevShas.put(r.getSourceCommit(), abbrevSha); } - Map<String, Object> e = Maps.newHashMapWithExpectedSize(6); + ImmutableMap.Builder<String, Object> e = ImmutableMap.builder(); e.put("abbrevSha", abbrevSha); String blameParent = ""; String blameText = "blame"; @@ -262,7 +260,7 @@ .toUrl()); e.put("author", CommitSoyData.toSoyData(r.getSourceAuthor(), df)); e.put("class", CLASSES.get(c)); - result.add(e); + result.add(e.build()); } // Pad the list with null regions so we can iterate in parallel in the // template. We can't do this by maintaining an index variable into the @@ -272,6 +270,6 @@ result.add(NULLS.get(c)); } } - return result; + return result.build(); } }
diff --git a/java/com/google/gitiles/doc/MarkdownConfig.java b/java/com/google/gitiles/doc/MarkdownConfig.java index b4add94..4758654 100644 --- a/java/com/google/gitiles/doc/MarkdownConfig.java +++ b/java/com/google/gitiles/doc/MarkdownConfig.java
@@ -78,7 +78,7 @@ if (safeHtml) { f = cfg.getStringList("markdown", null, "allowiframe"); } - allowAnyIFrame = f.length == 1 && StringUtils.toBooleanOrNull(f[0]) == Boolean.TRUE; + allowAnyIFrame = f.length == 1 && Boolean.TRUE.equals(StringUtils.toBooleanOrNull(f[0])); if (allowAnyIFrame) { allowIFrame = ImmutableList.of(); } else {
diff --git a/javatests/com/google/gitiles/DefaultErrorHandlingFilterTest.java b/javatests/com/google/gitiles/DefaultErrorHandlingFilterTest.java index b96e43d..0dc152a 100644 --- a/javatests/com/google/gitiles/DefaultErrorHandlingFilterTest.java +++ b/javatests/com/google/gitiles/DefaultErrorHandlingFilterTest.java
@@ -3,7 +3,9 @@ import static com.google.common.truth.Truth.assertThat; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import com.google.common.collect.ImmutableList; import com.google.gitiles.GitilesRequestFailureException.FailureReason; +import java.net.URL; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -19,7 +21,12 @@ @Before public void setUp() { - mf.serve("*").through(new DefaultErrorHandlingFilter()).with(new TestServlet()); + mf.serve("*") + .through( + new DefaultErrorHandlingFilter( + new DefaultRenderer( + GitilesServlet.STATIC_PREFIX, ImmutableList.<URL>of(), "test site"))) + .with(new TestServlet()); } @Test
diff --git a/javatests/com/google/gitiles/HostIndexServletTest.java b/javatests/com/google/gitiles/HostIndexServletTest.java index d8332d1..a79ee8c 100644 --- a/javatests/com/google/gitiles/HostIndexServletTest.java +++ b/javatests/com/google/gitiles/HostIndexServletTest.java
@@ -18,9 +18,8 @@ import static com.google.gitiles.TestGitilesUrls.URLS; import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.common.collect.ImmutableList; import com.google.gson.reflect.TypeToken; -import com.google.template.soy.data.SoyListData; -import com.google.template.soy.data.SoyMapData; import com.google.template.soy.data.restricted.NullData; import java.util.Map; import javax.servlet.http.HttpServletResponse; @@ -48,15 +47,16 @@ @Test public void rootHtml() throws Exception { - Map<String, ?> data = buildData("/"); + Map<String, Object> data = buildData("/"); assertThat(data).containsEntry("hostName", URLS.getHostName(null)); assertThat(data).containsEntry("breadcrumbs", NullData.INSTANCE); assertThat(data).containsEntry("prefix", ""); - SoyListData repos = (SoyListData) data.get("repositories"); + ImmutableList<Map<String, Object>> repos = + (ImmutableList<Map<String, Object>>) data.get("repositories"); assertThat(repos).hasSize(1); - SoyMapData ent = (SoyMapData) repos.get(0); + Map<String, Object> ent = repos.get(0); assertThat(ent.get("name").toString()).isEqualTo(NAME); assertThat(ent.get("url").toString()).isEqualTo("/b/" + NAME + "/"); } @@ -67,13 +67,15 @@ assertThat(data).containsEntry("hostName", URLS.getHostName(null) + "/foo"); assertThat(data).containsEntry("prefix", "foo/"); - SoyListData breadcrumbs = (SoyListData) data.get("breadcrumbs"); - assertThat(breadcrumbs.length()).isEqualTo(2); + ImmutableList<Map<String, Object>> breadcrumbs = + (ImmutableList<Map<String, Object>>) data.get("breadcrumbs"); + assertThat(breadcrumbs.size()).isEqualTo(2); - SoyListData repos = (SoyListData) data.get("repositories"); - assertThat(repos.length()).isEqualTo(1); + ImmutableList<Map<String, Object>> repos = + (ImmutableList<Map<String, Object>>) data.get("repositories"); + assertThat(repos.size()).isEqualTo(1); - SoyMapData ent = (SoyMapData) repos.get(0); + Map<String, Object> ent = repos.get(0); assertThat(ent.get("name").toString()).isEqualTo("bar/repo"); assertThat(ent.get("url").toString()).isEqualTo("/b/" + NAME + "/"); } @@ -84,13 +86,15 @@ assertThat(data).containsEntry("hostName", URLS.getHostName(null) + "/foo/bar"); assertThat(data).containsEntry("prefix", "foo/bar/"); - SoyListData breadcrumbs = (SoyListData) data.get("breadcrumbs"); - assertThat(breadcrumbs.length()).isEqualTo(3); + ImmutableList<Map<String, Object>> breadcrumbs = + (ImmutableList<Map<String, Object>>) data.get("breadcrumbs"); + assertThat(breadcrumbs.size()).isEqualTo(3); - SoyListData repos = (SoyListData) data.get("repositories"); - assertThat(repos.length()).isEqualTo(1); + ImmutableList<Map<String, Object>> repos = + (ImmutableList<Map<String, Object>>) data.get("repositories"); + assertThat(repos.size()).isEqualTo(1); - SoyMapData ent = (SoyMapData) repos.get(0); + Map<String, Object> ent = repos.get(0); assertThat(ent.get("name").toString()).isEqualTo("repo"); assertThat(ent.get("url").toString()).isEqualTo("/b/" + NAME + "/"); }
diff --git a/javatests/com/google/gitiles/LogServletTest.java b/javatests/com/google/gitiles/LogServletTest.java index 06ddaf4..7232439 100644 --- a/javatests/com/google/gitiles/LogServletTest.java +++ b/javatests/com/google/gitiles/LogServletTest.java
@@ -67,6 +67,19 @@ } @Test + public void firstParentLog() throws Exception { + RevCommit p1 = repo.update("master", repo.commit().add("foo", "foo\n")); + RevCommit p2 = repo.update("master", repo.commit().add("foo", "foo2\n")); + RevCommit c = repo.update("master", repo.commit().parent(p1).parent(p2).add("foo", "foo3\n")); + + Log response = buildJson(LOG, "/repo/+log/master", "first-parent"); + assertThat(response.log).hasSize(2); + + verifyJsonCommit(response.log.get(0), c); + verifyJsonCommit(response.log.get(1), p1); + } + + @Test public void follow() throws Exception { String contents = "contents"; RevCommit c1 = repo.branch("master").commit().add("foo", contents).create();
diff --git a/javatests/com/google/gitiles/MoreAssert.java b/javatests/com/google/gitiles/MoreAssert.java index 3f79874..3814be5 100644 --- a/javatests/com/google/gitiles/MoreAssert.java +++ b/javatests/com/google/gitiles/MoreAssert.java
@@ -15,13 +15,10 @@ /** Assertion methods for Gitiles. */ public class MoreAssert { - private MoreAssert() {} - /** Simple version of assertThrows that will be introduced in JUnit 4.13. */ public static <T extends Throwable> T assertThrows(Class<T> expected, ThrowingRunnable r) { try { r.run(); - throw new AssertionError("Expected " + expected.getSimpleName() + " to be thrown"); } catch (Throwable actual) { if (expected.isAssignableFrom(actual.getClass())) { @SuppressWarnings("unchecked") @@ -32,9 +29,12 @@ "Expected " + expected.getSimpleName() + ", but got " + actual.getClass().getSimpleName(), actual); } + throw new AssertionError("Expected " + expected.getSimpleName() + " to be thrown"); } public interface ThrowingRunnable { void run() throws Throwable; } + + private MoreAssert() {} }
diff --git a/javatests/com/google/gitiles/PathServletTest.java b/javatests/com/google/gitiles/PathServletTest.java index 998ede1..4311833 100644 --- a/javatests/com/google/gitiles/PathServletTest.java +++ b/javatests/com/google/gitiles/PathServletTest.java
@@ -102,6 +102,34 @@ } @Test + public void fileWithMaxLines() throws Exception { + int MAX_LINE_COUNT = 50000; + StringBuilder contentBuilder = new StringBuilder(); + for (int i = 1; i < MAX_LINE_COUNT; i++) { + contentBuilder.append("\n"); + } + repo.branch("master").commit().add("bar", contentBuilder.toString()).create(); + + Map<String, ?> data = buildData("/repo/+/master/bar"); + SoyListData lines = (SoyListData) getBlobData(data).get("lines"); + assertThat(lines.length()).isEqualTo(MAX_LINE_COUNT - 1); + } + + @Test + public void fileLargerThanSupportedLines() throws Exception { + int MAX_LINE_COUNT = 50000; + StringBuilder contentBuilder = new StringBuilder(); + for (int i = 1; i <= MAX_LINE_COUNT; i++) { + contentBuilder.append("\n"); + } + repo.branch("master").commit().add("largebar", contentBuilder.toString()).create(); + + Map<String, ?> data = buildData("/repo/+/master/largebar"); + SoyListData lines = (SoyListData) getBlobData(data).get("lines"); + assertThat(lines).isNull(); + } + + @Test public void largeFileHtml() throws Exception { int largeContentSize = BlobSoyData.MAX_FILE_SIZE + 1; repo.branch("master").commit().add("foo", generateContent(largeContentSize)).create();
diff --git a/javatests/com/google/gitiles/RevisionParserTest.java b/javatests/com/google/gitiles/RevisionParserTest.java index a204b61..16e76a7 100644 --- a/javatests/com/google/gitiles/RevisionParserTest.java +++ b/javatests/com/google/gitiles/RevisionParserTest.java
@@ -46,7 +46,7 @@ new RevisionParser( repo.getRepository(), new TestGitilesAccess(repo.getRepository()).forRequest(null), - new VisibilityCache(false, CacheBuilder.newBuilder().maximumSize(0))); + new VisibilityCache(CacheBuilder.newBuilder().maximumSize(0))); } @Test @@ -256,6 +256,14 @@ } @Test + public void parseEmailInRevision() throws Exception { + RevCommit c = repo.commit().create(); + repo.update("refs/experimental/[email protected]/foo", c); + assertThat(parser.parse("refs/experimental/[email protected]/foo")) + .isEqualTo(new Result(Revision.peeled("refs/experimental/[email protected]/foo", c))); + } + + @Test public void parseMissingSha() throws Exception { assertThat(parser.parse("deadbeef")).isNull(); assertThat(parser.parse("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")).isNull();
diff --git a/javatests/com/google/gitiles/ServletTest.java b/javatests/com/google/gitiles/ServletTest.java index c642a79..df349f0 100644 --- a/javatests/com/google/gitiles/ServletTest.java +++ b/javatests/com/google/gitiles/ServletTest.java
@@ -94,7 +94,7 @@ return buildHtml(path, true); } - protected Map<String, ?> buildData(String path) throws Exception { + protected Map<String, Object> buildData(String path) throws Exception { // Render the page through Soy to ensure templates are valid, then return // the Soy data for introspection. FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
diff --git a/javatests/com/google/gitiles/TestViewFilter.java b/javatests/com/google/gitiles/TestViewFilter.java index 68acffb..4f67efc 100644 --- a/javatests/com/google/gitiles/TestViewFilter.java +++ b/javatests/com/google/gitiles/TestViewFilter.java
@@ -65,7 +65,7 @@ new ViewFilter( new TestGitilesAccess(repo.getRepository()), TestGitilesUrls.URLS, - new VisibilityCache(false)); + new VisibilityCache()); MetaFilter mf = new MetaFilter(); for (Pattern p : ImmutableList.of(ROOT_REGEX, REPO_REGEX, REPO_PATH_REGEX)) {
diff --git a/javatests/com/google/gitiles/VisibilityCacheTest.java b/javatests/com/google/gitiles/VisibilityCacheTest.java index f22cf02..e271e71 100644 --- a/javatests/com/google/gitiles/VisibilityCacheTest.java +++ b/javatests/com/google/gitiles/VisibilityCacheTest.java
@@ -79,7 +79,7 @@ git.update("refs/tags/v0.1", commitA); } - visibilityCache = new VisibilityCache(true); + visibilityCache = new VisibilityCache(); walk = new RevWalk(repo); walk.setRetainBody(false); }
diff --git a/javatests/com/google/gitiles/VisibilityCheckerTest.java b/javatests/com/google/gitiles/VisibilityCheckerTest.java new file mode 100644 index 0000000..5470e52 --- /dev/null +++ b/javatests/com/google/gitiles/VisibilityCheckerTest.java
@@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019, Google LLC. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.gitiles; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class VisibilityCheckerTest { + private InMemoryRepository repo; + + private RevCommit baseCommit; + private RevCommit commit1; + private RevCommit commit2; + private RevCommit commitA; + private RevCommit commitB; + private RevCommit commitC; + + private VisibilityChecker visibilityChecker; + private RevWalk walk; + + @Before + public void setUp() throws Exception { + repo = new InMemoryRepository(new DfsRepositoryDescription()); + try (TestRepository<InMemoryRepository> git = new TestRepository<>(repo)) { + baseCommit = git.commit().message("baseCommit").create(); + commit1 = git.commit().parent(baseCommit).message("commit1").create(); + commit2 = git.commit().parent(commit1).message("commit2").create(); + + commitA = git.commit().parent(baseCommit).message("commitA").create(); + commitB = git.commit().parent(commitA).message("commitB").create(); + commitC = git.commit().parent(commitB).message("commitC").create(); + + git.update("master", commit2); + git.update("refs/tags/v0.1", commitA); + } + + visibilityChecker = new VisibilityChecker(); + walk = new RevWalk(repo); + walk.setRetainBody(false); + } + + @Test + public void isTip() throws IOException { + assertTrue(visibilityChecker.isTipOfBranch(repo.getRefDatabase(), commit2.getId())); + } + + @Test + public void isNotTip() throws IOException { + assertFalse(visibilityChecker.isTipOfBranch(repo.getRefDatabase(), commit1.getId())); + } + + @Test + public void reachableFromRef() throws IOException { + List<ObjectId> starters = Arrays.asList(commitC.getId()); + assertTrue( + visibilityChecker.isReachableFrom("test", walk, walk.parseCommit(commitB), starters)); + } + + @Test + public void unreachableFromRef() throws IOException { + List<ObjectId> starters = Arrays.asList(commit2.getId(), commitA.getId()); + assertFalse( + visibilityChecker.isReachableFrom("test", walk, walk.parseCommit(commitC), starters)); + } +}
diff --git a/lib/BUILD b/lib/BUILD index e1f4654..828ff38 100644 --- a/lib/BUILD +++ b/lib/BUILD
@@ -23,4 +23,9 @@ "guava", "guava-failureaccess", "prettify", + "ow2-asm", + "ow2-asm-analysis", + "ow2-asm-commons", + "ow2-asm-tree", + "ow2-asm-util", ]]
diff --git a/lib/soy/BUILD b/lib/soy/BUILD index 9bf2a39..f6f0b8e 100644 --- a/lib/soy/BUILD +++ b/lib/soy/BUILD
@@ -12,6 +12,11 @@ runtime_deps = [ "@html-types//jar", "@icu4j//jar", + "@ow2-asm-analysis//jar", + "@ow2-asm-commons//jar", + "@ow2-asm-tree//jar", + "@ow2-asm-util//jar", + "@ow2-asm//jar", "@protobuf//jar", ], )
diff --git a/resources/com/google/gitiles/templates/Error.soy b/resources/com/google/gitiles/templates/Error.soy new file mode 100644 index 0000000..39fcef3 --- /dev/null +++ b/resources/com/google/gitiles/templates/Error.soy
@@ -0,0 +1,39 @@ +// Copyright 2019 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +{namespace gitiles} + +/** + * HTML page for error. + */ +{template .error stricthtml="false"} + {@param? title: ?} /** page title. */ + {@param? menuEntries: ?} /** menu entries. */ + {@param? customVariant: ?} /** variant name for custom styling. */ + {@param? breadcrumbs: ?} /** map of breadcrumbs for header. */ +{call .header} + {param title: $title /} + {param menuEntries: $menuEntries /} + {param breadcrumbs: $breadcrumbs /} + {param customVariant: $customVariant /} +{/call} +<h1> + {msg desc="title"} + {$title} + {/msg} +</h1> + +{call .footer} + {param customVariant: $customVariant /} +{/call} +{/template}
diff --git a/tools/BUILD b/tools/BUILD index 6caa1da..ab2b9b3 100644 --- a/tools/BUILD +++ b/tools/BUILD
@@ -30,11 +30,12 @@ "-Xep:CannotMockFinalClass:ERROR", "-Xep:ClassCanBeStatic:ERROR", "-Xep:ClassNewInstance:ERROR", + "-Xep:DateFormatConstant:ERROR", "-Xep:DefaultCharset:ERROR", "-Xep:DoubleCheckedLocking:ERROR", - "-Xep:ElementsCountedInLoop:ERROR", "-Xep:DoubleCheckedLocking:ERROR", "-Xep:ElementsCountedInLoop:ERROR", + "-Xep:ElementsCountedInLoop:ERROR", "-Xep:EqualsHashCode:ERROR", "-Xep:EqualsIncompatibleType:ERROR", "-Xep:ExpectedExceptionChecker:ERROR", @@ -45,7 +46,7 @@ "-Xep:FunctionalInterfaceClash:ERROR", "-Xep:FutureReturnValueIgnored:ERROR", "-Xep:GetClassOnEnum:ERROR", - "-Xep:ImmutableAnnotationChecker:WARN", + "-Xep:ImmutableAnnotationChecker:ERROR", "-Xep:ImmutableEnumChecker:WARN", "-Xep:IncompatibleModifiers:ERROR", "-Xep:InjectOnConstructorOfAbstractClass:ERROR", @@ -69,7 +70,7 @@ "-Xep:PreconditionsInvalidPlaceholder:ERROR", "-Xep:ProtoFieldPreconditionsCheckNotNull:ERROR", "-Xep:ProtocolBufferOrdinal:ERROR", - "-Xep:ReferenceEquality:WARN", + "-Xep:ReferenceEquality:ERROR", "-Xep:RequiredModifiers:ERROR", "-Xep:ShortCircuitBoolean:ERROR", "-Xep:SimpleDateFormatConstant:ERROR",
diff --git a/version.bzl b/version.bzl index d361e3e..2094755 100644 --- a/version.bzl +++ b/version.bzl
@@ -4,4 +4,4 @@ # we currently have no stable releases, we use the "build number" scheme # described at: # https://www.mojohaus.org/versions-maven-plugin/version-rules.html -GITILES_VERSION = "0.2-11" +GITILES_VERSION = "0.3-6"