Merge branch 'stable-0.2'
* stable-0.2:
Bump version to 0.2-10
Navbar: Fix handling of [home] and [logo] metalinks
Change-Id: Idf61c0bc4af1923f0bb4732350e9dc7d2d92864d
diff --git a/WORKSPACE b/WORKSPACE
index 1d6e531..3ee16c0 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -46,8 +46,8 @@
maven_jar(
name = "guava",
- artifact = "com.google.guava:guava:27.1-jre",
- sha1 = "e47b59c893079b87743cdcfb6f17ca95c08c592c",
+ artifact = "com.google.guava:guava:28.0-jre",
+ sha1 = "54fed371b4b8a8cce1e94a9abd9620982d3aa54b",
)
maven_jar(
@@ -130,8 +130,8 @@
maven_jar(
name = "soy",
- artifact = "com.google.template:soy:2019-03-11",
- sha1 = "119ac4b3eb0e2c638526ca99374013965c727097",
+ artifact = "com.google.template:soy:2019-04-18",
+ sha1 = "5750208855562d74f29eee39ee497d5cf6df1490",
)
maven_jar(
@@ -194,17 +194,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(
@@ -259,46 +259,46 @@
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",
)
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..6a2e5eb 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);
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/DefaultErrorHandlingFilter.java b/java/com/google/gitiles/DefaultErrorHandlingFilter.java
index 958b800..f558c0d 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,56 @@
/** 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 {
+ int status = -1;
+ String message = null;
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());
- }
+ status = e.getReason().getHttpStatusCode();
+ message = e.getPublicErrorMessage();
} catch (RepositoryNotFoundException e) {
- res.sendError(SC_NOT_FOUND);
+ status = FailureReason.REPOSITORY_NOT_FOUND.getHttpStatusCode();
+ message = FailureReason.REPOSITORY_NOT_FOUND.getMessage();
} catch (AmbiguousObjectException e) {
- res.sendError(SC_BAD_REQUEST);
+ status = FailureReason.AMBIGUOUS_OBJECT.getHttpStatusCode();
+ message = FailureReason.AMBIGUOUS_OBJECT.getMessage();
} catch (ServiceMayNotContinueException e) {
- sendError(req, res, e.getStatusCode(), e.getMessage());
+ status = e.getStatusCode();
+ message = e.getMessage();
} catch (IOException | ServletException err) {
log.warn("Internal server error", err);
- res.sendError(SC_INTERNAL_SERVER_ERROR);
+ status = FailureReason.INTERNAL_SERVER_ERROR.getHttpStatusCode();
+ message = FailureReason.INTERNAL_SERVER_ERROR.getMessage();
}
+ if (status != -1) {
+ 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.render(req, res, templateName, startHtmlResponse(req, res, soyData));
+ }
+
+ private Map<String, ?> startHtmlResponse(
+ HttpServletRequest req, HttpServletResponse res, Map<String, ?> soyData) throws IOException {
+ 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/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..254fe22 100644
--- a/java/com/google/gitiles/GitilesFilter.java
+++ b/java/com/google/gitiles/GitilesFilter.java
@@ -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/LogServlet.java b/java/com/google/gitiles/LogServlet.java
index a91aeab..4d038ae 100644
--- a/java/com/google/gitiles/LogServlet.java
+++ b/java/com/google/gitiles/LogServlet.java
@@ -113,7 +113,7 @@
}
String title = "Log - ";
- if (view.getOldRevision() != Revision.NULL) {
+ if (!Revision.isNull(view.getOldRevision())) {
title += view.getRevisionRange();
} else {
title += view.getRevision().getName();
@@ -175,7 +175,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);
@@ -225,7 +225,7 @@
RevWalk walk = new RevWalk(repo);
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) {
diff --git a/java/com/google/gitiles/LogSoyData.java b/java/com/google/gitiles/LogSoyData.java
index 8bc243f..96ef6ae 100644
--- a/java/com/google/gitiles/LogSoyData.java
+++ b/java/com/google/gitiles/LogSoyData.java
@@ -176,14 +176,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..bc85290 100644
--- a/java/com/google/gitiles/Renderer.java
+++ b/java/com/google/gitiles/Renderer.java
@@ -60,6 +60,7 @@
"Common.soy",
"DiffDetail.soy",
"Doc.soy",
+ "Error.soy",
"HostIndex.soy",
"LogDetail.soy",
"ObjectDetail.soy",
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..d530cdd 100644
--- a/java/com/google/gitiles/RevisionParser.java
+++ b/java/com/google/gitiles/RevisionParser.java
@@ -225,7 +225,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..fe1c07e 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,7 +83,7 @@
}
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);
@@ -94,14 +94,37 @@
}
public VisibilityCache(boolean topoSort, CacheBuilder<Object, Object> builder) {
+ this(new VisibilityChecker(topoSort), builder);
+ }
+
+ /**
+ * Use the constructors with a boolean parameter (e.g. {@link #VisibilityCache(boolean)}). 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(boolean)}). 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..22d08bc
--- /dev/null
+++ b/java/com/google/gitiles/VisibilityChecker.java
@@ -0,0 +1,136 @@
+/*
+ * 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 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.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevSort;
+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 {
+
+ private final boolean topoSort;
+
+ /**
+ * @param topoSort whether to use a more thorough reachability check by sorting in topological
+ * order
+ */
+ public VisibilityChecker(boolean topoSort) {
+ this.topoSort = topoSort;
+ }
+
+ /**
+ * 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.
+ for (Ref ref : refDb.getRefs()) {
+ ref = refDb.peel(ref);
+ if (id.equals(ref.getObjectId()) || id.equals(ref.getPeeledObjectId())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * 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 pointing to wrong kind 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;
+ }
+
+ walk.reset();
+ if (topoSort) {
+ walk.sort(RevSort.TOPO);
+ }
+
+ walk.markStart(commit);
+ for (ObjectId id : starters) {
+ 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.
+ }
+ }
+}
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/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/VisibilityCacheTest.java b/javatests/com/google/gitiles/VisibilityCacheTest.java
index 1633803..bcc2b40 100644
--- a/javatests/com/google/gitiles/VisibilityCacheTest.java
+++ b/javatests/com/google/gitiles/VisibilityCacheTest.java
@@ -95,17 +95,18 @@
* </pre>
*/
repo = new InMemoryRepository(new DfsRepositoryDescription());
- 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();
+ 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();
+ 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);
+ git.update("master", commit2);
+ git.update("refs/tags/v0.1", commitA);
+ }
visibilityCache = new VisibilityCache(true);
walk = new RevWalk(repo);
diff --git a/javatests/com/google/gitiles/VisibilityCheckerTest.java b/javatests/com/google/gitiles/VisibilityCheckerTest.java
new file mode 100644
index 0000000..3171459
--- /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(true);
+ 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/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 6d15a21..9294fcf 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -29,11 +29,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",
@@ -44,7 +45,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",
@@ -68,7 +69,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 29a6a55..1bae3cf 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-10"
+GITILES_VERSION = "0.3-1"