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"