Merge "Change background colors of comment panels"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 880626e..fc4b00a 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1223,12 +1223,13 @@
high CPU usage, memory pressure, persistent cache bloat, and other problems.
+
The following operations are allowed even when a change is at the limit:
+
* Abandon
* Submit
* Submit by push with `%submit`
* Auto-close by pushing directly to the branch
* Fix with link:rest-api-changes.html#fix-input[`expect_merged_as`]
-+
+
By default 1000.
[[change.move]]change.move::
@@ -4784,6 +4785,15 @@
used for suggesting accounts when adding members to a group.
+
By default 0.
+[[suggest.relevantChanges]]suggest.relevantChanges::
++
+When suggesting reviewers, we go over recent changes of the user, and
+give priority to users that are present as reviewers in any of those
+changes. The number of changes we go over is `sugggest.relevantChanges`.
++
+By default 50. This nubmer is a tradeoff between speed and accuracy.
+A high number would be accurate but slow, and a low number would be
+fast but inaccurate.
[[tracing]]
=== Section tracing
diff --git a/WORKSPACE b/WORKSPACE
index 4a7add7..6e9fb38 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -266,31 +266,6 @@
sha1 = "1dcf1de382a0bf95a3d8b0849546c88bac1292c9",
)
-CAFFEINE_VERS = "2.8.0"
-
-maven_jar(
- name = "caffeine",
- artifact = "com.github.ben-manes.caffeine:caffeine:" + CAFFEINE_VERS,
- sha1 = "6000774d7f8412ced005a704188ced78beeed2bb",
-)
-
-# TODO(davido): Rename guava.jar to caffeine-guava.jar on fetch to prevent potential
-# naming collision between caffeine guava adapater and guava library itself.
-# Remove this renaming procedure, once this upstream issue is fixed:
-# https://github.com/ben-manes/caffeine/issues/364.
-http_file(
- name = "caffeine-guava-renamed",
- downloaded_file_path = "caffeine-guava-" + CAFFEINE_VERS + ".jar",
- sha256 = "3a66ee3ec70971dee0bae6e56bda7b8742bc4bedd7489161bfbbaaf7137d89e1",
- urls = [
- "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/guava/" +
- CAFFEINE_VERS +
- "/guava-" +
- CAFFEINE_VERS +
- ".jar",
- ],
-)
-
maven_jar(
name = "jsch",
artifact = "com.jcraft:jsch:0.1.54",
@@ -887,30 +862,30 @@
sha1 = "42a25dc3219429f0e5d060061f71acb49bf010a0",
)
-TRUTH_VERS = "1.0"
+TRUTH_VERS = "1.0.1"
maven_jar(
name = "truth",
artifact = "com.google.truth:truth:" + TRUTH_VERS,
- sha1 = "998e5fb3fa31df716574b4c9e8d374855e800451",
+ sha1 = "361459309085bd9441cb97b62f160e8b353a93c0",
)
maven_jar(
name = "truth-java8-extension",
artifact = "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERS,
- sha1 = "d85fbc1daf0510821f552f2aa71d9605e97aa438",
+ sha1 = "ef07b2cc2201472381fdd3bcf773310e22bb9080",
)
maven_jar(
name = "truth-liteproto-extension",
artifact = "com.google.truth.extensions:truth-liteproto-extension:" + TRUTH_VERS,
- sha1 = "7a279c50a0f93da15533cef4993b45606cf67d72",
+ sha1 = "bd1f5ac8a5f66e60cd1738f7b95c97a582ffcef9",
)
maven_jar(
name = "truth-proto-extension",
artifact = "com.google.truth.extensions:truth-proto-extension:" + TRUTH_VERS,
- sha1 = "8c0c2ea61750f02d0d5ce9c653106b6a5dc82d12",
+ sha1 = "039aa2d7c9196b30d367eac7cb467ecaa726e23d",
)
maven_jar(
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index a933dac..ca105f6 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -193,7 +193,13 @@
@RunWith(ConfigSuite.class)
public abstract class AbstractDaemonTest {
+
+ /**
+ * Test methods without special annotations will use a common server for efficiency reasons. The
+ * server is torn down after the test class is done.
+ */
private static GerritServer commonServer;
+
private static Description firstTest;
@ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index e4a993c..135a80e 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -7,6 +7,75 @@
"testsuite/ThrowingFunction.java",
]
+DEPLOY_ENV = [
+ "//java/com/google/gerrit/exceptions",
+ "//java/com/google/gerrit/gpg",
+ "//java/com/google/gerrit/git",
+ "//java/com/google/gerrit/httpd/auth/openid",
+ "//java/com/google/gerrit/index:query_exception",
+ "//java/com/google/gerrit/launcher",
+ "//java/com/google/gerrit/lifecycle",
+ "//java/com/google/gerrit/pgm",
+ "//java/com/google/gerrit/pgm/http/jetty",
+ "//java/com/google/gerrit/pgm/util",
+ "//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/entities",
+ "//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/httpd",
+ "//java/com/google/gerrit/index",
+ "//java/com/google/gerrit/index/project",
+ "//java/com/google/gerrit/json",
+ "//java/com/google/gerrit/lucene",
+ "//java/com/google/gerrit/mail",
+ "//java/com/google/gerrit/metrics",
+ "//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/audit",
+ "//java/com/google/gerrit/server/git/receive",
+ "//java/com/google/gerrit/server/logging",
+ "//java/com/google/gerrit/server/restapi",
+ "//java/com/google/gerrit/server/schema",
+ "//java/com/google/gerrit/server/util/git",
+ "//java/com/google/gerrit/server/util/time",
+ "//java/com/google/gerrit/sshd",
+ "//lib/auto:auto-value",
+ "//lib/auto:auto-value-annotations",
+ "//lib:args4j",
+ "//lib:gson",
+ "//lib:guava-retrying",
+ "//lib:jgit",
+ "//lib:jsch",
+ "//lib/commons:compress",
+ "//lib/commons:lang",
+ "//lib/flogger:api",
+ "//lib/guice",
+ "//lib/guice:guice-assistedinject",
+ "//lib/guice:guice-servlet",
+ "//lib/jetty:servlet",
+ "//lib/mail",
+ "//lib/mina:sshd",
+ "//lib/log:impl-log4j",
+ "//lib/log:log4j",
+ "//lib:guava",
+ "//lib/bouncycastle:bcpg",
+ "//lib/bouncycastle:bcprov",
+ "//prolog:gerrit-prolog-common",
+]
+
+TEST_DEPS = [
+ "//java/com/google/gerrit/truth",
+ "//java/com/google/gerrit/acceptance/config",
+ "//java/com/google/gerrit/acceptance/testsuite/project",
+ "//java/com/google/gerrit/server/fixes/testing",
+ "//java/com/google/gerrit/server/group/testing",
+ "//java/com/google/gerrit/server/project/testing:project-test-util",
+ "//java/com/google/gerrit/testing:gerrit-test-util",
+ "//java/com/google/gerrit/extensions/common/testing:common-test-util",
+ "//java/com/google/gerrit/extensions/restapi/testing:restapi-test-util",
+ "//java/com/google/gerrit/gpg/testing:gpg-test-util",
+ "//java/com/google/gerrit/git/testing",
+]
+
java_library(
name = "lib",
testonly = True,
@@ -15,61 +84,25 @@
visibility = ["//visibility:public"],
exports = [
":framework-lib",
- "//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/entities",
- "//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/extensions/common/testing:common-test-util",
- "//java/com/google/gerrit/extensions/restapi/testing:restapi-test-util",
- "//java/com/google/gerrit/git",
- "//java/com/google/gerrit/git/testing",
- "//java/com/google/gerrit/gpg/testing:gpg-test-util",
- "//java/com/google/gerrit/httpd",
- "//java/com/google/gerrit/index",
- "//java/com/google/gerrit/json",
- "//java/com/google/gerrit/launcher",
- "//java/com/google/gerrit/lucene",
- "//java/com/google/gerrit/mail",
- "//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/pgm",
- "//java/com/google/gerrit/pgm/init",
- "//java/com/google/gerrit/pgm/util",
- "//java/com/google/gerrit/server",
- "//java/com/google/gerrit/server/audit",
- "//java/com/google/gerrit/server/git/receive",
- "//java/com/google/gerrit/server/project/testing:project-test-util",
- "//java/com/google/gerrit/server/restapi",
- "//java/com/google/gerrit/sshd",
- "//java/com/google/gerrit/testing:gerrit-test-util",
- "//java/com/google/gerrit/truth",
- "//lib:args4j",
- "//lib:gson",
- "//lib:guava-retrying",
- "//lib:h2",
- "//lib:jgit",
- "//lib:jimfs",
- "//lib:jsch",
- "//lib:servlet-api-without-neverlink",
- "//lib/bouncycastle:bcpg",
- "//lib/bouncycastle:bcprov",
- "//lib/commons:compress",
- "//lib/flogger:api",
- "//lib/guice",
- "//lib/guice:guice-assistedinject",
- "//lib/guice:guice-servlet",
- "//lib/mina:sshd",
- "//prolog:gerrit-prolog-common",
- ],
+ ] + DEPLOY_ENV + TEST_DEPS,
)
java_binary(
name = "framework",
testonly = True,
+ deploy_env = [":framework-deploy-env"],
main_class = "Dummy",
visibility = ["//visibility:public"],
runtime_deps = [":framework-lib"],
)
+java_binary(
+ name = "framework-deploy-env",
+ testonly = True,
+ main_class = "Dummy",
+ runtime_deps = DEPLOY_ENV,
+)
+
java_library2(
name = "framework-lib",
testonly = True,
@@ -79,73 +112,19 @@
),
exported_deps = [
":function",
- "//java/com/google/gerrit/acceptance/config",
- "//java/com/google/gerrit/acceptance/testsuite/project",
- "//java/com/google/gerrit/exceptions",
- "//java/com/google/gerrit/gpg",
- "//java/com/google/gerrit/httpd/auth/openid",
- "//java/com/google/gerrit/index:query_exception",
- "//java/com/google/gerrit/launcher",
- "//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/pgm:daemon",
- "//java/com/google/gerrit/pgm/http/jetty",
- "//java/com/google/gerrit/pgm/util",
- "//java/com/google/gerrit/server/fixes/testing",
- "//java/com/google/gerrit/server/group/testing",
- "//java/com/google/gerrit/server/project/testing:project-test-util",
- "//java/com/google/gerrit/testing:gerrit-test-util",
- "//lib:guava",
"//lib:jgit-junit",
"//lib:jimfs",
- "//lib/auto:auto-value",
- "//lib/auto:auto-value-annotations",
+ "//lib:servlet-api",
"//lib/httpcomponents:fluent-hc",
"//lib/httpcomponents:httpclient",
"//lib/httpcomponents:httpcore",
- "//lib/jetty:servlet",
- "//lib/log:impl-log4j",
- "//lib/log:log4j",
"//lib/mockito",
"//lib/truth",
"//lib/truth:truth-java8-extension",
- "//prolog:gerrit-prolog-common",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/common:server",
- "//java/com/google/gerrit/entities",
- "//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/httpd",
- "//java/com/google/gerrit/index",
- "//java/com/google/gerrit/index/project",
- "//java/com/google/gerrit/json",
- "//java/com/google/gerrit/lucene",
- "//java/com/google/gerrit/mail",
- "//java/com/google/gerrit/metrics",
- "//java/com/google/gerrit/server",
- "//java/com/google/gerrit/server/audit",
- "//java/com/google/gerrit/server/git/receive",
- "//java/com/google/gerrit/server/logging",
- "//java/com/google/gerrit/server/restapi",
- "//java/com/google/gerrit/server/schema",
- "//java/com/google/gerrit/server/util/git",
- "//java/com/google/gerrit/server/util/time",
- "//java/com/google/gerrit/sshd",
- "//lib:args4j",
- "//lib:gson",
- "//lib:guava-retrying",
- "//lib:jgit",
- "//lib:jsch",
- "//lib:servlet-api",
- "//lib/commons:lang",
"//lib/greenmail",
- "//lib/guice",
- "//lib/guice:guice-assistedinject",
- "//lib/guice:guice-servlet",
- "//lib/mail",
- "//lib/mina:sshd",
- ],
+ ] + TEST_DEPS,
+ visibility = ["//visibility:public"],
+ deps = DEPLOY_ENV,
)
java_library(
diff --git a/java/com/google/gerrit/extensions/BUILD b/java/com/google/gerrit/extensions/BUILD
index 3683449..da5dc8b 100644
--- a/java/com/google/gerrit/extensions/BUILD
+++ b/java/com/google/gerrit/extensions/BUILD
@@ -26,7 +26,6 @@
],
)
-#TODO(davido): There is no provided_deps argument to java_library rule
java_library(
name = "api",
srcs = glob(["**/*.java"]),
diff --git a/java/com/google/gerrit/extensions/validators/CommentValidationContext.java b/java/com/google/gerrit/extensions/validators/CommentValidationContext.java
new file mode 100644
index 0000000..1cb00e3
--- /dev/null
+++ b/java/com/google/gerrit/extensions/validators/CommentValidationContext.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// 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.
+
+package com.google.gerrit.extensions.validators;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Holds a comment validators context in order to pass it to a validation plugin.
+ *
+ * <p>This is used to provided additional context around that comment that can be used by the
+ * validator to determine what validations should be run. For example, a comment validator may only
+ * want to validate a comment if it's on a change in the project foo.
+ *
+ * @see CommentValidator
+ */
+@AutoValue
+public abstract class CommentValidationContext {
+
+ /** Returns the change id the comment is being added to. */
+ public abstract int getChangeId();
+
+ /** Returns the project the comment is being added to. */
+ public abstract String getProject();
+
+ public static Builder builder() {
+ return new AutoValue_CommentValidationContext.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder changeId(int value);
+
+ public abstract Builder project(String value);
+
+ public abstract CommentValidationContext build();
+ }
+}
diff --git a/java/com/google/gerrit/extensions/validators/CommentValidator.java b/java/com/google/gerrit/extensions/validators/CommentValidator.java
index cfefdef..ba73e46 100644
--- a/java/com/google/gerrit/extensions/validators/CommentValidator.java
+++ b/java/com/google/gerrit/extensions/validators/CommentValidator.java
@@ -30,5 +30,5 @@
* @return An empty list if all comments are valid, or else a list of validation failures.
*/
ImmutableList<CommentValidationFailure> validateComments(
- ImmutableList<CommentForValidation> comments);
+ CommentValidationContext ctx, ImmutableList<CommentForValidation> comments);
}
diff --git a/java/com/google/gerrit/git/RefUpdateUtil.java b/java/com/google/gerrit/git/RefUpdateUtil.java
index fa7b98f..bd88962 100644
--- a/java/com/google/gerrit/git/RefUpdateUtil.java
+++ b/java/com/google/gerrit/git/RefUpdateUtil.java
@@ -150,6 +150,7 @@
public static void deleteChecked(Repository repo, String refName) throws IOException {
RefUpdate ru = repo.updateRef(refName);
ru.setForceUpdate(true);
+ ru.setCheckConflicting(false);
switch (ru.delete()) {
case FORCED:
// Ref was deleted.
diff --git a/java/com/google/gerrit/httpd/SetThreadNameFilter.java b/java/com/google/gerrit/httpd/SetThreadNameFilter.java
new file mode 100644
index 0000000..c7d977d
--- /dev/null
+++ b/java/com/google/gerrit/httpd/SetThreadNameFilter.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// 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.
+
+package com.google.gerrit.httpd;
+
+import com.google.gerrit.server.CurrentUser;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.servlet.ServletModule;
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+@Singleton
+public class SetThreadNameFilter implements Filter {
+ private static final int MAX_PATH_LENGTH = 120;
+
+ public static Module module() {
+ return new ServletModule() {
+ @Override
+ protected void configureServlets() {
+ filter("/*").through(SetThreadNameFilter.class);
+ }
+ };
+ }
+
+ private final Provider<CurrentUser> user;
+
+ @Inject
+ public SetThreadNameFilter(Provider<CurrentUser> user) {
+ this.user = user;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) {}
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ Thread current = Thread.currentThread();
+ String old = current.getName();
+ try {
+ current.setName(computeName((HttpServletRequest) request));
+ chain.doFilter(request, response);
+ } finally {
+ current.setName(old);
+ }
+ }
+
+ private String computeName(HttpServletRequest req) {
+ StringBuilder s = new StringBuilder();
+ s.append("HTTP ");
+ s.append(req.getMethod());
+ s.append(" ");
+ s.append(req.getRequestURI());
+ String query = req.getQueryString();
+ if (query != null) {
+ s.append("?").append(query);
+ }
+ if (s.length() > MAX_PATH_LENGTH) {
+ s.delete(MAX_PATH_LENGTH, s.length());
+ }
+ s.append(" (");
+ s.append(user.get().getUserName().orElse("N/A"));
+ s.append(" from ");
+ s.append(req.getRemoteAddr());
+ s.append(")");
+ return s.toString();
+ }
+
+ @Override
+ public void destroy() {}
+}
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 3da968e..769396e 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -31,6 +31,7 @@
import com.google.gerrit.httpd.RequestContextFilter;
import com.google.gerrit.httpd.RequestMetricsFilter;
import com.google.gerrit.httpd.RequireSslFilter;
+import com.google.gerrit.httpd.SetThreadNameFilter;
import com.google.gerrit.httpd.WebModule;
import com.google.gerrit.httpd.WebSshGlueModule;
import com.google.gerrit.httpd.auth.oauth.OAuthModule;
@@ -385,6 +386,7 @@
modules.add(sysInjector.getInstance(GerritAuthModule.class));
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
modules.add(RequestCleanupFilter.module());
+ modules.add(SetThreadNameFilter.module());
modules.add(AllRequestFilter.module());
modules.add(sysInjector.getInstance(WebModule.class));
modules.add(sysInjector.getInstance(RequireSslFilter.Module.class));
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index d9bb3b9..722955c 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -678,17 +678,24 @@
cause = Optional.of(e);
statusCode = SC_INTERNAL_SERVER_ERROR;
+ Optional<ExceptionHook.Status> status = getStatus(e);
+ statusCode = status.map(ExceptionHook.Status::statusCode).orElse(SC_INTERNAL_SERVER_ERROR);
+
if (res.isCommitted()) {
- logger.atSevere().withCause(e).log(
- "Error in %s %s, response already committed", req.getMethod(), uriForLogging(req));
responseBytes = 0;
+ if (statusCode == SC_INTERNAL_SERVER_ERROR) {
+ logger.atSevere().withCause(e).log(
+ "Error in %s %s, response already committed", req.getMethod(), uriForLogging(req));
+ } else {
+ logger.atWarning().log(
+ "Response for %s %s already committed, wanted to set status %d",
+ req.getMethod(), uriForLogging(req), statusCode);
+ }
} else {
res.reset();
traceContext.getTraceId().ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
- Optional<ExceptionHook.Status> status = getStatus(e);
if (status.isPresent()) {
- statusCode = status.get().statusCode();
responseBytes = reply(req, res, e, status.get(), getUserMessages(traceContext, e));
}
responseBytes = replyInternalServerError(req, res, e, getUserMessages(traceContext, e));
@@ -1182,8 +1189,14 @@
}
return OutputFormat.JSON.newGson().fromJson(json, type);
} finally {
- // Reader.close won't consume the rest of the input. Explicitly consume the request body.
- br.skip(Long.MAX_VALUE);
+ try {
+ // Reader.close won't consume the rest of the input. Explicitly consume the request
+ // body.
+ br.skip(Long.MAX_VALUE);
+ } catch (Exception e) {
+ // ignore, e.g. trying to consume the rest of the input may fail if the request was
+ // cancelled
+ }
}
}
}
@@ -1786,13 +1799,15 @@
return replyError(req, res, status.statusCode(), msg.toString(), err);
}
- private static long replyInternalServerError(
+ private long replyInternalServerError(
HttpServletRequest req,
HttpServletResponse res,
Throwable err,
ImmutableList<String> userMessages)
throws IOException {
- logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uriForLogging(req));
+ logger.atSevere().withCause(err).log(
+ "Error in %s %s: %s",
+ req.getMethod(), uriForLogging(req), globals.retryHelper.formatCause(err));
StringBuilder msg = new StringBuilder("Internal server error");
if (!userMessages.isEmpty()) {
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 08916a3..03b7c3d 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -1,17 +1,7 @@
load("@rules_java//java:defs.bzl", "java_library")
-# TODO(davido): This indirection doesn't avoid unwanted depdencies
-# in acceptance-framework and should be removed. Instead, provided_deps
-# should be used, once https://github.com/bazelbuild/bazel/issues/1402
-# is fixed.
-alias(
- name = "pgm",
- actual = ":daemon",
- visibility = ["//visibility:public"],
-)
-
java_library(
- name = "daemon",
+ name = "pgm",
srcs = glob(["**/*.java"]),
resource_strip_prefix = "resources",
resources = ["//resources/com/google/gerrit/pgm"],
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index d8beb76..2e9ef2f 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -35,6 +35,7 @@
import com.google.gerrit.httpd.RequestContextFilter;
import com.google.gerrit.httpd.RequestMetricsFilter;
import com.google.gerrit.httpd.RequireSslFilter;
+import com.google.gerrit.httpd.SetThreadNameFilter;
import com.google.gerrit.httpd.WebModule;
import com.google.gerrit.httpd.WebSshGlueModule;
import com.google.gerrit.httpd.auth.oauth.OAuthModule;
@@ -549,6 +550,7 @@
}
modules.add(RequestCleanupFilter.module());
modules.add(AllRequestFilter.module());
+ modules.add(SetThreadNameFilter.module());
modules.add(sysInjector.getInstance(WebModule.class));
modules.add(sysInjector.getInstance(RequireSslFilter.Module.class));
modules.add(new HttpPluginModule());
diff --git a/java/com/google/gerrit/pgm/init/InitModule.java b/java/com/google/gerrit/pgm/init/InitModule.java
index f2fc001..b658675 100644
--- a/java/com/google/gerrit/pgm/init/InitModule.java
+++ b/java/com/google/gerrit/pgm/init/InitModule.java
@@ -34,8 +34,6 @@
@Override
protected void configure() {
bind(SitePaths.class);
- bind(Libraries.class);
- bind(LibraryDownloader.class);
factory(Section.Factory.class);
factory(VersionedAuthorizedKeysOnInit.Factory.class);
diff --git a/java/com/google/gerrit/pgm/init/Libraries.java b/java/com/google/gerrit/pgm/init/Libraries.java
deleted file mode 100644
index c599e99..0000000
--- a/java/com/google/gerrit/pgm/init/Libraries.java
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// 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.
-
-package com.google.gerrit.pgm.init;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.gerrit.pgm.init.api.LibraryDownload;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.List;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-
-/** Standard {@link LibraryDownloader} instances derived from configuration. */
-@Singleton
-class Libraries {
- private static final String RESOURCE_FILE = "com/google/gerrit/pgm/init/libraries.config";
-
- private final Provider<LibraryDownloader> downloadProvider;
- private final List<String> skippedDownloads;
- private final boolean skipAllDownloads;
-
- /* final */ LibraryDownloader db2Driver;
- /* final */ LibraryDownloader db2DriverLicense;
- /* final */ LibraryDownloader hanaDriver;
- /* final */ LibraryDownloader mariadbDriver;
- /* final */ LibraryDownloader mysqlDriver;
- /* final */ LibraryDownloader oracleDriver;
-
- @Inject
- Libraries(
- final Provider<LibraryDownloader> downloadProvider,
- @LibraryDownload List<String> skippedDownloads,
- @LibraryDownload Boolean skipAllDownloads) {
- this.downloadProvider = downloadProvider;
- this.skippedDownloads = skippedDownloads;
- this.skipAllDownloads = skipAllDownloads;
- init();
- }
-
- private void init() {
- final Config cfg = new Config();
- try {
- cfg.fromText(read(RESOURCE_FILE));
- } catch (IOException | ConfigInvalidException e) {
- throw new RuntimeException(e.getMessage(), e);
- }
-
- for (Field f : Libraries.class.getDeclaredFields()) {
- if ((f.getModifiers() & Modifier.STATIC) == 0 && f.getType() == LibraryDownloader.class) {
- try {
- f.set(this, downloadProvider.get());
- } catch (IllegalArgumentException | IllegalAccessException e) {
- throw new IllegalStateException("Cannot initialize " + f.getName());
- }
- }
- }
-
- for (Field f : Libraries.class.getDeclaredFields()) {
- if ((f.getModifiers() & Modifier.STATIC) == 0 && f.getType() == LibraryDownloader.class) {
- try {
- init(f, cfg);
- } catch (IllegalArgumentException
- | IllegalAccessException
- | NoSuchFieldException
- | SecurityException e) {
- throw new IllegalStateException("Cannot configure " + f.getName());
- }
- }
- }
- }
-
- private void init(Field field, Config cfg)
- throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
- SecurityException {
- String n = field.getName();
- LibraryDownloader dl = (LibraryDownloader) field.get(this);
- dl.setName(get(cfg, n, "name"));
- dl.setJarUrl(get(cfg, n, "url"));
- dl.setSHA1(getOptional(cfg, n, "sha1"));
- dl.setRemove(get(cfg, n, "remove"));
- for (String d : cfg.getStringList("library", n, "needs")) {
- dl.addNeeds((LibraryDownloader) getClass().getDeclaredField(d).get(this));
- }
- dl.setSkipDownload(skipAllDownloads || skippedDownloads.contains(n));
- }
-
- private static String getOptional(Config cfg, String name, String key) {
- return doGet(cfg, name, key, false);
- }
-
- private static String get(Config cfg, String name, String key) {
- return doGet(cfg, name, key, true);
- }
-
- private static String doGet(Config cfg, String name, String key, boolean required) {
- String val = cfg.getString("library", name, key);
- if ((val == null || val.isEmpty()) && required) {
- throw new IllegalStateException(
- "Variable library." + name + "." + key + " is required within " + RESOURCE_FILE);
- }
- return val;
- }
-
- private static String read(String p) throws IOException {
- try (InputStream in = Libraries.class.getClassLoader().getResourceAsStream(p)) {
- if (in == null) {
- throw new FileNotFoundException("Cannot load resource " + p);
- }
- try (Reader r = new InputStreamReader(in, UTF_8)) {
- final StringBuilder buf = new StringBuilder();
- final char[] tmp = new char[512];
- int n;
- while (0 < (n = r.read(tmp))) {
- buf.append(tmp, 0, n);
- }
- return buf.toString();
- }
- }
- }
-}
diff --git a/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/java/com/google/gerrit/pgm/init/LibraryDownloader.java
deleted file mode 100644
index 0b31ee2..0000000
--- a/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ /dev/null
@@ -1,316 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// 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.
-
-package com.google.gerrit.pgm.init;
-
-import com.google.common.hash.Funnels;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
-import com.google.common.io.ByteStreams;
-import com.google.gerrit.common.Die;
-import com.google.gerrit.common.IoUtil;
-import com.google.gerrit.pgm.init.api.ConsoleUI;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import org.eclipse.jgit.util.HttpSupport;
-
-/** Get optional or required 3rd party library files into $site_path/lib. */
-class LibraryDownloader {
- private final ConsoleUI ui;
- private final Path lib_dir;
- private final StaleLibraryRemover remover;
-
- private boolean required;
- private String name;
- private String jarUrl;
- private String sha1;
- private String remove;
- private List<LibraryDownloader> needs;
- private LibraryDownloader neededBy;
- private Path dst;
- private boolean download; // download or copy
- private boolean exists;
- private boolean skipDownload;
-
- @Inject
- LibraryDownloader(ConsoleUI ui, SitePaths site, StaleLibraryRemover remover) {
- this.ui = ui;
- this.lib_dir = site.lib_dir;
- this.remover = remover;
- this.needs = new ArrayList<>(2);
- }
-
- void setName(String name) {
- this.name = name;
- }
-
- void setJarUrl(String url) {
- this.jarUrl = url;
- download = jarUrl.startsWith("http");
- }
-
- void setSHA1(String sha1) {
- this.sha1 = sha1;
- }
-
- void setRemove(String remove) {
- this.remove = remove;
- }
-
- void addNeeds(LibraryDownloader lib) {
- needs.add(lib);
- }
-
- void setSkipDownload(boolean skipDownload) {
- this.skipDownload = skipDownload;
- }
-
- void downloadRequired() {
- setRequired(true);
- download();
- }
-
- void downloadOptional() {
- required = false;
- download();
- }
-
- private void setRequired(boolean r) {
- required = r;
- for (LibraryDownloader d : needs) {
- d.setRequired(r);
- }
- }
-
- private void download() {
- if (skipDownload) {
- return;
- }
-
- if (jarUrl == null || !jarUrl.contains("/")) {
- throw new IllegalStateException("Invalid JarUrl for " + name);
- }
-
- final String jarName = jarUrl.substring(jarUrl.lastIndexOf('/') + 1);
- if (jarName.contains("/") || jarName.contains("\\")) {
- throw new IllegalStateException("Invalid JarUrl: " + jarUrl);
- }
-
- if (name == null) {
- name = jarName;
- }
-
- dst = lib_dir.resolve(jarName);
- if (Files.exists(dst)) {
- exists = true;
- } else if (shouldGet()) {
- doGet();
- }
-
- if (exists) {
- for (LibraryDownloader d : needs) {
- d.neededBy = this;
- d.downloadRequired();
- }
- }
- }
-
- private boolean shouldGet() {
- if (ui.isBatch()) {
- return required;
- }
- final StringBuilder msg = new StringBuilder();
- msg.append("\n");
- msg.append("Gerrit Code Review is not shipped with %s\n");
- if (neededBy != null) {
- msg.append(String.format("** This library is required by %s. **\n", neededBy.name));
- } else if (required) {
- msg.append("** This library is required for your configuration. **\n");
- } else {
- msg.append(" If available, Gerrit can take advantage of features\n");
- msg.append(" in the library, but will also function without it.\n");
- }
- msg.append(String.format("%s and install it now", download ? "Download" : "Copy"));
- return ui.yesno(true, msg.toString(), name);
- }
-
- private void doGet() {
- if (!Files.exists(lib_dir)) {
- try {
- Files.createDirectories(lib_dir);
- } catch (IOException e) {
- throw new Die("Cannot create " + lib_dir, e);
- }
- }
-
- try {
- remover.remove(remove);
- if (download) {
- doGetByHttp();
- } else {
- doGetByLocalCopy();
- }
- verifyFileChecksum();
- } catch (IOException err) {
- try {
- Files.delete(dst);
- } catch (IOException e) {
- // Delete failed; leave alone.
- }
-
- if (ui.isBatch()) {
- throw new Die("error: Cannot get " + jarUrl, err);
- }
-
- System.err.println();
- System.err.println();
- System.err.println("error: " + err.getMessage());
- System.err.println("Please download:");
- System.err.println();
- System.err.println(" " + jarUrl);
- System.err.println();
- System.err.println("and save as:");
- System.err.println();
- System.err.println(" " + dst.toAbsolutePath());
- System.err.println();
- System.err.flush();
-
- ui.waitForUser();
-
- if (Files.exists(dst)) {
- verifyFileChecksum();
-
- } else if (!ui.yesno(!required, "Continue without this library")) {
- throw new Die("aborted by user");
- }
- }
-
- if (Files.exists(dst)) {
- exists = true;
- IoUtil.loadJARs(dst);
- }
- }
-
- private void doGetByLocalCopy() throws IOException {
- System.err.print("Copying " + jarUrl + " ...");
- Path p = url2file(jarUrl);
- if (!Files.exists(p)) {
- StringBuilder msg =
- new StringBuilder()
- .append("\n")
- .append("Can not find the %s at this location: %s\n")
- .append("Please provide alternative URL");
- p = url2file(ui.readString(null, msg.toString(), name, jarUrl));
- }
- Files.copy(p, dst);
- }
-
- private static Path url2file(String urlString) throws IOException {
- final URL url = new URL(urlString);
- try {
- return Paths.get(url.toURI());
- } catch (URISyntaxException e) {
- return Paths.get(url.getPath());
- }
- }
-
- private void doGetByHttp() throws IOException {
- System.err.print("Downloading " + jarUrl + " ...");
- System.err.flush();
- try (InputStream in = openHttpStream(jarUrl);
- OutputStream out = Files.newOutputStream(dst)) {
- ByteStreams.copy(in, out);
- System.err.println(" OK");
- System.err.flush();
- } catch (IOException err) {
- deleteDst();
- System.err.println(" !! FAIL !!");
- System.err.println(err);
- System.err.flush();
- throw err;
- }
- }
-
- private static InputStream openHttpStream(String urlStr) throws IOException {
- ProxySelector proxySelector = ProxySelector.getDefault();
- URL url = new URL(urlStr);
- Proxy proxy = HttpSupport.proxyFor(proxySelector, url);
- HttpURLConnection c = (HttpURLConnection) url.openConnection(proxy);
-
- switch (HttpSupport.response(c)) {
- case HttpURLConnection.HTTP_OK:
- return c.getInputStream();
-
- case HttpURLConnection.HTTP_NOT_FOUND:
- throw new FileNotFoundException(url.toString());
-
- default:
- throw new IOException(
- url.toString() + ": " + HttpSupport.response(c) + " " + c.getResponseMessage());
- }
- }
-
- @SuppressWarnings("deprecation") // Use Hashing.sha1 for compatibility.
- private void verifyFileChecksum() {
- if (sha1 == null) {
- System.err.println();
- System.err.flush();
- return;
- }
- Hasher h = Hashing.sha1().newHasher();
- try (InputStream in = Files.newInputStream(dst);
- OutputStream out = Funnels.asOutputStream(h)) {
- ByteStreams.copy(in, out);
- } catch (IOException e) {
- deleteDst();
- throw new Die("cannot checksum " + dst, e);
- }
- if (sha1.equals(h.hash().toString())) {
- System.err.println("Checksum " + dst.getFileName() + " OK");
- System.err.flush();
- } else if (ui.isBatch()) {
- deleteDst();
- throw new Die(dst + " SHA-1 checksum does not match");
-
- } else if (!ui.yesno(
- null /* force an answer */,
- "error: SHA-1 checksum does not match\nUse %s anyway", //
- dst.getFileName())) {
- deleteDst();
- throw new Die("aborted by user");
- }
- }
-
- private void deleteDst() {
- try {
- Files.delete(dst);
- } catch (IOException e) {
- System.err.println(" Failed to clean up lib: " + dst);
- }
- }
-}
diff --git a/java/com/google/gerrit/server/ExceptionHookImpl.java b/java/com/google/gerrit/server/ExceptionHookImpl.java
index 8393451..5a3f077 100644
--- a/java/com/google/gerrit/server/ExceptionHookImpl.java
+++ b/java/com/google/gerrit/server/ExceptionHookImpl.java
@@ -20,6 +20,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.git.LockFailureException;
import java.util.Optional;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.RefUpdate;
/**
@@ -42,6 +43,9 @@
if (isLockFailure(throwable)) {
return Optional.of(RefUpdate.Result.LOCK_FAILURE.name());
}
+ if (isMissingObjectException(throwable)) {
+ return Optional.of("missing_object");
+ }
return Optional.empty();
}
@@ -65,6 +69,10 @@
return isMatching(throwable, t -> t instanceof LockFailureException);
}
+ private static boolean isMissingObjectException(Throwable throwable) {
+ return isMatching(throwable, t -> t instanceof MissingObjectException);
+ }
+
/**
* Check whether the given exception or any of its causes matches the given predicate.
*
diff --git a/java/com/google/gerrit/server/PublishCommentUtil.java b/java/com/google/gerrit/server/PublishCommentUtil.java
index 09042ab..3d34d6b 100644
--- a/java/com/google/gerrit/server/PublishCommentUtil.java
+++ b/java/com/google/gerrit/server/PublishCommentUtil.java
@@ -25,6 +25,7 @@
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -118,16 +119,18 @@
/**
* Helper to run the specified set of {@link CommentValidator}-s on the specified comments.
*
- * @return See {@link CommentValidator#validateComments(ImmutableList)}.
+ * @return See {@link CommentValidator#validateComments(CommentValidationContext,ImmutableList)}.
*/
public static ImmutableList<CommentValidationFailure> findInvalidComments(
+ CommentValidationContext ctx,
PluginSetContext<CommentValidator> commentValidators,
ImmutableList<CommentForValidation> commentsForValidation) {
ImmutableList.Builder<CommentValidationFailure> commentValidationFailures =
new ImmutableList.Builder<>();
commentValidators.runEach(
validator ->
- commentValidationFailures.addAll(validator.validateComments(commentsForValidation)));
+ commentValidationFailures.addAll(
+ validator.validateComments(ctx, commentsForValidation)));
return commentValidationFailures.build();
}
}
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 2b068aa..8f7e360 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -47,6 +47,10 @@
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
+/**
+ * Creates persistent caches depending on gerrit.config parameters. If the cache.directory property
+ * is unset, it will fall back to in-memory caches.
+ */
@Singleton
class H2CacheFactory implements PersistentCacheFactory, LifecycleListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
diff --git a/java/com/google/gerrit/server/change/FileInfoJson.java b/java/com/google/gerrit/server/change/FileInfoJson.java
index a823975..aca4fb0 100644
--- a/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -21,6 +21,7 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -30,6 +31,8 @@
import com.google.inject.Singleton;
import java.util.Map;
import java.util.TreeMap;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jgit.errors.NoMergeBaseException;
import org.eclipse.jgit.lib.ObjectId;
@Singleton
@@ -42,31 +45,44 @@
}
public Map<String, FileInfo> toFileInfoMap(Change change, PatchSet patchSet)
- throws PatchListNotAvailableException {
+ throws ResourceConflictException, PatchListNotAvailableException {
return toFileInfoMap(change, patchSet.commitId(), null);
}
public Map<String, FileInfo> toFileInfoMap(
Change change, ObjectId objectId, @Nullable PatchSet base)
- throws PatchListNotAvailableException {
+ throws ResourceConflictException, PatchListNotAvailableException {
ObjectId a = base != null ? base.commitId() : null;
return toFileInfoMap(change, PatchListKey.againstCommit(a, objectId, Whitespace.IGNORE_NONE));
}
public Map<String, FileInfo> toFileInfoMap(Change change, ObjectId objectId, int parent)
- throws PatchListNotAvailableException {
+ throws ResourceConflictException, PatchListNotAvailableException {
return toFileInfoMap(
change, PatchListKey.againstParentNum(parent + 1, objectId, Whitespace.IGNORE_NONE));
}
private Map<String, FileInfo> toFileInfoMap(Change change, PatchListKey key)
- throws PatchListNotAvailableException {
+ throws ResourceConflictException, PatchListNotAvailableException {
return toFileInfoMap(change.getProject(), key);
}
public Map<String, FileInfo> toFileInfoMap(Project.NameKey project, PatchListKey key)
- throws PatchListNotAvailableException {
- PatchList list = patchListCache.get(key, project);
+ throws ResourceConflictException, PatchListNotAvailableException {
+ PatchList list;
+ try {
+ list = patchListCache.get(key, project);
+ } catch (PatchListNotAvailableException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof ExecutionException) {
+ cause = cause.getCause();
+ }
+ if (cause instanceof NoMergeBaseException) {
+ throw new ResourceConflictException(
+ String.format("Cannot create auto merge commit: %s", e.getMessage()), e);
+ }
+ throw e;
+ }
Map<String, FileInfo> files = new TreeMap<>();
for (PatchListEntry e : list.getPatches()) {
diff --git a/java/com/google/gerrit/server/change/RevisionJson.java b/java/com/google/gerrit/server/change/RevisionJson.java
index fbd14c4..a4994fd 100644
--- a/java/com/google/gerrit/server/change/RevisionJson.java
+++ b/java/com/google/gerrit/server/change/RevisionJson.java
@@ -49,6 +49,7 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.Extension;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GpgException;
@@ -311,9 +312,13 @@
}
if (has(ALL_FILES) || (out.isCurrent && has(CURRENT_FILES))) {
- out.files = fileInfoJson.toFileInfoMap(c, in);
- out.files.remove(Patch.COMMIT_MSG);
- out.files.remove(Patch.MERGE_LIST);
+ try {
+ out.files = fileInfoJson.toFileInfoMap(c, in);
+ out.files.remove(Patch.COMMIT_MSG);
+ out.files.remove(Patch.MERGE_LIST);
+ } catch (ResourceConflictException e) {
+ logger.atWarning().withCause(e).log("creating file list failed");
+ }
}
if (out.isCurrent && has(CURRENT_ACTIONS) && userProvider.get().isIdentifiedUser()) {
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index c05a47d..128388d 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -53,6 +53,7 @@
import java.util.List;
import java.util.Optional;
import java.util.TimeZone;
+import org.eclipse.jgit.dircache.InvalidPathException;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -250,13 +251,14 @@
* @param filePath the path of the file whose contents should be modified
* @param newContent the new file content
* @throws AuthException if the user isn't authenticated or not allowed to use change edits
+ * @throws BadRequestException if the user provided bad input (e.g. invalid file paths)
* @throws InvalidChangeOperationException if the file already had the specified content
* @throws PermissionBackendException
* @throws ResourceConflictException if the project state does not permit the operation
*/
public void modifyFile(
Repository repository, ChangeNotes notes, String filePath, RawInput newContent)
- throws AuthException, InvalidChangeOperationException, IOException,
+ throws AuthException, BadRequestException, InvalidChangeOperationException, IOException,
PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new ChangeFileContentModification(filePath, newContent));
}
@@ -269,12 +271,13 @@
* @param notes the {@link ChangeNotes} of the change whose change edit should be modified
* @param file path of the file which should be deleted
* @throws AuthException if the user isn't authenticated or not allowed to use change edits
+ * @throws BadRequestException if the user provided bad input (e.g. invalid file paths)
* @throws InvalidChangeOperationException if the file does not exist
* @throws PermissionBackendException
* @throws ResourceConflictException if the project state does not permit the operation
*/
public void deleteFile(Repository repository, ChangeNotes notes, String file)
- throws AuthException, InvalidChangeOperationException, IOException,
+ throws AuthException, BadRequestException, InvalidChangeOperationException, IOException,
PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new DeleteFileModification(file));
}
@@ -288,6 +291,7 @@
* @param currentFilePath the current path/name of the file
* @param newFilePath the desired path/name of the file
* @throws AuthException if the user isn't authenticated or not allowed to use change edits
+ * @throws BadRequestException if the user provided bad input (e.g. invalid file paths)
* @throws InvalidChangeOperationException if the file was already renamed to the specified new
* name
* @throws PermissionBackendException
@@ -295,7 +299,7 @@
*/
public void renameFile(
Repository repository, ChangeNotes notes, String currentFilePath, String newFilePath)
- throws AuthException, InvalidChangeOperationException, IOException,
+ throws AuthException, BadRequestException, InvalidChangeOperationException, IOException,
PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new RenameFileModification(currentFilePath, newFilePath));
}
@@ -313,14 +317,14 @@
* @throws PermissionBackendException
*/
public void restoreFile(Repository repository, ChangeNotes notes, String file)
- throws AuthException, InvalidChangeOperationException, IOException,
+ throws AuthException, BadRequestException, InvalidChangeOperationException, IOException,
PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new RestoreFileModification(file));
}
private void modifyTree(
Repository repository, ChangeNotes notes, TreeModification treeModification)
- throws AuthException, IOException, InvalidChangeOperationException,
+ throws AuthException, BadRequestException, IOException, InvalidChangeOperationException,
PermissionBackendException, ResourceConflictException {
assertCanEdit(notes);
@@ -370,8 +374,8 @@
ChangeNotes notes,
PatchSet patchSet,
List<TreeModification> treeModifications)
- throws AuthException, IOException, InvalidChangeOperationException, MergeConflictException,
- PermissionBackendException, ResourceConflictException {
+ throws AuthException, BadRequestException, IOException, InvalidChangeOperationException,
+ MergeConflictException, PermissionBackendException, ResourceConflictException {
assertCanEdit(notes);
Optional<ChangeEdit> optionalChangeEdit = lookupChangeEdit(notes);
@@ -486,10 +490,15 @@
private static ObjectId createNewTree(
Repository repository, RevCommit baseCommit, List<TreeModification> treeModifications)
- throws IOException, InvalidChangeOperationException {
- TreeCreator treeCreator = new TreeCreator(baseCommit);
- treeCreator.addTreeModifications(treeModifications);
- ObjectId newTreeId = treeCreator.createNewTreeAndGetId(repository);
+ throws BadRequestException, IOException, InvalidChangeOperationException {
+ ObjectId newTreeId;
+ try {
+ TreeCreator treeCreator = new TreeCreator(baseCommit);
+ treeCreator.addTreeModifications(treeModifications);
+ newTreeId = treeCreator.createNewTreeAndGetId(repository);
+ } catch (InvalidPathException e) {
+ throw new BadRequestException(e.getMessage());
+ }
if (ObjectId.isEqual(newTreeId, baseCommit.getTree())) {
throw new InvalidChangeOperationException("no changes were made");
diff --git a/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index 926e0d5..710916e 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -169,7 +169,10 @@
RevCommit squashed = squashEdit(rw, oi, edit.getEditCommit(), basePatchSet);
PatchSet.Id psId = ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId());
- PatchSetInserter inserter = patchSetInserterFactory.create(notes, psId, squashed);
+ PatchSetInserter inserter =
+ patchSetInserterFactory
+ .create(notes, psId, squashed)
+ .setSendEmail(!change.isWorkInProgress());
StringBuilder message =
new StringBuilder("Patch Set ").append(inserter.getPatchSetId().get()).append(": ");
diff --git a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
index 8c9ebeb..1bd8b45 100644
--- a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
+++ b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
@@ -95,7 +95,6 @@
return Iterables.getOnlyElement(result);
}
- @SuppressWarnings("deprecation")
@Override
public Map<String, Ref> getRefs(String prefix) throws IOException {
List<Ref> refs = getDelegate().getRefDatabase().getRefsByPrefix(prefix);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 3d531b2..d8aa054 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -93,6 +93,7 @@
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.validators.CommentForValidation;
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.server.ApprovalsUtil;
@@ -2014,8 +2015,13 @@
: CommentType.FILE_COMMENT,
comment.message))
.collect(toImmutableList());
+ CommentValidationContext ctx =
+ CommentValidationContext.builder()
+ .changeId(change.getChangeId())
+ .project(change.getProject().get())
+ .build();
ImmutableList<CommentValidationFailure> commentValidationFailures =
- PublishCommentUtil.findInvalidComments(commentValidators, draftsForValidation);
+ PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
magicBranch.setCommentsValid(commentValidationFailures.isEmpty());
commentValidationFailures.forEach(
failure ->
@@ -3337,7 +3343,8 @@
}
logger.atFine().log(
- "Auto-closing %d changes with existing patch sets and %d with new patch sets",
+ "Auto-closing %d changes with existing patch sets and %d with new patch"
+ + " sets",
existingPatchSets, newPatchSets);
bu.execute();
} catch (IOException | StorageException | PermissionBackendException e) {
diff --git a/java/com/google/gerrit/server/git/validators/CommentLimitsValidator.java b/java/com/google/gerrit/server/git/validators/CommentLimitsValidator.java
index 8237e69..3a8bcac 100644
--- a/java/com/google/gerrit/server/git/validators/CommentLimitsValidator.java
+++ b/java/com/google/gerrit/server/git/validators/CommentLimitsValidator.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -33,7 +34,7 @@
@Override
public ImmutableList<CommentValidationFailure> validateComments(
- ImmutableList<CommentForValidation> comments) {
+ CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
return comments.stream()
.filter(c -> c.getText().length() > maxCommentLength)
.map(
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 71d8c15..e79696a 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -34,6 +34,7 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.mail.HtmlParser;
@@ -287,8 +288,14 @@
MAIL_COMMENT_TYPE_TO_VALIDATION_TYPE.get(comment.getType()),
comment.getMessage()))
.collect(ImmutableList.toImmutableList());
+ CommentValidationContext commentValidationCtx =
+ CommentValidationContext.builder()
+ .changeId(cd.change().getChangeId())
+ .project(cd.change().getProject().get())
+ .build();
ImmutableList<CommentValidationFailure> commentValidationFailures =
- PublishCommentUtil.findInvalidComments(commentValidators, parsedCommentsForValidation);
+ PublishCommentUtil.findInvalidComments(
+ commentValidationCtx, commentValidators, parsedCommentsForValidation);
if (!commentValidationFailures.isEmpty()) {
sendRejectionEmail(message, InboundEmailRejectionSender.Error.COMMENT_REJECTED);
return;
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index aa36b73..cbc1b79 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -138,7 +138,8 @@
@Override
public Response<?> apply(ChangeResource rsrc, IdString id, Input in)
- throws IOException, AuthException, ResourceConflictException, PermissionBackendException {
+ throws IOException, AuthException, BadRequestException, ResourceConflictException,
+ PermissionBackendException {
return deleteContent.apply(rsrc, id.get());
}
}
@@ -184,7 +185,8 @@
@Override
public Response<EditInfo> apply(ChangeResource rsrc)
- throws AuthException, IOException, ResourceNotFoundException, PermissionBackendException {
+ throws AuthException, IOException, ResourceNotFoundException, ResourceConflictException,
+ PermissionBackendException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getNotes(), rsrc.getUser());
if (!edit.isPresent()) {
return Response.none();
@@ -239,7 +241,8 @@
@Override
public Response<?> apply(ChangeResource resource, Post.Input input)
- throws AuthException, IOException, ResourceConflictException, PermissionBackendException {
+ throws AuthException, BadRequestException, IOException, ResourceConflictException,
+ PermissionBackendException {
Project.NameKey project = resource.getProject();
try (Repository repository = repositoryManager.openRepository(project)) {
if (isRestoreFile(input)) {
@@ -325,12 +328,14 @@
@Override
public Response<?> apply(ChangeEditResource rsrc, Input input)
- throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
+ throws AuthException, BadRequestException, ResourceConflictException, IOException,
+ PermissionBackendException {
return apply(rsrc.getChangeResource(), rsrc.getPath());
}
public Response<?> apply(ChangeResource rsrc, String filePath)
- throws AuthException, IOException, ResourceConflictException, PermissionBackendException {
+ throws AuthException, BadRequestException, IOException, ResourceConflictException,
+ PermissionBackendException {
try (Repository repository = repositoryManager.openRepository(rsrc.getProject())) {
editModifier.deleteFile(repository, rsrc.getNotes(), filePath);
} catch (InvalidChangeOperationException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/GetPatch.java b/java/com/google/gerrit/server/restapi/change/GetPatch.java
index 31785be..66ccef3 100644
--- a/java/com/google/gerrit/server/restapi/change/GetPatch.java
+++ b/java/com/google/gerrit/server/restapi/change/GetPatch.java
@@ -136,7 +136,9 @@
} finally {
if (close) {
rw.close();
- bin.close();
+ if (bin != null) {
+ bin.close();
+ }
}
}
} finally {
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 03c2fc4..324069d 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -77,6 +77,7 @@
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidationFailure;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.json.OutputFormat;
@@ -999,16 +1000,22 @@
}
}
+ CommentValidationContext commentValidationCtx =
+ CommentValidationContext.builder()
+ .changeId(ctx.getChange().getChangeId())
+ .project(ctx.getChange().getProject().get())
+ .build();
switch (in.drafts) {
case PUBLISH:
case PUBLISH_ALL_REVISIONS:
- validateComments(Streams.concat(drafts.values().stream(), toPublish.stream()));
+ validateComments(
+ commentValidationCtx, Streams.concat(drafts.values().stream(), toPublish.stream()));
publishCommentUtil.publish(ctx, ctx.getUpdate(psId), drafts.values(), in.tag);
comments.addAll(drafts.values());
break;
case KEEP:
default:
- validateComments(toPublish.stream());
+ validateComments(commentValidationCtx, toPublish.stream());
break;
}
ChangeUpdate changeUpdate = ctx.getUpdate(psId);
@@ -1017,7 +1024,8 @@
return !toPublish.isEmpty();
}
- private void validateComments(Stream<Comment> comments) throws CommentsRejectedException {
+ private void validateComments(CommentValidationContext ctx, Stream<Comment> comments)
+ throws CommentsRejectedException {
ImmutableList<CommentForValidation> draftsForValidation =
comments
.map(
@@ -1029,7 +1037,7 @@
comment.message))
.collect(toImmutableList());
ImmutableList<CommentValidationFailure> draftValidationFailures =
- PublishCommentUtil.findInvalidComments(commentValidators, draftsForValidation);
+ PublishCommentUtil.findInvalidComments(ctx, commentValidators, draftsForValidation);
if (!draftValidationFailures.isEmpty()) {
throw new CommentsRejectedException(draftValidationFailures);
}
@@ -1415,8 +1423,14 @@
buf.append(String.format("\n\n(%d comments)", comments.size()));
}
if (!msg.isEmpty()) {
+ CommentValidationContext commentValidationCtx =
+ CommentValidationContext.builder()
+ .changeId(ctx.getChange().getChangeId())
+ .project(ctx.getChange().getProject().get())
+ .build();
ImmutableList<CommentValidationFailure> messageValidationFailure =
PublishCommentUtil.findInvalidComments(
+ commentValidationCtx,
commentValidators,
ImmutableList.of(
CommentForValidation.create(
diff --git a/java/com/google/gerrit/server/restapi/change/PutDescription.java b/java/com/google/gerrit/server/restapi/change/PutDescription.java
index d84ab3e..f442a42 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDescription.java
@@ -87,17 +87,21 @@
if (oldDescription.equals(newDescription)) {
return false;
}
- String summary;
- if (oldDescription.isEmpty()) {
- summary = "Description set to \"" + newDescription + "\"";
- } else if (newDescription.isEmpty()) {
- summary = "Description \"" + oldDescription + "\" removed";
- } else {
- summary = "Description changed to \"" + newDescription + "\"";
- }
-
update.setPsDescription(newDescription);
+ String summary;
+ if (oldDescription.isEmpty()) {
+ summary =
+ String.format("Description of patch set %d set to \"%s\"", psId.get(), newDescription);
+ } else if (newDescription.isEmpty()) {
+ summary =
+ String.format(
+ "Description \"%s\" removed from patch set %d", oldDescription, psId.get());
+ } else {
+ summary =
+ String.format(
+ "Description of patch set %d changed to \"%s\"", psId.get(), newDescription);
+ }
ChangeMessage cmsg =
ChangeMessagesUtil.newMessage(
psId, ctx.getUser(), ctx.getWhen(), summary, ChangeMessagesUtil.TAG_SET_DESCRIPTION);
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
index f07d815..39df82d 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewerRecommender.java
@@ -22,7 +22,6 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.PatchSetApproval;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.ApprovalsUtil;
@@ -202,23 +201,25 @@
private Map<Account.Id, MutableDouble> baseRanking(
double baseWeight, String query, List<Account.Id> candidateList)
throws IOException, ConfigInvalidException {
- // Get the user's last 25 changes, check approvals
+ int numberOfRelevantChanges = config.getInt("suggest", "relevantChanges", 50);
+ // Get the user's last 25 changes, check reviewers
try {
List<ChangeData> result =
queryProvider
.get()
- .setLimit(25)
- .setRequestedFields(ChangeField.APPROVAL)
+ .setLimit(numberOfRelevantChanges)
+ .setRequestedFields(ChangeField.REVIEWER)
.query(changeQueryBuilder.owner("self"));
Map<Account.Id, MutableDouble> suggestions = new LinkedHashMap<>();
// Put those candidates at the bottom of the list
candidateList.stream().forEach(id -> suggestions.put(id, new MutableDouble(0)));
for (ChangeData cd : result) {
- for (PatchSetApproval approval : cd.currentApprovals()) {
- Account.Id id = approval.accountId();
- if (Strings.isNullOrEmpty(query) || accountMatchesQuery(id, query)) {
- suggestions.computeIfAbsent(id, (ignored) -> new MutableDouble(0)).add(baseWeight);
+ for (Account.Id reviewer : cd.reviewers().all()) {
+ if (Strings.isNullOrEmpty(query) || accountMatchesQuery(reviewer, query)) {
+ suggestions
+ .computeIfAbsent(reviewer, (ignored) -> new MutableDouble(0))
+ .add(baseWeight);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java b/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
index 0ee8279..0d5ab88 100644
--- a/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/FilesInCommitCollection.java
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -86,7 +87,7 @@
@Override
public Response<Map<String, FileInfo>> apply(CommitResource resource)
- throws PatchListNotAvailableException {
+ throws ResourceConflictException, PatchListNotAvailableException {
RevCommit commit = resource.getCommit();
PatchListKey key;
diff --git a/java/com/google/gerrit/testing/TestCommentHelper.java b/java/com/google/gerrit/testing/TestCommentHelper.java
index b72cca7..deda355 100644
--- a/java/com/google/gerrit/testing/TestCommentHelper.java
+++ b/java/com/google/gerrit/testing/TestCommentHelper.java
@@ -16,14 +16,21 @@
import static java.util.stream.Collectors.toList;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.FixSuggestionInfo;
import com.google.inject.Inject;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
/** Test helper for dealing with comments/drafts. */
public class TestCommentHelper {
@@ -44,7 +51,7 @@
}
public void addDraft(String changeId, String revId, DraftInput in) throws Exception {
- gApi.changes().id(changeId).revision(revId).createDraft(in).get();
+ gApi.changes().id(changeId).revision(revId).createDraft(in);
}
public Collection<CommentInfo> getPublishedComments(String changeId) throws Exception {
@@ -104,4 +111,34 @@
range.endCharacter = 5;
return range;
}
+
+ public static RobotCommentInput createRobotCommentInputWithMandatoryFields(String path) {
+ RobotCommentInput in = new RobotCommentInput();
+ in.robotId = "happyRobot";
+ in.robotRunId = "1";
+ in.line = 1;
+ in.message = "nit: trailing whitespace";
+ in.path = path;
+ return in;
+ }
+
+ public static RobotCommentInput createRobotCommentInput(
+ String path, FixSuggestionInfo... fixSuggestionInfos) {
+ RobotCommentInput in = TestCommentHelper.createRobotCommentInputWithMandatoryFields(path);
+ in.url = "http://www.happy-robot.com";
+ in.properties = new HashMap<>();
+ in.properties.put("key1", "value1");
+ in.properties.put("key2", "value2");
+ in.fixSuggestions = Arrays.asList(fixSuggestionInfos);
+ return in;
+ }
+
+ public void addRobotComment(String targetChangeId, RobotCommentInput robotCommentInput)
+ throws Exception {
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.robotComments =
+ Collections.singletonMap(robotCommentInput.path, ImmutableList.of(robotCommentInput));
+ reviewInput.message = "robot comment test";
+ gApi.changes().id(targetChangeId).current().review(reviewInput);
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
index 7156c8d..524a05e 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/PostReviewIT.java
@@ -16,6 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -37,6 +38,7 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.validators.CommentForValidation;
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.server.restapi.change.PostReview;
import com.google.gerrit.server.update.CommentsRejectedException;
@@ -80,14 +82,17 @@
@Test
public void validateCommentsInInput_commentOK() throws Exception {
+ PushOneCommit.Result r = createChange();
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(r.getChange().getId().get())
+ .project(r.getChange().project().get())
+ .build(),
ImmutableList.of(
CommentForValidation.create(
CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
.thenReturn(ImmutableList.of());
- PushOneCommit.Result r = createChange();
-
ReviewInput input = new ReviewInput();
CommentInput comment = newComment(r.getChange().currentFilePaths().get(0));
comment.updated = new Timestamp(0);
@@ -101,14 +106,17 @@
@Test
public void validateCommentsInInput_commentRejected() throws Exception {
+ PushOneCommit.Result r = createChange();
CommentForValidation commentForValidation =
CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(r.getChange().getId().get())
+ .project(r.getChange().project().get())
+ .build(),
ImmutableList.of(CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT))))
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
- PushOneCommit.Result r = createChange();
-
ReviewInput input = new ReviewInput();
CommentInput comment = newComment(r.getChange().currentFilePaths().get(0));
comment.updated = new Timestamp(0);
@@ -151,14 +159,17 @@
@Test
public void validateDrafts_draftOK() throws Exception {
+ PushOneCommit.Result r = createChange();
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(r.getChange().getId().get())
+ .project(r.getChange().project().get())
+ .build(),
ImmutableList.of(
CommentForValidation.create(
CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
.thenReturn(ImmutableList.of());
- PushOneCommit.Result r = createChange();
-
DraftInput draft =
testCommentHelper.newDraft(
r.getChange().currentFilePaths().get(0), Side.REVISION, 1, COMMENT_TEXT);
@@ -174,14 +185,18 @@
@Test
public void validateDrafts_draftRejected() throws Exception {
+ PushOneCommit.Result r = createChange();
CommentForValidation commentForValidation =
CommentForValidation.create(CommentType.INLINE_COMMENT, COMMENT_TEXT);
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(r.getChange().getId().get())
+ .project(r.getChange().project().get())
+ .build(),
ImmutableList.of(
CommentForValidation.create(
CommentForValidation.CommentType.INLINE_COMMENT, COMMENT_TEXT))))
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
- PushOneCommit.Result r = createChange();
DraftInput draft =
testCommentHelper.newDraft(
@@ -218,7 +233,8 @@
testCommentHelper.addDraft(r.getChangeId(), r.getCommit().getName(), draftFile);
assertThat(testCommentHelper.getPublishedComments(r.getChangeId())).isEmpty();
- when(mockCommentValidator.validateComments(capture.capture())).thenReturn(ImmutableList.of());
+ when(mockCommentValidator.validateComments(any(), capture.capture()))
+ .thenReturn(ImmutableList.of());
ReviewInput input = new ReviewInput();
input.drafts = DraftHandling.PUBLISH;
@@ -236,11 +252,15 @@
@Test
public void validateCommentsInChangeMessage_messageOK() throws Exception {
+ PushOneCommit.Result r = createChange();
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(r.getChange().getId().get())
+ .project(r.getChange().project().get())
+ .build(),
ImmutableList.of(
CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
.thenReturn(ImmutableList.of());
- PushOneCommit.Result r = createChange();
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
int numMessages = gApi.changes().id(r.getChangeId()).get().messages.size();
@@ -253,13 +273,17 @@
@Test
public void validateCommentsInChangeMessage_messageRejected() throws Exception {
+ PushOneCommit.Result r = createChange();
CommentForValidation commentForValidation =
CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT);
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(r.getChange().getId().get())
+ .project(r.getChange().project().get())
+ .build(),
ImmutableList.of(
CommentForValidation.create(CommentType.CHANGE_MESSAGE, COMMENT_TEXT))))
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
- PushOneCommit.Result r = createChange();
ReviewInput input = new ReviewInput().message(COMMENT_TEXT);
assertThat(gApi.changes().id(r.getChangeId()).get().messages)
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index c4dec31..e6b2190 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1320,10 +1320,24 @@
public void description() throws Exception {
PushOneCommit.Result r = createChange();
assertDescription(r, "");
- gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).description("test");
- assertDescription(r, "test");
+
+ // set description
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).description("foo");
+ assertDescription(r, "foo");
+ assertThat(Iterables.getLast(gApi.changes().id(r.getChangeId()).get().messages).message)
+ .isEqualTo("Description of patch set 1 set to \"foo\"");
+
+ // update description
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).description("bar");
+ assertDescription(r, "bar");
+ assertThat(Iterables.getLast(gApi.changes().id(r.getChangeId()).get().messages).message)
+ .isEqualTo("Description of patch set 1 changed to \"bar\"");
+
+ // remove description
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).description("");
assertDescription(r, "");
+ assertThat(Iterables.getLast(gApi.changes().id(r.getChangeId()).get().messages).message)
+ .isEqualTo("Description \"bar\" removed from patch set 1");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index abf0279..c35ded6 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -28,7 +28,6 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.config.GerritConfig;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -45,9 +44,10 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.testing.BinaryResultSubject;
+import com.google.gerrit.testing.TestCommentHelper;
+import com.google.inject.Inject;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -57,6 +57,8 @@
import org.junit.Test;
public class RobotCommentsIT extends AbstractDaemonTest {
+ @Inject private TestCommentHelper testCommentHelper;
+
private static final String PLAIN_TEXT_CONTENT_TYPE = "text/plain";
private static final String FILE_NAME = "file_to_fix.txt";
@@ -86,7 +88,8 @@
fixReplacementInfo = createFixReplacementInfo();
fixSuggestionInfo = createFixSuggestionInfo(fixReplacementInfo);
- withFixRobotCommentInput = createRobotCommentInput(fixSuggestionInfo);
+ withFixRobotCommentInput =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME, fixSuggestionInfo);
}
@Test
@@ -100,8 +103,8 @@
@Test
public void addedRobotCommentsCanBeRetrieved() throws Exception {
- RobotCommentInput in = createRobotCommentInput();
- addRobotComment(changeId, in);
+ RobotCommentInput in = TestCommentHelper.createRobotCommentInput(FILE_NAME);
+ testCommentHelper.addRobotComment(changeId, in);
Map<String, List<RobotCommentInfo>> out = gApi.changes().id(changeId).current().robotComments();
@@ -112,13 +115,13 @@
@Test
public void addedRobotCommentsCanBeRetrievedByChange() throws Exception {
- RobotCommentInput in = createRobotCommentInput();
- addRobotComment(changeId, in);
+ RobotCommentInput in = TestCommentHelper.createRobotCommentInput(FILE_NAME);
+ testCommentHelper.addRobotComment(changeId, in);
pushFactory.create(admin.newIdent(), testRepo, changeId).to("refs/for/master");
- RobotCommentInput in2 = createRobotCommentInput();
- addRobotComment(changeId, in2);
+ RobotCommentInput in2 = TestCommentHelper.createRobotCommentInput(FILE_NAME);
+ testCommentHelper.addRobotComment(changeId, in2);
Map<String, List<RobotCommentInfo>> out = gApi.changes().id(changeId).robotComments();
@@ -133,8 +136,8 @@
@Test
public void robotCommentsCanBeRetrievedAsList() throws Exception {
- RobotCommentInput robotCommentInput = createRobotCommentInput();
- addRobotComment(changeId, robotCommentInput);
+ RobotCommentInput robotCommentInput = TestCommentHelper.createRobotCommentInput(FILE_NAME);
+ testCommentHelper.addRobotComment(changeId, robotCommentInput);
List<RobotCommentInfo> robotCommentInfos =
gApi.changes().id(changeId).current().robotCommentsAsList();
@@ -146,8 +149,8 @@
@Test
public void specificRobotCommentCanBeRetrieved() throws Exception {
- RobotCommentInput robotCommentInput = createRobotCommentInput();
- addRobotComment(changeId, robotCommentInput);
+ RobotCommentInput robotCommentInput = TestCommentHelper.createRobotCommentInput(FILE_NAME);
+ testCommentHelper.addRobotComment(changeId, robotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
RobotCommentInfo robotCommentInfo = Iterables.getOnlyElement(robotCommentInfos);
@@ -159,8 +162,8 @@
@Test
public void robotCommentWithoutOptionalFieldsCanBeAdded() throws Exception {
- RobotCommentInput in = createRobotCommentInputWithMandatoryFields();
- addRobotComment(changeId, in);
+ RobotCommentInput in = TestCommentHelper.createRobotCommentInputWithMandatoryFields(FILE_NAME);
+ testCommentHelper.addRobotComment(changeId, in);
Map<String, List<RobotCommentInfo>> out = gApi.changes().id(changeId).current().robotComments();
assertThat(out).hasSize(1);
@@ -169,14 +172,15 @@
}
@Test
- public void hugeRobotCommentIsRejected() throws Exception {
+ public void hugeRobotCommentIsRejected() {
int defaultSizeLimit = 1024 * 1024;
int sizeOfRest = 451;
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest + 1);
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown).hasMessageThat().contains("limit");
}
@@ -186,7 +190,7 @@
int sizeOfRest = 451;
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit - sizeOfRest);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThat(robotCommentInfos).hasSize(1);
@@ -194,13 +198,14 @@
@Test
@GerritConfig(name = "change.robotCommentSizeLimit", value = "10k")
- public void maximumAllowedSizeOfRobotCommentCanBeAdjusted() throws Exception {
+ public void maximumAllowedSizeOfRobotCommentCanBeAdjusted() {
int sizeLimit = 10 * 1024;
fixReplacementInfo.replacement = getStringFor(sizeLimit);
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown).hasMessageThat().contains("limit");
}
@@ -210,7 +215,7 @@
int defaultSizeLimit = 1024 * 1024;
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThat(robotCommentInfos).hasSize(1);
@@ -223,7 +228,7 @@
int defaultSizeLimit = 1024 * 1024;
fixReplacementInfo.replacement = getStringFor(defaultSizeLimit);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThat(robotCommentInfos).hasSize(1);
@@ -231,7 +236,7 @@
@Test
public void addedFixSuggestionCanBeRetrieved() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThatList(robotCommentInfos).onlyElement().onlyFixSuggestion().isNotNull();
@@ -239,7 +244,7 @@
@Test
public void fixIdIsGeneratedForFixSuggestion() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThatList(robotCommentInfos).onlyElement().onlyFixSuggestion().fixId().isNotEmpty();
@@ -252,7 +257,7 @@
@Test
public void descriptionOfFixSuggestionIsAcceptedAsIs() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThatList(robotCommentInfos)
@@ -263,12 +268,13 @@
}
@Test
- public void descriptionOfFixSuggestionIsMandatory() throws Exception {
+ public void descriptionOfFixSuggestionIsMandatory() {
fixSuggestionInfo.description = null;
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown)
.hasMessageThat()
.contains(
@@ -279,7 +285,7 @@
@Test
public void addedFixReplacementCanBeRetrieved() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThatList(robotCommentInfos)
@@ -290,12 +296,13 @@
}
@Test
- public void fixReplacementsAreMandatory() throws Exception {
+ public void fixReplacementsAreMandatory() {
fixSuggestionInfo.replacements = Collections.emptyList();
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown)
.hasMessageThat()
.contains(
@@ -307,7 +314,7 @@
@Test
public void pathOfFixReplacementIsAcceptedAsIs() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
@@ -320,12 +327,13 @@
}
@Test
- public void pathOfFixReplacementIsMandatory() throws Exception {
+ public void pathOfFixReplacementIsMandatory() {
fixReplacementInfo.path = null;
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown)
.hasMessageThat()
.contains(
@@ -336,7 +344,7 @@
@Test
public void rangeOfFixReplacementIsAcceptedAsIs() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
@@ -349,12 +357,13 @@
}
@Test
- public void rangeOfFixReplacementIsMandatory() throws Exception {
+ public void rangeOfFixReplacementIsMandatory() {
fixReplacementInfo.range = null;
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown)
.hasMessageThat()
.contains(
@@ -364,17 +373,17 @@
}
@Test
- public void rangeOfFixReplacementNeedsToBeValid() throws Exception {
+ public void rangeOfFixReplacementNeedsToBeValid() {
fixReplacementInfo.range = createRange(13, 9, 5, 10);
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown).hasMessageThat().contains("Range (13:9 - 5:10)");
}
@Test
- public void rangesOfFixReplacementsOfSameFixSuggestionForSameFileMayNotOverlap()
- throws Exception {
+ public void rangesOfFixReplacementsOfSameFixSuggestionForSameFileMayNotOverlap() {
FixReplacementInfo fixReplacementInfo1 = new FixReplacementInfo();
fixReplacementInfo1.path = FILE_NAME;
fixReplacementInfo1.range = createRange(2, 0, 3, 1);
@@ -391,7 +400,8 @@
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown).hasMessageThat().contains("overlap");
}
@@ -412,7 +422,7 @@
createFixSuggestionInfo(fixReplacementInfo1, fixReplacementInfo2);
withFixRobotCommentInput.fixSuggestions = ImmutableList.of(fixSuggestionInfo);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThatList(robotCommentInfos).onlyElement().fixSuggestions().hasSize(1);
@@ -436,7 +446,7 @@
withFixRobotCommentInput.fixSuggestions =
ImmutableList.of(fixSuggestionInfo1, fixSuggestionInfo2);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThatList(robotCommentInfos).onlyElement().fixSuggestions().hasSize(2);
@@ -463,7 +473,7 @@
createFixSuggestionInfo(fixReplacementInfo2, fixReplacementInfo1, fixReplacementInfo3);
withFixRobotCommentInput.fixSuggestions = ImmutableList.of(fixSuggestionInfo);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
assertThatList(robotCommentInfos).onlyElement().onlyFixSuggestion().replacements().hasSize(3);
@@ -471,7 +481,7 @@
@Test
public void replacementStringOfFixReplacementIsAcceptedAsIs() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
@@ -484,12 +494,13 @@
}
@Test
- public void replacementStringOfFixReplacementIsMandatory() throws Exception {
+ public void replacementStringOfFixReplacementIsMandatory() {
fixReplacementInfo.replacement = null;
BadRequestException thrown =
assertThrows(
- BadRequestException.class, () -> addRobotComment(changeId, withFixRobotCommentInput));
+ BadRequestException.class,
+ () -> testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput));
assertThat(thrown)
.hasMessageThat()
.contains(
@@ -505,7 +516,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -528,7 +539,7 @@
fixReplacementInfo.replacement = "Modified content\n5";
fixReplacementInfo.range = createRange(3, 2, 5, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
@@ -560,7 +571,7 @@
createFixSuggestionInfo(fixReplacementInfo1, fixReplacementInfo2);
withFixRobotCommentInput.fixSuggestions = ImmutableList.of(fixSuggestionInfo);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
@@ -590,10 +601,12 @@
fixReplacementInfo2.replacement = "Some other modified content\n";
FixSuggestionInfo fixSuggestionInfo2 = createFixSuggestionInfo(fixReplacementInfo2);
- RobotCommentInput robotCommentInput1 = createRobotCommentInput(fixSuggestionInfo1);
- RobotCommentInput robotCommentInput2 = createRobotCommentInput(fixSuggestionInfo2);
- addRobotComment(changeId, robotCommentInput1);
- addRobotComment(changeId, robotCommentInput2);
+ RobotCommentInput robotCommentInput1 =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME, fixSuggestionInfo1);
+ RobotCommentInput robotCommentInput2 =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME, fixSuggestionInfo2);
+ testCommentHelper.addRobotComment(changeId, robotCommentInput1);
+ testCommentHelper.addRobotComment(changeId, robotCommentInput2);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -623,10 +636,12 @@
fixReplacementInfo2.replacement = "Some other modified content\n";
FixSuggestionInfo fixSuggestionInfo2 = createFixSuggestionInfo(fixReplacementInfo2);
- RobotCommentInput robotCommentInput1 = createRobotCommentInput(fixSuggestionInfo1);
- RobotCommentInput robotCommentInput2 = createRobotCommentInput(fixSuggestionInfo2);
- addRobotComment(changeId, robotCommentInput1);
- addRobotComment(changeId, robotCommentInput2);
+ RobotCommentInput robotCommentInput1 =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME, fixSuggestionInfo1);
+ RobotCommentInput robotCommentInput2 =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME, fixSuggestionInfo2);
+ testCommentHelper.addRobotComment(changeId, robotCommentInput1);
+ testCommentHelper.addRobotComment(changeId, robotCommentInput2);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -655,7 +670,7 @@
withFixRobotCommentInput.fixSuggestions =
ImmutableList.of(fixSuggestionInfo1, fixSuggestionInfo2);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -677,7 +692,7 @@
fixReplacementInfo.range = createRange(2, 0, 3, 0);
fixReplacementInfo.replacement = "Modified content\n";
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
@@ -707,7 +722,7 @@
createFixSuggestionInfo(fixReplacementInfo1, fixReplacementInfo2);
withFixRobotCommentInput.fixSuggestions = ImmutableList.of(fixSuggestionInfo);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
@@ -734,7 +749,7 @@
fixReplacementInfo.range = createRange(1, 0, 2, 0);
fixReplacementInfo.replacement = "Modified content\n";
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
@@ -750,7 +765,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
// Remember patch set and add another one.
@@ -776,7 +791,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
// Remember patch set and add another one.
@@ -811,7 +826,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -833,7 +848,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -851,7 +866,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -874,7 +889,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -892,7 +907,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -915,7 +930,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -938,7 +953,7 @@
fixReplacementInfo.replacement = "Modified content";
fixReplacementInfo.range = createRange(3, 1, 3, 3);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -957,7 +972,8 @@
.create(admin.newIdent(), testRepo, SUBJECT, FILE_NAME, "new content", r1.getChangeId())
.to("refs/for/master");
- addRobotComment(r2.getChangeId(), createRobotCommentInputWithMandatoryFields());
+ testCommentHelper.addRobotComment(
+ r2.getChangeId(), TestCommentHelper.createRobotCommentInputWithMandatoryFields(FILE_NAME));
try (AutoCloseable ignored = disableNoteDb()) {
ChangeInfo result = Iterables.getOnlyElement(query(r2.getChangeId()));
@@ -971,7 +987,7 @@
@Test
public void getFixPreviewWithNonexistingFixId() throws Exception {
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
assertThrows(
ResourceNotFoundException.class,
@@ -986,7 +1002,7 @@
fixReplacementInfo.range = createRange(1, 0, 2, 0);
fixReplacementInfo.replacement = "Modified content\n";
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
String fixId = Iterables.getOnlyElement(fixIds);
@@ -1010,9 +1026,10 @@
fixSuggestionInfo = createFixSuggestionInfo(fixReplacementInfoFile1, fixReplacementInfoFile2);
- withFixRobotCommentInput = createRobotCommentInput(fixSuggestionInfo);
+ withFixRobotCommentInput =
+ TestCommentHelper.createRobotCommentInput(FILE_NAME, fixSuggestionInfo);
- addRobotComment(changeId, withFixRobotCommentInput);
+ testCommentHelper.addRobotComment(changeId, withFixRobotCommentInput);
List<RobotCommentInfo> robotCommentInfos = getRobotComments();
List<String> fixIds = getFixIds(robotCommentInfos);
@@ -1105,27 +1122,6 @@
assertThat(diff2).content().element(2).linesOfB().isNull();
}
- private static RobotCommentInput createRobotCommentInputWithMandatoryFields() {
- RobotCommentInput in = new RobotCommentInput();
- in.robotId = "happyRobot";
- in.robotRunId = "1";
- in.line = 1;
- in.message = "nit: trailing whitespace";
- in.path = FILE_NAME;
- return in;
- }
-
- private static RobotCommentInput createRobotCommentInput(
- FixSuggestionInfo... fixSuggestionInfos) {
- RobotCommentInput in = createRobotCommentInputWithMandatoryFields();
- in.url = "http://www.happy-robot.com";
- in.properties = new HashMap<>();
- in.properties.put("key1", "value1");
- in.properties.put("key2", "value2");
- in.fixSuggestions = Arrays.asList(fixSuggestionInfos);
- return in;
- }
-
private static FixSuggestionInfo createFixSuggestionInfo(
FixReplacementInfo... fixReplacementInfos) {
FixSuggestionInfo newFixSuggestionInfo = new FixSuggestionInfo();
@@ -1153,15 +1149,6 @@
return range;
}
- private void addRobotComment(String targetChangeId, RobotCommentInput robotCommentInput)
- throws Exception {
- ReviewInput reviewInput = new ReviewInput();
- reviewInput.robotComments =
- Collections.singletonMap(robotCommentInput.path, ImmutableList.of(robotCommentInput));
- reviewInput.message = "robot comment test";
- gApi.changes().id(targetChangeId).current().review(reviewInput);
- }
-
private List<RobotCommentInfo> getRobotComments() throws RestApiException {
return gApi.changes().id(changeId).current().robotCommentsAsList();
}
diff --git a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index b0f183e..e72bf06 100644
--- a/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/javatests/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -59,6 +59,7 @@
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -141,8 +142,11 @@
public void publishEdit() throws Exception {
createArbitraryEditFor(changeId);
+ AddReviewerInput in = new AddReviewerInput();
+ in.reviewer = user.email();
+ gApi.changes().id(changeId).addReviewer(in);
+
PublishChangeEditInput publishInput = new PublishChangeEditInput();
- publishInput.notify = NotifyHandling.NONE;
gApi.changes().id(changeId).edit().publish(publishInput);
assertThat(getEdit(changeId)).isAbsent();
@@ -159,8 +163,10 @@
assertThat(info.messages).isNotEmpty();
assertThat(Iterables.getLast(info.messages).tag)
.isEqualTo(ChangeMessagesUtil.TAG_UPLOADED_PATCH_SET);
+ assertThat(sender.getMessages()).isNotEmpty();
// Move the change to WIP, repeat, and verify.
+ sender.clear();
gApi.changes().id(changeId).setWorkInProgress();
createEmptyEditFor(changeId);
gApi.changes().id(changeId).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW2));
@@ -169,6 +175,7 @@
assertThat(info.messages).isNotEmpty();
assertThat(Iterables.getLast(info.messages).tag)
.isEqualTo(ChangeMessagesUtil.TAG_UPLOADED_WIP_PATCH_SET);
+ assertThat(sender.getMessages()).isEmpty();
}
@Test
@@ -436,6 +443,16 @@
}
@Test
+ public void renameExistingFileToInvalidPath() throws Exception {
+ createEmptyEditFor(changeId);
+ BadRequestException badRequest =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(changeId).edit().renameFile(FILE_NAME, "invalid/path/"));
+ assertThat(badRequest.getMessage()).isEqualTo("Invalid path: invalid/path/");
+ }
+
+ @Test
public void createEditByDeletingExistingFileRest() throws Exception {
adminRestSession.delete(urlEditFile(changeId, FILE_NAME)).assertNoContent();
assertThat(getFileContentOfEdit(changeId, FILE_NAME)).isAbsent();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
index 568c63b..1bd2d99 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
@@ -39,7 +39,6 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
-import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
@@ -397,19 +396,13 @@
requestScopeOperations.setApiUser(user1.id());
String changeId1 = createChangeFromApi();
- requestScopeOperations.setApiUser(reviewer1.id());
- reviewChange(changeId1);
+ reviewChange(changeId1, reviewer1);
- requestScopeOperations.setApiUser(user1.id());
String changeId2 = createChangeFromApi();
- requestScopeOperations.setApiUser(reviewer1.id());
- reviewChange(changeId2);
+ reviewChange(changeId2, reviewer1);
+ reviewChange(changeId2, reviewer2);
- requestScopeOperations.setApiUser(reviewer2.id());
- reviewChange(changeId2);
-
- requestScopeOperations.setApiUser(user1.id());
String changeId3 = createChangeFromApi();
List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId3, null, 4);
assertThat(reviewers.stream().map(r -> r.account._accountId).collect(toList()))
@@ -440,13 +433,11 @@
String name = name("foo");
TestAccount foo1 = accountCreator.create(name + "-1");
- requestScopeOperations.setApiUser(foo1.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo1);
assertThat(gApi.accounts().id(foo1.username()).getActive()).isTrue();
TestAccount foo2 = accountCreator.create(name + "-2");
- requestScopeOperations.setApiUser(foo2.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo2);
assertThat(gApi.accounts().id(foo2.username()).getActive()).isTrue();
assertReviewers(
@@ -466,12 +457,10 @@
String name = name("foo");
TestAccount foo1 = accountCreator.create(name + "-1");
- requestScopeOperations.setApiUser(foo1.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo1);
TestAccount foo2 = accountCreator.create(name + "-2");
- requestScopeOperations.setApiUser(foo2.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo2);
assertReviewers(
suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
@@ -488,12 +477,10 @@
String name = name("foo");
TestAccount foo1 = accountCreator.create(name + "-1");
- requestScopeOperations.setApiUser(foo1.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo1);
TestAccount foo2 = accountCreator.create(name + "-2");
- requestScopeOperations.setApiUser(foo2.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo2);
assertReviewers(
suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
@@ -514,12 +501,10 @@
String name = name("foo");
TestAccount foo1 = accountCreator.create(name + "-1");
- requestScopeOperations.setApiUser(foo1.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo1);
TestAccount foo2 = accountCreator.create(name + "-2");
- requestScopeOperations.setApiUser(foo2.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, foo2);
assertReviewers(suggestCcs(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
@@ -575,8 +560,7 @@
String changeIdReviewed = createChangeFromApi();
TestAccount reviewer = accountCreator.create("newReviewer");
- requestScopeOperations.setApiUser(reviewer.id());
- reviewChange(changeIdReviewed);
+ reviewChange(changeIdReviewed, reviewer);
List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, "new", 4);
assertThat(reviewers.stream().map(r -> r.account._accountId).collect(toList()))
@@ -624,10 +608,8 @@
return user(name, fullName, name);
}
- private void reviewChange(String changeId) throws RestApiException {
- ReviewInput ri = new ReviewInput();
- ri.label("Code-Review", 1);
- gApi.changes().id(changeId).current().review(ri);
+ private void reviewChange(String changeId, TestAccount reviewer) throws RestApiException {
+ gApi.changes().id(changeId).addReviewer(reviewer.id().toString());
}
private String createChangeFromApi() throws RestApiException {
diff --git a/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
index d8b65b7..ccfe783 100644
--- a/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/git/receive/ReceiveCommitsCommentValidationIT.java
@@ -32,6 +32,7 @@
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.validators.CommentForValidation;
import com.google.gerrit.extensions.validators.CommentForValidation.CommentType;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.testing.TestCommentHelper;
import com.google.inject.Inject;
@@ -53,6 +54,7 @@
private static final String COMMENT_TEXT = "The comment text";
@Captor private ArgumentCaptor<ImmutableList<CommentForValidation>> capture;
+ @Captor private ArgumentCaptor<CommentValidationContext> captureCtx;
@Override
public Module createModule() {
@@ -76,14 +78,18 @@
@Test
public void validateComments_commentOK() throws Exception {
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String revId = result.getCommit().getName();
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(result.getChange().getId().get())
+ .project(result.getChange().project().get())
+ .build(),
ImmutableList.of(
CommentForValidation.create(
CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
.thenReturn(ImmutableList.of());
- PushOneCommit.Result result = createChange();
- String changeId = result.getChangeId();
- String revId = result.getCommit().getName();
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
testCommentHelper.addDraft(changeId, revId, comment);
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
@@ -97,14 +103,18 @@
public void validateComments_commentRejected() throws Exception {
CommentForValidation commentForValidation =
CommentForValidation.create(CommentType.FILE_COMMENT, COMMENT_TEXT);
+ PushOneCommit.Result result = createChange();
+ String changeId = result.getChangeId();
+ String revId = result.getCommit().getName();
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder()
+ .changeId(result.getChange().getId().get())
+ .project(result.getChange().project().get())
+ .build(),
ImmutableList.of(
CommentForValidation.create(
CommentForValidation.CommentType.FILE_COMMENT, COMMENT_TEXT))))
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
- PushOneCommit.Result result = createChange();
- String changeId = result.getChangeId();
- String revId = result.getCommit().getName();
DraftInput comment = testCommentHelper.newDraft(COMMENT_TEXT);
testCommentHelper.addDraft(changeId, revId, comment);
assertThat(testCommentHelper.getPublishedComments(result.getChangeId())).isEmpty();
@@ -116,7 +126,8 @@
@Test
public void validateComments_inlineVsFileComments_allOK() throws Exception {
- when(mockCommentValidator.validateComments(capture.capture())).thenReturn(ImmutableList.of());
+ when(mockCommentValidator.validateComments(captureCtx.capture(), capture.capture()))
+ .thenReturn(ImmutableList.of());
PushOneCommit.Result result = createChange();
String changeId = result.getChangeId();
String revId = result.getCommit().getName();
@@ -132,6 +143,9 @@
assertThat(capture.getAllValues()).hasSize(1);
+ assertThat(captureCtx.getValue().getProject()).isEqualTo(result.getChange().project().get());
+ assertThat(captureCtx.getValue().getChangeId()).isEqualTo(result.getChange().getId().get());
+
assertThat(capture.getAllValues().get(0))
.containsExactly(
CommentForValidation.create(
@@ -143,7 +157,7 @@
@Test
@GerritConfig(name = "change.maxCommentLength", value = "" + MAX_COMMENT_LENGTH)
public void validateComments_enforceLimits_commentTooLarge() throws Exception {
- when(mockCommentValidator.validateComments(any())).thenReturn(ImmutableList.of());
+ when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
PushOneCommit.Result result = createChange();
String changeId = result.getChangeId();
int commentLength = MAX_COMMENT_LENGTH + 1;
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index 5531709..2409f52 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -29,6 +29,7 @@
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
import com.google.gerrit.extensions.validators.CommentValidator;
import com.google.gerrit.mail.MailMessage;
import com.google.gerrit.mail.MailProcessingUtil;
@@ -70,7 +71,7 @@
@BeforeClass
public static void setUpMock() {
// Let the mock comment validator accept all comments during test setup.
- when(mockCommentValidator.validateComments(any())).thenReturn(ImmutableList.of());
+ when(mockCommentValidator.validateComments(any(), any())).thenReturn(ImmutableList.of());
}
@Before
@@ -274,7 +275,8 @@
MailProcessingUtil.rfcDateformatter.format(
ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
- setupFailValidation(CommentForValidation.CommentType.CHANGE_MESSAGE);
+ setupFailValidation(
+ CommentForValidation.CommentType.CHANGE_MESSAGE, changeInfo.project, changeInfo._number);
MailMessage.Builder b = messageBuilderWithDefaultFields();
String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", COMMENT_TEXT, null, null, null);
@@ -298,7 +300,8 @@
MailProcessingUtil.rfcDateformatter.format(
ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
- setupFailValidation(CommentForValidation.CommentType.INLINE_COMMENT);
+ setupFailValidation(
+ CommentForValidation.CommentType.INLINE_COMMENT, changeInfo.project, changeInfo._number);
MailMessage.Builder b = messageBuilderWithDefaultFields();
String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", null, COMMENT_TEXT, null, null);
@@ -322,7 +325,8 @@
MailProcessingUtil.rfcDateformatter.format(
ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
- setupFailValidation(CommentForValidation.CommentType.FILE_COMMENT);
+ setupFailValidation(
+ CommentForValidation.CommentType.FILE_COMMENT, changeInfo.project, changeInfo._number);
MailMessage.Builder b = messageBuilderWithDefaultFields();
String txt = newPlaintextBody(getChangeUrl(changeInfo) + "/1", null, null, COMMENT_TEXT, null);
@@ -341,10 +345,12 @@
return canonicalWebUrl.get() + "c/" + changeInfo.project + "/+/" + changeInfo._number;
}
- private void setupFailValidation(CommentForValidation.CommentType type) {
+ private void setupFailValidation(
+ CommentForValidation.CommentType type, String failProject, int failChange) {
CommentForValidation commentForValidation = CommentForValidation.create(type, COMMENT_TEXT);
when(mockCommentValidator.validateComments(
+ CommentValidationContext.builder().changeId(failChange).project(failProject).build(),
ImmutableList.of(CommentForValidation.create(type, COMMENT_TEXT))))
.thenReturn(ImmutableList.of(commentForValidation.failValidation("Oh no!")));
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index c67a842..53acdeb 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -63,7 +63,7 @@
case V7_4:
return "blacktop/elasticsearch:7.4.2";
case V7_5:
- return "blacktop/elasticsearch:7.5.1";
+ return "blacktop/elasticsearch:7.5.2";
}
throw new IllegalStateException("No tests for version: " + version.name());
}
diff --git a/javatests/com/google/gerrit/pgm/BUILD b/javatests/com/google/gerrit/pgm/BUILD
index eb0bf25..0cae937 100644
--- a/javatests/com/google/gerrit/pgm/BUILD
+++ b/javatests/com/google/gerrit/pgm/BUILD
@@ -5,9 +5,7 @@
name = "pgm_tests",
srcs = glob(["**/*.java"]),
deps = [
- "//java/com/google/gerrit/pgm",
"//java/com/google/gerrit/pgm/http",
- "//java/com/google/gerrit/pgm/init",
"//java/com/google/gerrit/pgm/init/api",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/securestore/testing",
diff --git a/javatests/com/google/gerrit/pgm/init/LibrariesTest.java b/javatests/com/google/gerrit/pgm/init/LibrariesTest.java
deleted file mode 100644
index 5aa4718..0000000
--- a/javatests/com/google/gerrit/pgm/init/LibrariesTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// 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.
-
-package com.google.gerrit.pgm.init;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-import com.google.gerrit.pgm.init.api.ConsoleUI;
-import com.google.gerrit.server.config.SitePaths;
-import java.nio.file.Paths;
-import java.util.Collections;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class LibrariesTest {
- @Mock ConsoleUI ui;
- @Mock StaleLibraryRemover remover;
-
- @Test
- public void create() throws Exception {
- final SitePaths site = new SitePaths(Paths.get("."));
-
- Libraries lib =
- new Libraries(
- () -> new LibraryDownloader(ui, site, remover), Collections.emptyList(), false);
-
- assertNotNull(lib.mysqlDriver);
- verifyZeroInteractions(ui);
- verifyZeroInteractions(remover);
- }
-}
diff --git a/lib/nongoogle_test.sh b/lib/nongoogle_test.sh
index dcf40f0..336fcbf 100755
--- a/lib/nongoogle_test.sh
+++ b/lib/nongoogle_test.sh
@@ -20,19 +20,12 @@
httpcore-nio
j2objc
jackson-core
-javassist
jna
jruby
mina-core
nekohtml
objenesis
openid-consumer
-powermock-api-easymock
-powermock-api-support
-powermock-core
-powermock-module-junit4
-powermock-module-junit4-common
-powermock-reflect
sshd
sshd-common
sshd-mina
diff --git a/lib/truth/BUILD b/lib/truth/BUILD
index bb30945..dc1d802 100644
--- a/lib/truth/BUILD
+++ b/lib/truth/BUILD
@@ -2,6 +2,7 @@
java_library(
name = "truth",
+ testonly = True,
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
visibility = ["//visibility:public"],
exports = ["@truth//jar"],
@@ -14,6 +15,7 @@
java_library(
name = "truth-java8-extension",
+ testonly = True,
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
visibility = ["//visibility:public"],
exports = ["@truth-java8-extension//jar"],
@@ -25,6 +27,7 @@
java_library(
name = "truth-liteproto-extension",
+ testonly = True,
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
visibility = ["//visibility:private"],
exports = ["@truth-liteproto-extension//jar"],
@@ -37,6 +40,7 @@
java_library(
name = "diffutils",
+ testonly = True,
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
visibility = ["//visibility:private"],
exports = ["@diffutils//jar"],
@@ -44,6 +48,7 @@
java_library(
name = "truth-proto-extension",
+ testonly = True,
data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
visibility = ["//visibility:public"],
exports = [
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index e645972..a35bd6f 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -109,7 +109,7 @@
For daily development you typically only want to run and debug individual tests.
Run the local [Go proxy server](#go-server) and navigate for example to
-<http://localhost:8081/elements/change/gr-account-entry/gr-account-entry_test.html>.
+<http://localhost:8081/elements/shared/gr-account-entry/gr-account-entry_test.html>.
Check "Disable cache" in the "Network" tab of Chrome's dev tools, so code
changes are picked up on "reload".
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index e719154..508c3a2 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -271,7 +271,12 @@
// API to work as expected.
const groupId = decodeURIComponent(e.detail.value.id)
.replace(/\+/g, ' ');
- this.set(['permission', 'value', 'rules', groupId], {});
+ // We cannot use "this.set(...)" here, because groupId may contain dots,
+ // and dots in property path names are totally unsupported by Polymer.
+ // Apparently Polymer picks up this change anyway, otherwise we should
+ // have looked at using MutableData:
+ // https://polymer-library.polymer-project.org/2.0/docs/devguide/data-system#mutable-data
+ this.permission.value.rules[groupId] = {};
// Purposely don't recompute sorted array so that the newly added rule
// is the last item of the array.
@@ -292,7 +297,8 @@
Polymer.dom.flush();
const value = this._rules[this._rules.length - 1].value;
value.added = true;
- this.set(['permission', 'value', 'rules', groupId], value);
+ // See comment above for why we cannot use "this.set(...)" here.
+ this.permission.value.rules[groupId] = value;
this.dispatchEvent(
new CustomEvent('access-modified', {bubbles: true, composed: true}));
}
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index 7919b28..9928b9e 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -312,11 +312,11 @@
element.name = 'Priority';
element.section = 'refs/*';
element.groups = {};
- element.$.groupAutocomplete.text = 'ldap/tests tests';
+ element.$.groupAutocomplete.text = 'ldap/tests te.st';
const e = {
detail: {
value: {
- id: 'ldap:CN=test+test',
+ id: 'ldap:CN=test+te.st',
},
},
};
@@ -325,11 +325,11 @@
assert.equal(Object.keys(element._groupsWithRules).length, 2);
element._handleAddRuleItem(e);
flushAsynchronousOperations();
- assert.deepEqual(element.groups, {'ldap:CN=test test': {
- name: 'ldap/tests tests'}});
+ assert.deepEqual(element.groups, {'ldap:CN=test te.st': {
+ name: 'ldap/tests te.st'}});
assert.equal(element._rules.length, 3);
assert.equal(Object.keys(element._groupsWithRules).length, 3);
- assert.deepEqual(element.permission.value.rules['ldap:CN=test test'],
+ assert.deepEqual(element.permission.value.rules['ldap:CN=test te.st'],
{action: 'ALLOW', min: -2, max: 2, added: true});
// New rule should be removed if cancel from editing.
element.editing = false;
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index f4bd6a6..283bd74 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -205,6 +205,9 @@
<gr-confirm-revert-dialog id="confirmRevertDialog"
class="confirmDialog"
on-confirm="_handleRevertDialogConfirm"
+ commit-message="[[commitMessage]]"
+ change="[[change]]"
+ changes="[[_revertChanges]]"
on-cancel="_handleConfirmDialogCancel"
hidden></gr-confirm-revert-dialog>
<gr-confirm-revert-submission-dialog id="confirmRevertSubmissionDialog"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 2c4ca82..c0a603e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -192,6 +192,11 @@
const AWAIT_CHANGE_ATTEMPTS = 5;
const AWAIT_CHANGE_TIMEOUT_MS = 1000;
+ const REVERT_TYPES = {
+ REVERT_SINGLE_CHANGE: 1,
+ REVERT_SUBMISSION: 2,
+ };
+
/**
* @appliesMixin Gerrit.FireMixin
* @appliesMixin Gerrit.PatchSetMixin
@@ -421,6 +426,7 @@
type: Boolean,
value: true,
},
+ _revertChanges: Array,
};
}
@@ -915,16 +921,13 @@
return null;
}
- _modifyRevertMsg() {
- return this.$.jsAPI.modifyRevertMsg(this.change,
- this.$.confirmRevertDialog.message, this.commitMessage);
- }
-
showRevertDialog() {
- this.$.confirmRevertDialog.populateRevertMessage(
- this.commitMessage, this.change.current_revision);
- this.$.confirmRevertDialog.message = this._modifyRevertMsg();
- this._showActionDialog(this.$.confirmRevertDialog);
+ const query = 'submissionid:' + this.change.submission_id;
+ this.$.restAPI.getChanges('', query)
+ .then(changes => {
+ this._revertChanges = changes;
+ this._showActionDialog(this.$.confirmRevertDialog);
+ });
}
showRevertSubmissionDialog() {
@@ -932,7 +935,7 @@
this.$.restAPI.getChanges('', query)
.then(changes => {
this.$.confirmRevertSubmissionDialog.
- populateRevertSubmissionMessage(
+ _populateRevertSubmissionMessage(
this.commitMessage, this.change, changes);
this._showActionDialog(this.$.confirmRevertSubmissionDialog);
});
@@ -1143,12 +1146,24 @@
);
}
- _handleRevertDialogConfirm() {
+ _handleRevertDialogConfirm(e) {
+ const revertType = e.detail.revertType;
+ const message = e.detail.message;
const el = this.$.confirmRevertDialog;
this.$.overlay.close();
el.hidden = true;
- this._fireAction('/revert', this.actions.revert, false,
- {message: el.message});
+ switch (revertType) {
+ case REVERT_TYPES.REVERT_SINGLE_CHANGE:
+ this._fireAction('/revert', this.actions.revert, false,
+ {message});
+ break;
+ case REVERT_TYPES.REVERT_SUBMISSION:
+ this._fireAction('/revert_submission', this.actions.revert_submission,
+ false, {message});
+ break;
+ default:
+ console.error('invalid revert type');
+ }
}
_handleRevertSubmissionDialogConfirm() {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 1c894ee..9c1a527 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -37,6 +37,7 @@
</test-fixture>
<script>
+ // TODO(dhruvsri): remove use of _populateRevertMessage as it's private
suite('gr-change-actions tests', () => {
let element;
let sandbox;
@@ -795,12 +796,12 @@
});
suite('revert change', () => {
- let alertStub;
let fireActionStub;
setup(() => {
fireActionStub = sandbox.stub(element, '_fireAction');
- alertStub = sandbox.stub(window, 'alert');
+ element.commitMessage = 'random commit message';
+ element.change.current_revision = 'abcdef';
element.actions = {
revert: {
method: 'POST',
@@ -813,50 +814,149 @@
});
test('revert change with plugin hook', done => {
+ const newRevertMsg = 'Modified revert msg';
+ sandbox.stub(element.$.confirmRevertDialog, '_modifyRevertMsg',
+ () => newRevertMsg);
element.change = {
current_revision: 'abc1234',
};
- const newRevertMsg = 'Modified revert msg';
- sandbox.stub(element, '_modifyRevertMsg',
- () => newRevertMsg);
- sandbox.stub(element.$.confirmRevertDialog, 'populateRevertMessage',
- () => 'original msg');
+ sandbox.stub(element.$.confirmRevertDialog,
+ '_populateRevertSubmissionMessage', () => 'original msg');
flush(() => {
- const revertButton =
- element.$$('gr-button[data-action-key="revert"]');
+ const revertButton = element.shadowRoot
+ .querySelector('gr-button[data-action-key="revert"]');
MockInteractions.tap(revertButton);
-
- assert.equal(element.$.confirmRevertDialog.message, newRevertMsg);
- done();
+ flush(() => {
+ assert.equal(element.$.confirmRevertDialog.message, newRevertMsg);
+ done();
+ });
});
});
- test('works', () => {
- element.change = {
- current_revision: 'abc1234',
- };
- sandbox.stub(element.$.confirmRevertDialog, 'populateRevertMessage',
- () => 'original msg');
- const revertButton = element.$$('gr-button[data-action-key="revert"]');
- MockInteractions.tap(revertButton);
+ suite('revert change submitted together', () => {
+ setup(() => {
+ element.change = {
+ submission_id: '199',
+ current_revision: '2000',
+ };
+ sandbox.stub(element.$.restAPI, 'getChanges')
+ .returns(Promise.resolve([
+ {change_id: '12345678901234', topic: 'T', subject: 'random'},
+ {change_id: '23456', topic: 'T', subject: 'a'.repeat(100)},
+ ]));
+ });
- element.$.confirmRevertDialog.message = 'foo message';
- element._handleRevertDialogConfirm();
- assert.notOk(alertStub.called);
+ test('confirm revert dialog shows both options', done => {
+ const revertButton = element.shadowRoot
+ .querySelector('gr-button[data-action-key="revert"]');
+ MockInteractions.tap(revertButton);
+ flush(() => {
+ const confirmRevertDialog = element.$.confirmRevertDialog;
+ const revertSingleChangeLabel = confirmRevertDialog
+ .shadowRoot.querySelector('.revertSingleChange');
+ const revertSubmissionLabel = confirmRevertDialog.
+ shadowRoot.querySelector('.revertSubmission');
+ assert(revertSingleChangeLabel.innerText.trim() ===
+ 'Revert single change');
+ assert(revertSubmissionLabel.innerText.trim() ===
+ 'Revert entire submission (2 Changes)');
+ let expectedMsg = 'Revert submission 199' + '\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>' + '\n' +
+ 'Reverted Changes:' + '\n' +
+ '1234567890:random' + '\n' +
+ '23456:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...' +
+ '\n';
+ assert.equal(confirmRevertDialog.message, expectedMsg);
+ const radioInputs = confirmRevertDialog.shadowRoot
+ .querySelectorAll('input[name="revertOptions"]');
+ MockInteractions.tap(radioInputs[0]);
+ flush(() => {
+ expectedMsg = 'Revert "random commit message"\n\nThis reverts '
+ + 'commit 2000.\n\nReason'
+ + ' for revert: <INSERT REASONING HERE>\n';
+ assert.equal(confirmRevertDialog.message, expectedMsg);
+ done();
+ });
+ });
+ });
- const action = {
- __key: 'revert',
- __type: 'change',
- __primary: false,
- enabled: true,
- label: 'Revert',
- method: 'POST',
- title: 'Revert the change',
- };
- assert.deepEqual(fireActionStub.lastCall.args, [
- '/revert', action, false, {
- message: 'foo message',
- }]);
+ test('message modification is retained on switching', done => {
+ const revertButton = element.shadowRoot
+ .querySelector('gr-button[data-action-key="revert"]');
+ const confirmRevertDialog = element.$.confirmRevertDialog;
+ MockInteractions.tap(revertButton);
+ flush(() => {
+ const radioInputs = confirmRevertDialog.shadowRoot
+ .querySelectorAll('input[name="revertOptions"]');
+ const revertSubmissionMsg = 'Revert submission 199' + '\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>' + '\n' +
+ 'Reverted Changes:' + '\n' +
+ '1234567890:random' + '\n' +
+ '23456:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...' +
+ '\n';
+ const singleChangeMsg =
+ 'Revert "random commit message"\n\nThis reverts '
+ + 'commit 2000.\n\nReason'
+ + ' for revert: <INSERT REASONING HERE>\n';
+ assert.equal(confirmRevertDialog.message, revertSubmissionMsg);
+ const newRevertMsg = revertSubmissionMsg + 'random';
+ const newSingleChangeMsg = singleChangeMsg + 'random';
+ confirmRevertDialog.message = newRevertMsg;
+ MockInteractions.tap(radioInputs[0]);
+ flush(() => {
+ assert.equal(confirmRevertDialog.message, singleChangeMsg);
+ confirmRevertDialog.message = newSingleChangeMsg;
+ MockInteractions.tap(radioInputs[1]);
+ flush(() => {
+ assert.equal(confirmRevertDialog.message, newRevertMsg);
+ MockInteractions.tap(radioInputs[0]);
+ flush(() => {
+ assert.equal(confirmRevertDialog.message, newSingleChangeMsg);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+
+ suite('revert single change', () => {
+ setup(() => {
+ element.change = {
+ submission_id: '199',
+ current_revision: '2000',
+ };
+ sandbox.stub(element.$.restAPI, 'getChanges')
+ .returns(Promise.resolve([
+ {change_id: '12345678901234', topic: 'T', subject: 'random'},
+ ]));
+ });
+
+ test('confirm revert dialog shows one radio button', done => {
+ const revertButton = element.shadowRoot
+ .querySelector('gr-button[data-action-key="revert"]');
+ MockInteractions.tap(revertButton);
+ flush(() => {
+ const confirmRevertDialog = element.$.confirmRevertDialog;
+ const radioInputs = confirmRevertDialog.shadowRoot
+ .querySelectorAll('input[name="revertOptions"]');
+ assert.equal(radioInputs.length, 1);
+ const msg = 'Revert "random commit message"\n\n'
+ + 'This reverts commit 2000.\n\nReason '
+ + 'for revert: <INSERT REASONING HERE>\n';
+ assert.equal(confirmRevertDialog.message, msg);
+ const confirmButton = element.$.confirmRevertDialog.shadowRoot
+ .querySelector('gr-dialog')
+ .shadowRoot.querySelector('#confirm');
+ MockInteractions.tap(confirmButton);
+ flush(() => {
+ assert.equal(fireActionStub.getCall(0).args[0], '/revert');
+ assert.equal(fireActionStub.getCall(0).args[1].__key, 'revert');
+ assert.equal(fireActionStub.getCall(0).args[3].message, msg);
+ done();
+ });
+ });
+ });
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 471560d..02b017b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -135,15 +135,15 @@
line-height: var(--line-height-mono);
margin-right: var(--spacing-l);
margin-bottom: var(--spacing-l);
- /* Account for border and padding */
- max-width: calc(72ch + 2px + 2*var(--spacing-m));
+ /* Account for border and padding and rounding errors. */
+ max-width: calc(72ch + 2px + 2*var(--spacing-m) + 0.4px);
}
.commitMessage gr-linked-text {
word-break: break-word;
}
#commitMessageEditor {
- /* Account for border and padding */
- min-width: calc(72ch + 2px + 2*var(--spacing-m));
+ /* Account for border and padding and rounding errors. */
+ min-width: calc(72ch + 2px + 2*var(--spacing-m) + 0.4px);
}
.editCommitMessage {
margin-top: var(--spacing-l);
@@ -190,10 +190,6 @@
height: 0;
margin-bottom: var(--spacing-l);
}
- #commitMessage.collapsed {
- max-height: 36em;
- overflow: hidden;
- }
#relatedChanges.collapsed {
margin-bottom: var(--spacing-l);
max-height: var(--relation-chain-max-height, 2em);
@@ -449,12 +445,13 @@
</div>
<div
id="commitMessage"
- class$="commitMessage [[_computeCommitClass(_commitCollapsed, _latestCommitMessage)]]">
+ class="commitMessage">
<gr-editable-content id="commitMessageEditor"
editing="[[_editingCommitMessage]]"
content="{{_latestCommitMessage}}"
storage-key="[[_computeCommitMessageKey(_change._number, _change.current_revision)]]"
- remove-zero-width-space>
+ remove-zero-width-space
+ collapsed$="[[_computeCommitMessageCollapsed(_commitCollapsed, _commitCollapsible)]]">
<gr-linked-text pre
content="[[_latestCommitMessage]]"
config="[[_projectConfig.commentlinks]]"
@@ -477,7 +474,7 @@
<div
id="commitCollapseToggle"
class="collapseToggleContainer"
- hidden$="[[_computeCommitToggleHidden(_latestCommitMessage)]]">
+ hidden$="[[!_commitCollapsible]]">
<gr-button
link
id="commitCollapseToggleButton"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 058bce8..7536c2c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -184,7 +184,8 @@
_hideEditCommitMessage: {
type: Boolean,
computed: '_computeHideEditCommitMessage(_loggedIn, ' +
- '_editingCommitMessage, _change, _editMode)',
+ '_editingCommitMessage, _change, _editMode, _commitCollapsed, ' +
+ '_commitCollapsible)',
},
_diffAgainst: String,
/** @type {?string} */
@@ -243,10 +244,16 @@
computed:
'_computeChangeStatusChips(_change, _mergeable, _submitEnabled)',
},
+ /** If false, then the "Show more" button was used to expand. */
_commitCollapsed: {
type: Boolean,
value: true,
},
+ /** Is the "Show more/less" button visible? */
+ _commitCollapsible: {
+ type: Boolean,
+ computed: '_computeCommitCollapsible(_latestCommitMessage)',
+ },
_relatedChangesCollapsed: {
type: Boolean,
value: true,
@@ -543,10 +550,12 @@
return this.changeStatuses(change, options);
}
- _computeHideEditCommitMessage(loggedIn, editing, change, editMode) {
+ _computeHideEditCommitMessage(
+ loggedIn, editing, change, editMode, collapsed, collapsible) {
if (!loggedIn || editing ||
(change && change.status === this.ChangeStatus.MERGED) ||
- editMode) {
+ editMode ||
+ (collapsed && collapsible)) {
return true;
}
@@ -713,10 +722,12 @@
}
_handleReplySent(e) {
+ this.addEventListener('change-details-loaded',
+ () => {
+ this.$.reporting.timeEnd(SEND_REPLY_TIMING_LABEL);
+ }, {once: true});
this.$.replyOverlay.close();
- this._reload().then(() => {
- this.$.reporting.timeEnd(SEND_REPLY_TIMING_LABEL);
- });
+ this._reload();
}
_handleReplyCancel(e) {
@@ -1459,7 +1470,11 @@
// Resolves when the loading flag is set to false, meaning that some
// change content may start appearing.
const loadingFlagSet = detailCompletes
- .then(() => { this._loading = false; })
+ .then(() => {
+ this._loading = false;
+ this.dispatchEvent(new CustomEvent('change-details-loaded',
+ {bubbles: true, composed: true}));
+ })
.then(() => {
this.$.reporting.timeEnd(CHANGE_RELOAD_TIMING_LABEL);
if (opt_isLocationChange) {
@@ -1583,9 +1598,8 @@
return 'Change ' + changeNum;
}
- _computeCommitClass(collapsed, commitMessage) {
- if (this._computeCommitToggleHidden(commitMessage)) { return ''; }
- return collapsed ? 'collapsed' : '';
+ _computeCommitMessageCollapsed(collapsed, collapsible) {
+ return collapsible && collapsed;
}
_computeRelatedChangesClass(collapsed) {
@@ -1622,9 +1636,9 @@
}
}
- _computeCommitToggleHidden(commitMessage) {
- if (!commitMessage) { return true; }
- return commitMessage.split('\n').length < MIN_LINES_FOR_COMMIT_COLLAPSE;
+ _computeCommitCollapsible(commitMessage) {
+ if (!commitMessage) { return false; }
+ return commitMessage.split('\n').length >= MIN_LINES_FOR_COMMIT_COLLAPSE;
}
_getOffsetHeight(element) {
@@ -1652,8 +1666,6 @@
// bottom margin.
const EXTRA_HEIGHT = 30;
let newHeight;
- const hasCommitToggle =
- !this._computeCommitToggleHidden(this._latestCommitMessage);
if (window.matchMedia(`(max-width: ${BREAKPOINT_RELATED_SMALL})`)
.matches) {
@@ -1672,7 +1684,7 @@
MINIMUM_RELATED_MAX_HEIGHT);
newHeight = medRelatedHeight;
} else {
- if (hasCommitToggle) {
+ if (this._commitCollapsible) {
// Make sure the content is lined up if both areas have buttons. If
// the commit message is not collapsed, instead use the change info
// height.
@@ -1695,7 +1707,7 @@
stylesToUpdate['--relation-chain-max-height'] = newHeight + 'px';
// Update the max-height of the relation chain to this new height.
- if (hasCommitToggle) {
+ if (this._commitCollapsible) {
stylesToUpdate['--related-change-btn-top-padding'] = remainder + 'px';
}
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 9c23d0e..1ef79cf 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -1238,14 +1238,16 @@
});
test('commitCollapseToggle functions', () => {
- element._latestCommitMessage = _.times(31, String).join('\n');
+ element._latestCommitMessage = _.times(35, String).join('\n');
assert.isTrue(element._commitCollapsed);
+ assert.isTrue(element._commitCollapsible);
assert.isTrue(
- element.$.commitMessage.classList.contains('collapsed'));
+ element.$.commitMessageEditor.hasAttribute('collapsed'));
MockInteractions.tap(element.$.commitCollapseToggleButton);
assert.isFalse(element._commitCollapsed);
+ assert.isTrue(element._commitCollapsible);
assert.isFalse(
- element.$.commitMessage.classList.contains('collapsed'));
+ element.$.commitMessageEditor.hasAttribute('collapsed'));
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
index fc3a8c1..7bffe8a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.html
@@ -21,6 +21,7 @@
<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<dom-module id="gr-confirm-revert-dialog">
<template>
@@ -37,6 +38,13 @@
display: block;
width: 100%;
}
+ .revertSubmissionLayout {
+ display: flex;
+ }
+ .label {
+ margin-left: var(--spacing-m);
+ margin-bottom: var(--spacing-m);
+ }
iron-autogrow-textarea {
font-family: var(--monospace-font-family);
font-size: var(--font-size-mono);
@@ -50,7 +58,45 @@
on-cancel="_handleCancelTap">
<div class="header" slot="header">Revert Merged Change</div>
<div class="main" slot="main">
+ <div class="revertSubmissionLayout">
+ <input
+ name="revertOptions"
+ type="radio"
+ id="revertSingleChange"
+ on-change="_handleRevertSingleChangeClicked"
+ checked="[[_computeIfSingleRevert(_revertType)]]">
+ <label for="revertSingleChange" class="label revertSingleChange">
+ Revert single change
+ </label>
+ </div>
+ <template is="dom-if" if="[[_showRevertSubmission]]">
+ <div on-click="_handleRevertSubmissionClicked" class="revertSubmissionLayout">
+ <input
+ name="revertOptions"
+ type="radio"
+ id="revertSubmission"
+ checked="[[_computeIfRevertSubmission(_revertType)]]">
+ <label for="revertSubmission" class="label revertSubmission">
+ Revert entire submission ([[changes.length]] Changes)
+ </label>
+ </template>
<gr-endpoint-decorator name="confirm-revert-change">
+ <!-- Duplicating the text-area as a plugin in the case of a single
+ revert will override the entire textarea which should not happen
+ for multiple revert -->
+ <template is="dom-if" if="[[_computeIfSingleRevert(_revertType)]]">
+ <label for="messageInput">
+ Revert Commit Message
+ </label>
+ <iron-autogrow-textarea
+ id="messageInput"
+ class="message"
+ autocomplete="on"
+ max-rows="15"
+ bind-value="{{message}}"></iron-autogrow-textarea>
+ </template>
+ </gr-endpoint-decorator>
+ <template is="dom-if" if="[[_computeIfRevertSubmission(_revertType)]]">
<label for="messageInput">
Revert Commit Message
</label>
@@ -60,9 +106,10 @@
autocomplete="on"
max-rows="15"
bind-value="{{message}}"></iron-autogrow-textarea>
- </gr-endpoint-decorator>
+ </template>
</div>
</gr-dialog>
+ <gr-js-api-interface id="jsAPI"></gr-js-api-interface>
</template>
<script src="gr-confirm-revert-dialog.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
index bf727ec..438440e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog.js
@@ -19,6 +19,13 @@
const ERR_COMMIT_NOT_FOUND =
'Unable to find the commit hash of this change.';
+ const CHANGE_SUBJECT_LIMIT = 50;
+
+ // TODO(dhruvsri): clean up repeated definitions after moving to js modules
+ const REVERT_TYPES = {
+ REVERT_SINGLE_CHANGE: 1,
+ REVERT_SUBMISSION: 2,
+ };
/**
* @appliesMixin Gerrit.FireMixin
@@ -45,12 +52,55 @@
static get properties() {
return {
message: String,
+ _revertType: {
+ type: Number,
+ value: REVERT_TYPES.REVERT_SINGLE_CHANGE,
+ },
+ _showRevertSubmission: {
+ type: Boolean,
+ value: false,
+ },
+ changes: {
+ type: Array,
+ value() { return []; },
+ },
+ change: Object,
+ commitMessage: String,
};
}
- populateRevertMessage(message, commitHash) {
+ static get observers() {
+ return [
+ 'onInputUpdate(change, commitMessage, changes)',
+ ];
+ }
+
+ _computeIfSingleRevert(revertType) {
+ return revertType === REVERT_TYPES.REVERT_SINGLE_CHANGE;
+ }
+
+ _computeIfRevertSubmission(revertType) {
+ return revertType === REVERT_TYPES.REVERT_SUBMISSION;
+ }
+
+ _modifyRevertMsg(change, commitMessage, message) {
+ return this.$.jsAPI.modifyRevertMsg(change,
+ message, commitMessage);
+ }
+
+ onInputUpdate(change, commitMessage, changes) {
+ if (!change || !changes) return;
+ this._populateRevertSingleChangeMessage(
+ change, commitMessage, change.current_revision);
+ if (changes.length > 1) {
+ this._populateRevertSubmissionMessage(
+ change, changes);
+ }
+ }
+
+ _populateRevertSingleChangeMessage(change, commitMessage, commitHash) {
// Figure out what the revert title should be.
- const originalTitle = message.split('\n')[0];
+ const originalTitle = (commitMessage || '').split('\n')[0];
const revertTitle = `Revert "${originalTitle}"`;
if (!commitHash) {
this.fire('show-alert', {message: ERR_COMMIT_NOT_FOUND});
@@ -58,20 +108,77 @@
}
const revertCommitText = `This reverts commit ${commitHash}.`;
- this.message = `${revertTitle}\n\n${revertCommitText}\n\n` +
+ this.revertSingleChangeMessage =
+ `${revertTitle}\n\n${revertCommitText}\n\n` +
`Reason for revert: <INSERT REASONING HERE>\n`;
+ // This is to give plugins a chance to update message
+ this.revertSingleChangeMessage =
+ this._modifyRevertMsg(change, commitMessage,
+ this.revertSingleChangeMessage);
+ this.message = this.revertSingleChangeMessage;
+ }
+
+ _getTrimmedChangeSubject(subject) {
+ if (!subject) return '';
+ if (subject.length < CHANGE_SUBJECT_LIMIT) return subject;
+ return subject.substring(0, CHANGE_SUBJECT_LIMIT) + '...';
+ }
+
+ _modifyRevertSubmissionMsg(change) {
+ return this.$.jsAPI.modifyRevertSubmissionMsg(change,
+ this.revertSubmissionMessage, this.commitMessage);
+ }
+
+ _populateRevertSubmissionMessage(change, changes) {
+ // Follow the same convention of the revert
+ const commitHash = change.current_revision;
+ if (!commitHash) {
+ this.fire('show-alert', {message: ERR_COMMIT_NOT_FOUND});
+ return;
+ }
+ if (!changes || changes.length <= 1) return;
+ const submissionId = change.submission_id;
+ const revertTitle = 'Revert submission ' + submissionId;
+ this.changes = changes;
+ this.revertSubmissionMessage = revertTitle + '\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>\n';
+ this.revertSubmissionMessage += 'Reverted Changes:\n';
+ changes.forEach(change => {
+ this.revertSubmissionMessage += change.change_id.substring(0, 10) + ':'
+ + this._getTrimmedChangeSubject(change.subject) + '\n';
+ });
+ this.revertSubmissionMessage = this._modifyRevertSubmissionMsg(change);
+ this.message = this.revertSubmissionMessage;
+ this._revertType = REVERT_TYPES.REVERT_SUBMISSION;
+ this._showRevertSubmission = true;
+ }
+
+ _handleRevertSingleChangeClicked() {
+ if (this._revertType === REVERT_TYPES.REVERT_SINGLE_CHANGE) return;
+ this.revertSubmissionMessage = this.message;
+ this.message = this.revertSingleChangeMessage;
+ this._revertType = REVERT_TYPES.REVERT_SINGLE_CHANGE;
+ }
+
+ _handleRevertSubmissionClicked() {
+ if (this._revertType === REVERT_TYPES.REVERT_SUBMISSION) return;
+ this._revertType = REVERT_TYPES.REVERT_SUBMISSION;
+ this.revertSingleChangeMessage = this.message;
+ this.message = this.revertSubmissionMessage;
}
_handleConfirmTap(e) {
e.preventDefault();
e.stopPropagation();
- this.fire('confirm', null, {bubbles: false});
+ this.fire('confirm', {revertType: this._revertType,
+ message: this.message}, {bubbles: false});
}
_handleCancelTap(e) {
e.preventDefault();
e.stopPropagation();
- this.fire('cancel', null, {bubbles: false});
+ this.fire('cancel', {revertType: this._revertType},
+ {bubbles: false});
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
index dbdfba2..1d28d32 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
@@ -50,13 +50,14 @@
assert.isNotOk(element.message);
const alertStub = sandbox.stub();
element.addEventListener('show-alert', alertStub);
- element.populateRevertMessage('not a commitHash in sight', undefined);
+ element._populateRevertSingleChangeMessage({},
+ 'not a commitHash in sight', undefined);
assert.isTrue(alertStub.calledOnce);
});
test('single line', () => {
assert.isNotOk(element.message);
- element.populateRevertMessage(
+ element._populateRevertSingleChangeMessage({},
'one line commit\n\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert "one line commit"\n\n' +
@@ -67,7 +68,7 @@
test('multi line', () => {
assert.isNotOk(element.message);
- element.populateRevertMessage(
+ element._populateRevertSingleChangeMessage({},
'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert "many lines"\n\n' +
@@ -78,7 +79,7 @@
test('issue above change id', () => {
assert.isNotOk(element.message);
- element.populateRevertMessage(
+ element._populateRevertSingleChangeMessage({},
'much lines\nvery\n\ncommit\n\nBug: Issue 42\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert "much lines"\n\n' +
@@ -89,7 +90,7 @@
test('revert a revert', () => {
assert.isNotOk(element.message);
- element.populateRevertMessage(
+ element._populateRevertSingleChangeMessage({},
'Revert "one line commit"\n\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert "Revert "one line commit""\n\n' +
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
index d59f5cd..ae8dfa5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
@@ -50,7 +50,7 @@
};
}
- getTrimmedChangeSubject(subject) {
+ _getTrimmedChangeSubject(subject) {
if (!subject) return '';
if (subject.length < CHANGE_SUBJECT_LIMIT) return subject;
return subject.substring(0, CHANGE_SUBJECT_LIMIT) + '...';
@@ -61,7 +61,7 @@
this.message, this.commitMessage);
}
- populateRevertSubmissionMessage(message, change, changes) {
+ _populateRevertSubmissionMessage(message, change, changes) {
// Follow the same convention of the revert
const commitHash = change.current_revision;
if (!commitHash) {
@@ -77,7 +77,7 @@
changes = changes || [];
changes.forEach(change => {
this.message += change.change_id.substring(0, 10) + ': ' +
- this.getTrimmedChangeSubject(change.subject) + '\n';
+ this._getTrimmedChangeSubject(change.subject) + '\n';
});
this.message = this._modifyRevertSubmissionMsg(change);
}
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
index cc4bd54..af99c7e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
@@ -51,7 +51,7 @@
assert.isNotOk(element.message);
const alertStub = sandbox.stub();
element.addEventListener('show-alert', alertStub);
- element.populateRevertSubmissionMessage(
+ element._populateRevertSubmissionMessage(
'not a commitHash in sight'
);
assert.isTrue(alertStub.calledOnce);
@@ -59,7 +59,7 @@
test('single line', () => {
assert.isNotOk(element.message);
- element.populateRevertSubmissionMessage(
+ element._populateRevertSubmissionMessage(
'one line commit\n\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert submission\n\n' +
@@ -69,7 +69,7 @@
test('multi line', () => {
assert.isNotOk(element.message);
- element.populateRevertSubmissionMessage(
+ element._populateRevertSubmissionMessage(
'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert submission\n\n' +
@@ -79,7 +79,7 @@
test('issue above change id', () => {
assert.isNotOk(element.message);
- element.populateRevertSubmissionMessage(
+ element._populateRevertSubmissionMessage(
'test \nvery\n\ncommit\n\nBug: Issue 42\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert submission\n\n' +
@@ -89,7 +89,7 @@
test('revert a revert', () => {
assert.isNotOk(element.message);
- element.populateRevertSubmissionMessage(
+ element._populateRevertSubmissionMessage(
'Revert "one line commit"\n\nChange-Id: abcdefg\n',
'abcd123');
const expected = 'Revert submission\n\n' +
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index 359a039..e46f959 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -192,15 +192,20 @@
}
}
- /**
- * For Polymer 2, use shadowRoot.getSelection instead.
- */
_getSelection() {
- const diffHost = util.querySelector(document.body, 'gr-diff');
- const selection = diffHost &&
- diffHost.shadowRoot &&
- diffHost.shadowRoot.getSelection();
- return selection ? selection: window.getSelection();
+ const diffHosts = util.querySelectorAll(document.body, 'gr-diff');
+ if (!diffHosts.length) return window.getSelection();
+
+ const curDiffHost = diffHosts.find(diffHost => {
+ if (!diffHost || !diffHost.shadowRoot) return false;
+ const selection = diffHost.shadowRoot.getSelection();
+ // Pick the one with valid selection:
+ // https://developer.mozilla.org/en-US/docs/Web/API/Selection/type
+ return selection && selection.type !== 'None';
+ });
+
+ return curDiffHost ?
+ curDiffHost.shadowRoot.getSelection(): window.getSelection();
}
/**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index ef22188..5d32e9b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -815,10 +815,9 @@
// for each line from the start.
let lastEl;
for (const threadEl of addedThreadEls) {
- const lineNumString = threadEl.getAttribute('line-num') || 'FILE';
const commentSide = threadEl.getAttribute('comment-side');
- const lineEl = this.$.diffBuilder.getLineElByNumber(
- lineNumString, commentSide);
+ const lineEl = this._getLineElement(threadEl,
+ commentSide);
const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
const contentEl = contentText.parentElement;
const threadGroupEl = this._getOrCreateThreadGroup(
@@ -844,6 +843,18 @@
});
}
+ _getLineElement(threadEl, commentSide) {
+ const lineNumString = threadEl.getAttribute('line-num') || 'FILE';
+ const lineEl = this.$.diffBuilder.getLineElByNumber(
+ lineNumString, commentSide);
+ if (lineEl) {
+ return lineEl;
+ }
+ // It is possible to add comment to non-existing line via API
+ threadEl.invalidLineNumber = true;
+ return this.$.diffBuilder.getLineElByNumber('FILE', commentSide);
+ }
+
_unobserveIncrementalNodes() {
if (this._incrementalNodeObserver) {
Polymer.dom(this).unobserveNodes(this._incrementalNodeObserver);
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
index cce1d19..7bc93b5 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
@@ -16,6 +16,8 @@
-->
<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="/bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -78,6 +80,18 @@
margin-left: var(--spacing-m);
font-style: italic;
}
+ .invalidLineNumber {
+ padding: var(--spacing-m);
+ }
+ .invalidLineNumberText {
+ color: var(--error-text-color);
+ }
+ .invalidLineNumberIcon {
+ color: var(--error-text-color);
+ vertical-align: top;
+ margin-right: var(--spacing-s);
+ }
+
</style>
<template is="dom-if" if="[[showFilePath]]">
<div class="pathInfo">
@@ -86,6 +100,11 @@
</div>
</template>
<div id="container" class$="[[_computeHostClass(unresolved, isRobotComment)]]">
+ <template is="dom-if" if="[[invalidLineNumber]]">
+ <div class="invalidLineNumber">
+ <span class="invalidLineNumberText"><iron-icon icon="gr-icons:error" class="invalidLineNumberIcon"></iron-icon>This comment thread is attached to non-existing line [[lineNum]].</span>
+ </div>
+ </template>
<template id="commentList" is="dom-repeat" items="[[_orderedComments]]"
as="comment">
<gr-comment
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
index d8a56f8..00ff03a 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
@@ -124,6 +124,11 @@
type: Boolean,
value: false,
},
+ /** It is possible to add comment to non-existing line via API */
+ invalidLineNumber: {
+ type: Number,
+ reflectToAttribute: true,
+ },
/** Necessary only if showFilePath is true or when used with gr-diff */
lineNum: {
type: Number,
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
index 69c280b..627f948 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.html
@@ -38,6 +38,10 @@
box-shadow: var(--elevation-level-1);
padding: var(--spacing-m);
}
+ :host([collapsed]) .viewer {
+ max-height: 36em;
+ overflow: hidden;
+ }
.editor iron-autogrow-textarea {
background-color: var(--view-background-color);
width: 100%;
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
index 743923b..5ecae00 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -54,6 +54,8 @@
<g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g>
<!-- This SVG is a copy from material.io https://material.io/icons/#ic_hourglass_full-->
<g id="hourglass"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6z"/><path d="M0 0h24v24H0V0z" fill="none"/></g>
+ <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
+ <g id="error"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></g>
<!-- This is a custom PolyGerrit SVG -->
<g id="side-by-side"><path d="M17.1578947,10.8888889 L2.84210526,10.8888889 C2.37894737,10.8888889 2,11.2888889 2,11.7777778 L2,17.1111111 C2,17.6 2.37894737,18 2.84210526,18 L17.1578947,18 C17.6210526,18 18,17.6 18,17.1111111 L18,11.7777778 C18,11.2888889 17.6210526,10.8888889 17.1578947,10.8888889 Z M17.1578947,2 L2.84210526,2 C2.37894737,2 2,2.4 2,2.88888889 L2,8.22222222 C2,8.71111111 2.37894737,9.11111111 2.84210526,9.11111111 L17.1578947,9.11111111 C17.6210526,9.11111111 18,8.71111111 18,8.22222222 L18,2.88888889 C18,2.4 17.6210526,2 17.1578947,2 Z M16.1973628,2 L2.78874238,2 C2.35493407,2 2,2.4 2,2.88888889 L2,8.22222222 C2,8.71111111 2.35493407,9.11111111 2.78874238,9.11111111 L16.1973628,9.11111111 C16.6311711,9.11111111 16.9861052,8.71111111 16.9861052,8.22222222 L16.9861052,2.88888889 C16.9861052,2.4 16.6311711,2 16.1973628,2 Z" id="Shape" transform="scale(1.2) translate(10.000000, 10.000000) rotate(-90.000000) translate(-10.000000, -10.000000)"/></g>
<!-- This is a custom PolyGerrit SVG -->
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
index 8821bcb..5dee2fe 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.js
@@ -104,6 +104,12 @@
fn.call(this);
} else if (iters++ < AWAIT_MAX_ITERS) {
step.call(this);
+ } else {
+ // TODO(crbug.com/gerrit/10774): Once this is confirmed as the root
+ // cause of the bug, fix it by either making sure to resolve the fn
+ // function or find a better way to listen on the overlay being
+ // shown.
+ console.warn('gr-overlay _awaitOpen failed to resolve');
}
}, AWAIT_STEP);
};
diff --git a/polygerrit-ui/app/scripts/util.js b/polygerrit-ui/app/scripts/util.js
index 672c43f..565b9b3 100644
--- a/polygerrit-ui/app/scripts/util.js
+++ b/polygerrit-ui/app/scripts/util.js
@@ -105,7 +105,7 @@
*/
util.querySelector = (el, selector) => {
let nodes = [el];
- let element = null;
+ let result = null;
while (nodes.length) {
const node = nodes.pop();
@@ -113,19 +113,49 @@
if (!node || !node.querySelector) continue;
// Try find it with native querySelector directly
- element = node.querySelector(selector);
+ result = node.querySelector(selector);
- if (element) {
+ if (result) {
break;
- } else if (node.shadowRoot) {
- // If shadowHost detected, add the host and its children
- nodes = nodes.concat(Array.from(node.children));
- nodes.push(node.shadowRoot);
- } else {
- nodes = nodes.concat(Array.from(node.children));
}
+
+ // Add all nodes with shadowRoot and loop through
+ const allShadowNodes = [...node.querySelectorAll('*')]
+ .filter(child => !!child.shadowRoot)
+ .map(child => child.shadowRoot);
+ nodes = nodes.concat(allShadowNodes);
}
- return element;
+ return result;
+ };
+
+ /**
+ * Query selector all dom elements matching with certain selector.
+ *
+ * This is shadow DOM compatible, but only works when selector is within
+ * one shadow host, won't work if your selector is crossing
+ * multiple shadow hosts.
+ *
+ * Note: this can be very expensive, only use when have to.
+ */
+ util.querySelectorAll = (el, selector) => {
+ let nodes = [el];
+ const results = new Set();
+ while (nodes.length) {
+ const node = nodes.pop();
+
+ if (!node || !node.querySelectorAll) continue;
+
+ // Try find all from regular children
+ [...node.querySelectorAll(selector)]
+ .forEach(el => results.add(el));
+
+ // Add all nodes with shadowRoot and loop through
+ const allShadowNodes = [...node.querySelectorAll('*')]
+ .filter(child => !!child.shadowRoot)
+ .map(child => child.shadowRoot);
+ nodes = nodes.concat(allShadowNodes);
+ }
+ return [...results];
};
window.util = util;
diff --git a/resources/com/google/gerrit/pgm/init/libraries.config b/resources/com/google/gerrit/pgm/init/libraries.config
deleted file mode 100644
index 3d3545b..0000000
--- a/resources/com/google/gerrit/pgm/init/libraries.config
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# 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.
-
-[library "mysqlDriver"]
- name = MySQL Connector/J 5.1.43
- url = https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.43/mysql-connector-java-5.1.43.jar
- sha1 = dee9103eec0d877f3a21c82d4d9e9f4fbd2d6e0a
- remove = mysql-connector-java-.*[.]jar
-
-[library "mariadbDriver"]
- name = MariaDB Connector/J 2.3.0
- url = https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/2.3.0/mariadb-java-client-2.3.0.jar
- sha1 = c2b1a6002a169757d0649449288e9b3b776af76b
- remove = mariadb-java-client-.*[.]jar
-
-[library "oracleDriver"]
- name = Oracle JDBC driver 11g Release 2 (11.2.0)
- url = file:///u01/app/oracle/product/11.2.0/xe/jdbc/lib/ojdbc6.jar
- sha1 = 2f89cd9176772c3a6c261ce6a8e3d0d4425f5679
- remove = ojdbc6.jar
-
-[library "db2Driver"]
- name = DB2 Type 4 JDBC driver (10.5)
- url = file:///opt/ibm/db2/V10.5/java/db2jcc4.jar
- sha1 = 9344d4fd41d6511f2d1d1deb7759056495b3a39b
- needs = db2DriverLicense
- remove = db2jcc4.jar
-
-# Omit SHA-1 for license JAR as it's not stable and depends on the product
-# the customer has purchased.
-[library "db2DriverLicense"]
- name = DB2 Type 4 JDBC driver license (10.5)
- url = file:///opt/ibm/db2/V10.5/java/db2jcc_license_cu.jar
- remove = db2jcc_license_cu.jar
-
-[library "hanaDriver"]
- name = HANA JDBC driver
- url = file:///usr/sap/hdbclient/ngdbc.jar
- remove = ngdbc.jar
diff --git a/tools/bzl/asciidoc.bzl b/tools/bzl/asciidoc.bzl
index f3c4646..1e7ec96 100644
--- a/tools/bzl/asciidoc.bzl
+++ b/tools/bzl/asciidoc.bzl
@@ -1,7 +1,7 @@
def documentation_attributes():
return [
"toc2",
- 'newline="\\n"',
+ "newline=\"\\n\"",
'asterisk="*"',
'plus="+"',
'caret="^"',
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index 5cc1511..acf01fa 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -102,8 +102,8 @@
# and httpasyncclient as necessary.
maven_jar(
name = "elasticsearch-rest-client",
- artifact = "org.elasticsearch.client:elasticsearch-rest-client:7.5.1",
- sha1 = "094c155906dc94146fc5adc344ea2c676d487cf2",
+ artifact = "org.elasticsearch.client:elasticsearch-rest-client:7.5.2",
+ sha1 = "e11393f600a425b7f62e6f653e19a9e53556fd79",
)
maven_jar(
@@ -126,50 +126,6 @@
sha1 = "dc13ae4faca6df981fc7aeb5a522d9db446d5d50",
)
- POWERM_VERS = "1.6.1"
-
- maven_jar(
- name = "powermock-module-junit4",
- artifact = "org.powermock:powermock-module-junit4:" + POWERM_VERS,
- sha1 = "ea8530b2848542624f110a393513af397b37b9cf",
- )
-
- maven_jar(
- name = "powermock-module-junit4-common",
- artifact = "org.powermock:powermock-module-junit4-common:" + POWERM_VERS,
- sha1 = "7222ced54dabc310895d02e45c5428ca05193cda",
- )
-
- maven_jar(
- name = "powermock-reflect",
- artifact = "org.powermock:powermock-reflect:" + POWERM_VERS,
- sha1 = "97d25eda8275c11161bcddda6ef8beabd534c878",
- )
-
- maven_jar(
- name = "powermock-api-easymock",
- artifact = "org.powermock:powermock-api-easymock:" + POWERM_VERS,
- sha1 = "aa740ecf89a2f64d410b3d93ef8cd6833009ef00",
- )
-
- maven_jar(
- name = "powermock-api-support",
- artifact = "org.powermock:powermock-api-support:" + POWERM_VERS,
- sha1 = "592ee6d929c324109d3469501222e0c76ccf0869",
- )
-
- maven_jar(
- name = "powermock-core",
- artifact = "org.powermock:powermock-core:" + POWERM_VERS,
- sha1 = "5afc1efce8d44ed76b30af939657bd598e45d962",
- )
-
- maven_jar(
- name = "javassist",
- artifact = "org.javassist:javassist:3.22.0-GA",
- sha1 = "3e83394258ae2089be7219b971ec21a8288528ad",
- )
-
TESTCONTAINERS_VERSION = "1.12.4"
maven_jar(