Merge "Add PG UI support for new changes with base commit"
diff --git a/Documentation/concept-changes.txt b/Documentation/concept-changes.txt
index 6bd484b..7320a50 100644
--- a/Documentation/concept-changes.txt
+++ b/Documentation/concept-changes.txt
@@ -169,7 +169,7 @@
Gerrit uses a Change-Id to identify which patch sets belong to the same review.
For example, you make a change to a project. A reviewer supplies some feedback,
-which you address in a second commit. By assigning the same Change-Id to both
+which you address in an amended commit. By assigning the same Change-Id to both
commits, Gerrit can attach those commits to the same change.
Change-Ids are appended to the end of a commit message, and resemble the
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index cadab83..a79b7a5 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -942,6 +942,11 @@
cache should be flushed. Newly inserted projects do not require
a cache flush, as they will be read upon first reference.
+cache `"prolog_rules"`::
++
+Caches parsed `rules.pl` contents for each project. This cache uses the same
+size as the `projects` cache, and cannot be configured independently.
+
cache `"sshkeys"`::
+
Caches unpacked versions of user SSH keys, so the internal SSH daemon
@@ -1166,6 +1171,15 @@
+
Default is true.
+[[change.enableParallelFormatting]]change.enableParallelFormatting::
++
+Whether or not changes can be formatted in parallel when requesting
+multiple changes at once. An example for this is Dashboards.
++
+This setting is experimental.
++
+Default is `false`.
+
[[change.showAssigneeInChangesTable]]change.showAssigneeInChangesTable::
+
Show assignee field in changes table. If set to false, assignees will
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 03f8d70..ef6a488 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -11,7 +11,7 @@
in favor of Soy, and Velocity templates that modify text emails are no longer
supported.
-== Template Locations and Extensions:
+== Template Locations and Extensions
The default example templates reside under: `'$site_path'/etc/mail` and are
terminated with the double extension `.soy.example`. Modifying these example
@@ -19,7 +19,7 @@
example template to an equivalently named file without the `.example` extension
and modifying it will allow an administrator to customize the template.
-== Supported Mail Templates:
+== Supported Mail Templates
Each mail that Gerrit sends out is controlled by at least one template. These
are listed below. Change emails are influenced by two additional templates,
@@ -225,10 +225,9 @@
+
The project name with the path abbreviated.
-$instanceName::
+$instanceAndProjectName::
+
-The Gerrit instance name, as defined in the
-link:config-gerrit.html#gerrit.instanceName[configuration].
+The Gerrit instance name, followed by the short project name
$addInstanceNameInSubject::
+
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 89c4b84..ccad352 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -363,18 +363,6 @@
`lib/jgit/jgit.bzl` setting LOCAL_JGIT_REPO to a directory holding a
JGit repository.
-[[clean-download-cache]]
-=== Cleaning The download cache
-
-The cache for downloaded artifacts is located in
-`~/.gerritcodereview/bazel-cache/downloaded-artifacts`.
-
-If you really do need to clean the download cache manually, then:
-
-----
- rm -rf ~/.gerritcodereview/bazel-cache/downloaded-artifacts
-----
-
[[local-action-cache]]
To accelerate builds, local action cache can be activated. Note, that this
@@ -423,7 +411,6 @@
[NOTE] `experimental_repository_cache` must be absolute path. Expansion of `~` is
unfortunately not supported yet. This is also the reason why we can't activate this
feature by default yet (by adjusting tools/bazel.rc file).
-
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 59a4608..4b8922a 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -446,6 +446,16 @@
"mem": 99
}
},
+ "prolog_rules": {
+ "type": "MEM",
+ "entries": {
+ "mem": 35
+ },
+ "average_get": "103.0ms",
+ "hit_ratio": {
+ "mem": 99
+ }
+ },
"quota-repo_size": {
"type": "DISK",
"entries": {
@@ -516,6 +526,7 @@
"plugin_resources",
"project_list",
"projects",
+ "prolog_rules",
"quota-repo_size",
"sshkeys",
"web_sessions"
diff --git a/WORKSPACE b/WORKSPACE
index 4d1f81b..c15c4ef 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1141,8 +1141,8 @@
bower_archive(
name = "polymer-resin",
package = "polymer/polymer-resin",
- sha1 = "93ac118f2b9209cfbfd6dc8022d9492743d17f24",
- version = "1.2.7",
+ sha1 = "5cb65081d461e710252a1ba1e671fe4c290356ef",
+ version = "1.2.8",
)
bower_archive(
@@ -1177,9 +1177,9 @@
bower_archive(
name = "web-component-tester",
- package = "web-component-tester",
- sha1 = "4e778f8b7d784ba2a069d83d0cd146125c5c4fcb",
- version = "5.0.1",
+ package = "polymer/web-component-tester",
+ sha1 = "62739cb633fccfddc5eeed98e9e3f69cd0388b5b",
+ version = "6.5.0",
)
# Bower component transitive dependencies.
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java
index d2e5d49..43281bd 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java
@@ -69,9 +69,7 @@
List<AgreementInfo> agreements = new ArrayList<>();
JsArray<AgreementInfo> contributorAgreements = _contributorAgreements();
if (contributorAgreements != null) {
- for (AgreementInfo a : Natives.asList(contributorAgreements)) {
- agreements.add(a);
- }
+ agreements.addAll(Natives.asList(contributorAgreements));
}
return agreements;
}
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/DownloadInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/DownloadInfo.java
index 8d56a1c..a22a1e8 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/DownloadInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/DownloadInfo.java
@@ -31,9 +31,7 @@
public final List<String> archives() {
List<String> archives = new ArrayList<>();
- for (String f : Natives.asList(_archives())) {
- archives.add(f);
- }
+ archives.addAll(Natives.asList(_archives()));
return archives;
}
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index fbf3b84..bf01615 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -101,6 +101,8 @@
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.group.db.Groups;
+import com.google.gerrit.server.index.account.AccountIndex;
+import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -271,6 +273,7 @@
protected BlockStrategy noSleepBlockStrategy = t -> {}; // Don't sleep in tests.
@Inject private ChangeIndexCollection changeIndexes;
+ @Inject private AccountIndexCollection accountIndexes;
@Inject private EventRecorder.Factory eventRecorderFactory;
@Inject private InProcessProtocol inProcessProtocol;
@Inject private Provider<AnonymousUser> anonymousUser;
@@ -878,6 +881,23 @@
};
}
+ protected AutoCloseable disableAccountIndex() {
+ AccountIndex searchIndex = accountIndexes.getSearchIndex();
+ if (!(searchIndex instanceof DisabledAccountIndex)) {
+ accountIndexes.setSearchIndex(new DisabledAccountIndex(searchIndex), false);
+ }
+
+ return new AutoCloseable() {
+ @Override
+ public void close() {
+ AccountIndex searchIndex = accountIndexes.getSearchIndex();
+ if (searchIndex instanceof DisabledAccountIndex) {
+ accountIndexes.setSearchIndex(((DisabledAccountIndex) searchIndex).unwrap(), false);
+ }
+ }
+ };
+ }
+
protected static Gson newGson() {
return OutputFormat.JSON_COMPACT.newGson();
}
@@ -1286,9 +1306,7 @@
protected void assertDiffForNewFile(
DiffInfo diff, RevCommit commit, String path, String expectedContentSideB) throws Exception {
List<String> expectedLines = new ArrayList<>();
- for (String line : expectedContentSideB.split("\n")) {
- expectedLines.add(line);
- }
+ Collections.addAll(expectedLines, expectedContentSideB.split("\n"));
assertThat(diff.binary).isNull();
assertThat(diff.changeType).isEqualTo(ChangeType.ADDED);
diff --git a/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index d8dc605..3acee77 100644
--- a/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -166,13 +166,7 @@
public Context disableDb() {
Context old = current.get();
- SchemaFactory<ReviewDb> sf =
- new SchemaFactory<ReviewDb>() {
- @Override
- public ReviewDb open() {
- return new DisabledReviewDb();
- }
- };
+ SchemaFactory<ReviewDb> sf = DisabledReviewDb::new;
Context ctx = new Context(sf, old.session, old.user, old.created);
current.set(ctx);
diff --git a/java/com/google/gerrit/acceptance/DisabledAccountIndex.java b/java/com/google/gerrit/acceptance/DisabledAccountIndex.java
new file mode 100644
index 0000000..91baafb
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/DisabledAccountIndex.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2018 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.acceptance;
+
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.index.account.AccountIndex;
+
+/** This class wraps an index and assumes the search index can't handle any queries. */
+public class DisabledAccountIndex implements AccountIndex {
+ private final AccountIndex index;
+
+ public DisabledAccountIndex(AccountIndex index) {
+ this.index = index;
+ }
+
+ public AccountIndex unwrap() {
+ return index;
+ }
+
+ @Override
+ public Schema<AccountState> getSchema() {
+ return index.getSchema();
+ }
+
+ @Override
+ public void close() {
+ index.close();
+ }
+
+ @Override
+ public void replace(AccountState obj) {
+ throw new UnsupportedOperationException("AccountIndex is disabled");
+ }
+
+ @Override
+ public void delete(Account.Id key) {
+ throw new UnsupportedOperationException("AccountIndex is disabled");
+ }
+
+ @Override
+ public void deleteAll() {
+ throw new UnsupportedOperationException("AccountIndex is disabled");
+ }
+
+ @Override
+ public DataSource<AccountState> getSource(Predicate<AccountState> p, QueryOptions opts) {
+ throw new UnsupportedOperationException("AccountIndex is disabled");
+ }
+
+ @Override
+ public void markReady(boolean ready) {
+ throw new UnsupportedOperationException("AccountIndex is disabled");
+ }
+}
diff --git a/java/com/google/gerrit/common/data/AccessSection.java b/java/com/google/gerrit/common/data/AccessSection.java
index 95f48b1..82dc620 100644
--- a/java/com/google/gerrit/common/data/AccessSection.java
+++ b/java/com/google/gerrit/common/data/AccessSection.java
@@ -18,7 +18,6 @@
import com.google.gerrit.reviewdb.client.Project;
import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -94,11 +93,7 @@
public void removePermission(String name) {
if (permissions != null) {
- for (Iterator<Permission> itr = permissions.iterator(); itr.hasNext(); ) {
- if (name.equalsIgnoreCase(itr.next().getName())) {
- itr.remove();
- }
- }
+ permissions.removeIf(permission -> name.equalsIgnoreCase(permission.getName()));
}
}
diff --git a/java/com/google/gerrit/common/data/Permission.java b/java/com/google/gerrit/common/data/Permission.java
index 4ee176b..dff30d7 100644
--- a/java/com/google/gerrit/common/data/Permission.java
+++ b/java/com/google/gerrit/common/data/Permission.java
@@ -177,11 +177,7 @@
public void removeRule(GroupReference group) {
if (rules != null) {
- for (Iterator<PermissionRule> itr = rules.iterator(); itr.hasNext(); ) {
- if (sameGroup(itr.next(), group)) {
- itr.remove();
- }
- }
+ rules.removeIf(permissionRule -> sameGroup(permissionRule, group));
}
}
diff --git a/java/com/google/gerrit/common/data/SubmitRecord.java b/java/com/google/gerrit/common/data/SubmitRecord.java
index ae8b2bb..8638d6d 100644
--- a/java/com/google/gerrit/common/data/SubmitRecord.java
+++ b/java/com/google/gerrit/common/data/SubmitRecord.java
@@ -32,7 +32,7 @@
}
// The change can be submitted, unless at least one plugin prevents it.
- return in.stream().noneMatch(r -> r.status != Status.OK);
+ return in.stream().map(SubmitRecord::status).allMatch(SubmitRecord.Status::allowsSubmission);
}
public enum Status {
@@ -56,7 +56,11 @@
*
* <p>Additional detail may be available in {@link SubmitRecord#errorMessage}.
*/
- RULE_ERROR
+ RULE_ERROR;
+
+ private boolean allowsSubmission() {
+ return this == OK || this == FORCED;
+ }
}
public Status status;
@@ -178,4 +182,8 @@
public int hashCode() {
return Objects.hash(status, labels, errorMessage, requirements);
}
+
+ private Status status() {
+ return status;
+ }
}
diff --git a/java/com/google/gerrit/extensions/common/GitPerson.java b/java/com/google/gerrit/extensions/common/GitPerson.java
index 904829c..8ed919e 100644
--- a/java/com/google/gerrit/extensions/common/GitPerson.java
+++ b/java/com/google/gerrit/extensions/common/GitPerson.java
@@ -51,6 +51,6 @@
+ date
+ ", tz="
+ tz
- + "}".toString();
+ + "}";
}
}
diff --git a/java/com/google/gerrit/extensions/common/WebLinkInfo.java b/java/com/google/gerrit/extensions/common/WebLinkInfo.java
index 3af5aba..84fd970 100644
--- a/java/com/google/gerrit/extensions/common/WebLinkInfo.java
+++ b/java/com/google/gerrit/extensions/common/WebLinkInfo.java
@@ -62,6 +62,6 @@
+ url
+ ", target"
+ target
- + "}".toString();
+ + "}";
}
}
diff --git a/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java b/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
index 6ec5f3b..2642a543 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
+++ b/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
@@ -189,6 +189,6 @@
}
private static boolean isGerritLogin(HttpServletRequest request) {
- return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
+ return request.getRequestURI().contains(GERRIT_LOGIN);
}
}
diff --git a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index 6202cfc..6090fed 100644
--- a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -364,6 +364,6 @@
}
private static boolean isGerritLogin(HttpServletRequest request) {
- return request.getRequestURI().indexOf(OAuthSessionOverOpenID.GERRIT_LOGIN) >= 0;
+ return request.getRequestURI().contains(OAuthSessionOverOpenID.GERRIT_LOGIN);
}
}
diff --git a/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java b/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
index fe3d9ee..2e8585d 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
@@ -90,6 +90,6 @@
}
private static boolean isGerritLogin(HttpServletRequest request) {
- return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
+ return request.getRequestURI().contains(GERRIT_LOGIN);
}
}
diff --git a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 4000610..a971fc3 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -172,10 +172,7 @@
pape.setMaxAuthAge(papeMaxAuthAge);
aReq.addExtension(pape);
}
- } catch (MessageException e) {
- log.error("Cannot create OpenID redirect for " + openidIdentifier, e);
- return new DiscoveryResult(DiscoveryResult.Status.ERROR);
- } catch (ConsumerException e) {
+ } catch (MessageException | ConsumerException e) {
log.error("Cannot create OpenID redirect for " + openidIdentifier, e);
return new DiscoveryResult(DiscoveryResult.Status.ERROR);
}
diff --git a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index ba2a063..19172e2 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -471,7 +471,7 @@
String queryString = req.getQueryString();
if (queryString != null && !queryString.isEmpty()) {
- token = token.concat("?" + queryString);
+ token = token + "?" + queryString;
}
return (loginUrl + Url.encode(token));
}
diff --git a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 969b9ff..b490810 100644
--- a/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -202,13 +202,7 @@
return null;
}
- plugin.add(
- new RegistrationHandle() {
- @Override
- public void remove() {
- filter.destroy();
- }
- });
+ plugin.add(filter::destroy);
return filter;
}
return null;
diff --git a/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java b/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
index 7e013e6..e8f2e33 100644
--- a/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
+++ b/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
@@ -151,13 +151,7 @@
return null;
}
- plugin.add(
- new RegistrationHandle() {
- @Override
- public void remove() {
- guiceFilter.destroy();
- }
- });
+ plugin.add(guiceFilter::destroy);
return guiceFilter;
}
return null;
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index dc2639b..913128e 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -296,7 +296,7 @@
RestCollection<RestResource, RestResource> rc = members.get();
globals
.permissionBackend
- .user(globals.currentUser.get())
+ .currentUser()
.checkAny(GlobalPermission.fromAnnotation(rc.getClass()));
viewData = new ViewData(null, null);
@@ -1182,7 +1182,7 @@
throws AuthException, PermissionBackendException {
globals
.permissionBackend
- .user(globals.currentUser.get())
+ .currentUser()
.checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
}
diff --git a/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java b/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
index c7a92a3..ee87397 100644
--- a/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
+++ b/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
@@ -21,7 +21,6 @@
import com.google.common.collect.Maps;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
-import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -67,12 +66,7 @@
}
void doPrune() {
- Iterator<Map.Entry<Object, ValueGauge>> i = cells.entrySet().iterator();
- while (i.hasNext()) {
- if (!i.next().getValue().set) {
- i.remove();
- }
- }
+ cells.entrySet().removeIf(objectValueGaugeEntry -> !objectValueGaugeEntry.getValue().set);
}
void doEndSet() {
diff --git a/java/com/google/gerrit/metrics/dropwizard/GetMetric.java b/java/com/google/gerrit/metrics/dropwizard/GetMetric.java
index f0ae97e..ae1e6ec 100644
--- a/java/com/google/gerrit/metrics/dropwizard/GetMetric.java
+++ b/java/com/google/gerrit/metrics/dropwizard/GetMetric.java
@@ -16,7 +16,6 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -25,23 +24,21 @@
class GetMetric implements RestReadView<MetricResource> {
private final PermissionBackend permissionBackend;
- private final CurrentUser user;
private final DropWizardMetricMaker metrics;
@Option(name = "--data-only", usage = "return only values")
boolean dataOnly;
@Inject
- GetMetric(PermissionBackend permissionBackend, CurrentUser user, DropWizardMetricMaker metrics) {
+ GetMetric(PermissionBackend permissionBackend, DropWizardMetricMaker metrics) {
this.permissionBackend = permissionBackend;
- this.user = user;
this.metrics = metrics;
}
@Override
public MetricJson apply(MetricResource resource)
throws AuthException, PermissionBackendException {
- permissionBackend.user(user).check(GlobalPermission.VIEW_CACHES);
+ permissionBackend.currentUser().check(GlobalPermission.VIEW_CACHES);
return new MetricJson(
resource.getMetric(), metrics.getAnnotations(resource.getName()), dataOnly);
}
diff --git a/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java b/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java
index 59f6b97..b028a16 100644
--- a/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java
+++ b/java/com/google/gerrit/metrics/dropwizard/ListMetrics.java
@@ -17,7 +17,6 @@
import com.codahale.metrics.Metric;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -32,7 +31,6 @@
class ListMetrics implements RestReadView<ConfigResource> {
private final PermissionBackend permissionBackend;
- private final CurrentUser user;
private final DropWizardMetricMaker metrics;
@Option(name = "--data-only", usage = "return only values")
@@ -47,17 +45,15 @@
List<String> query = new ArrayList<>();
@Inject
- ListMetrics(
- PermissionBackend permissionBackend, CurrentUser user, DropWizardMetricMaker metrics) {
+ ListMetrics(PermissionBackend permissionBackend, DropWizardMetricMaker metrics) {
this.permissionBackend = permissionBackend;
- this.user = user;
this.metrics = metrics;
}
@Override
public Map<String, MetricJson> apply(ConfigResource resource)
throws AuthException, PermissionBackendException {
- permissionBackend.user(user).check(GlobalPermission.VIEW_CACHES);
+ permissionBackend.currentUser().check(GlobalPermission.VIEW_CACHES);
SortedMap<String, MetricJson> out = new TreeMap<>();
List<String> prefixes = new ArrayList<>(query.size());
diff --git a/java/com/google/gerrit/metrics/proc/JGitMetricModule.java b/java/com/google/gerrit/metrics/proc/JGitMetricModule.java
index 4487ae5..438f70e 100644
--- a/java/com/google/gerrit/metrics/proc/JGitMetricModule.java
+++ b/java/com/google/gerrit/metrics/proc/JGitMetricModule.java
@@ -14,7 +14,6 @@
package com.google.gerrit.metrics.proc;
-import com.google.common.base.Supplier;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
@@ -29,22 +28,12 @@
new Description("Bytes of memory retained in JGit block cache.")
.setGauge()
.setUnit(Units.BYTES),
- new Supplier<Long>() {
- @Override
- public Long get() {
- return WindowCacheStats.getOpenBytes();
- }
- });
+ WindowCacheStats::getOpenBytes);
metrics.newCallbackMetric(
"jgit/block_cache/open_files",
Integer.class,
new Description("File handles held open by JGit block cache.").setGauge().setUnit("fds"),
- new Supplier<Integer>() {
- @Override
- public Integer get() {
- return WindowCacheStats.getOpenFiles();
- }
- });
+ WindowCacheStats::getOpenFiles);
}
}
diff --git a/java/com/google/gerrit/metrics/proc/ProcMetricModule.java b/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
index 8978e99..5b9b2ae 100644
--- a/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
+++ b/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
@@ -60,12 +60,7 @@
"proc/uptime",
Long.class,
new Description("Uptime of this process").setUnit(Units.MILLISECONDS),
- new Supplier<Long>() {
- @Override
- public Long get() {
- return ManagementFactory.getRuntimeMXBean().getUptime();
- }
- });
+ ManagementFactory.getRuntimeMXBean()::getUptime);
}
private void procCpuUsage(MetricMaker metrics) {
@@ -93,12 +88,7 @@
"proc/num_open_fds",
Long.class,
new Description("Number of open file descriptors").setGauge().setUnit("fds"),
- new Supplier<Long>() {
- @Override
- public Long get() {
- return provider.getOpenFileDescriptorCount();
- }
- });
+ provider::getOpenFileDescriptorCount);
}
}
diff --git a/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index fa3207f..96cf7be 100644
--- a/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -15,7 +15,6 @@
package com.google.gerrit.pgm.http.jetty;
import static com.google.gerrit.server.config.ConfigUtil.getTimeUnit;
-import static com.google.inject.Scopes.SINGLETON;
import static java.util.concurrent.TimeUnit.MINUTES;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
@@ -74,7 +73,7 @@
public static class Module extends ServletModule {
@Override
protected void configureServlets() {
- bind(QueueProvider.class).to(CommandExecutorQueueProvider.class).in(SINGLETON);
+ bind(QueueProvider.class).to(CommandExecutorQueueProvider.class);
filterRegex(FILTER_RE).through(ProjectQoSFilter.class);
}
}
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index a5b4f46..88e48aa 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -72,7 +72,6 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
@@ -202,12 +201,7 @@
}
List<String> names = pluginsDistribution.listPluginNames();
if (pluginsToInstall != null) {
- for (Iterator<String> i = names.iterator(); i.hasNext(); ) {
- String n = i.next();
- if (!pluginsToInstall.contains(n)) {
- i.remove();
- }
- }
+ names.removeIf(n -> !pluginsToInstall.contains(n));
}
return names;
} catch (FileNotFoundException e) {
diff --git a/java/com/google/gerrit/server/ApprovalsUtil.java b/java/com/google/gerrit/server/ApprovalsUtil.java
index 90ee38d..8ffe33d 100644
--- a/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -108,7 +108,6 @@
}
private final NotesMigration migration;
- private final IdentifiedUser.GenericFactory userFactory;
private final ApprovalCopier copier;
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
@@ -117,12 +116,10 @@
@Inject
public ApprovalsUtil(
NotesMigration migration,
- IdentifiedUser.GenericFactory userFactory,
ApprovalCopier copier,
PermissionBackend permissionBackend,
ProjectCache projectCache) {
this.migration = migration;
- this.userFactory = userFactory;
this.copier = copier;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
@@ -267,9 +264,12 @@
private boolean canSee(ReviewDb db, ChangeNotes notes, Account.Id accountId) {
try {
- IdentifiedUser user = userFactory.create(accountId);
return projectCache.checkedGet(notes.getProjectName()).statePermitsRead()
- && permissionBackend.user(user).change(notes).database(db).test(ChangePermission.READ);
+ && permissionBackend
+ .absentUser(accountId)
+ .change(notes)
+ .database(db)
+ .test(ChangePermission.READ);
} catch (IOException | PermissionBackendException e) {
log.warn(
String.format(
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 70b3d68..ab2c26b 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -89,6 +89,7 @@
"//lib/ow2:ow2-asm-tree",
"//lib/ow2:ow2-asm-util",
"//lib/prolog:runtime",
+ "//proto:cache_java_proto",
],
)
diff --git a/java/com/google/gerrit/server/PatchSetUtil.java b/java/com/google/gerrit/server/PatchSetUtil.java
index 82ca8d2..252eb61 100644
--- a/java/com/google/gerrit/server/PatchSetUtil.java
+++ b/java/com/google/gerrit/server/PatchSetUtil.java
@@ -26,15 +26,24 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.sql.Timestamp;
@@ -42,16 +51,31 @@
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
/** Utilities for manipulating patch sets. */
@Singleton
public class PatchSetUtil {
private final NotesMigration migration;
+ private final Provider<ApprovalsUtil> approvalsUtilProvider;
+ private final ProjectCache projectCache;
+ private final Provider<ReviewDb> dbProvider;
+ private final GitRepositoryManager repoManager;
@Inject
- PatchSetUtil(NotesMigration migration) {
+ PatchSetUtil(
+ NotesMigration migration,
+ Provider<ApprovalsUtil> approvalsUtilProvider,
+ ProjectCache projectCache,
+ Provider<ReviewDb> dbProvider,
+ GitRepositoryManager repoManager) {
this.migration = migration;
+ this.approvalsUtilProvider = approvalsUtilProvider;
+ this.projectCache = projectCache;
+ this.dbProvider = dbProvider;
+ this.repoManager = repoManager;
}
public PatchSet current(ReviewDb db, ChangeNotes notes) throws OrmException {
@@ -155,4 +179,49 @@
update.setGroups(groups);
db.patchSets().update(Collections.singleton(ps));
}
+
+ /** Check if the current patch set of the change is locked. */
+ public void checkPatchSetNotLocked(ChangeNotes notes, CurrentUser user)
+ throws OrmException, IOException, ResourceConflictException {
+ if (isPatchSetLocked(notes, user)) {
+ throw new ResourceConflictException(
+ String.format("The current patch set of change %s is locked", notes.getChangeId()));
+ }
+ }
+
+ /** Is the current patch set locked against state changes? */
+ public boolean isPatchSetLocked(ChangeNotes notes, CurrentUser user)
+ throws OrmException, IOException {
+ Change change = notes.getChange();
+ if (change.getStatus() == Change.Status.MERGED) {
+ return false;
+ }
+
+ ProjectState projectState = projectCache.checkedGet(notes.getProjectName());
+ checkNotNull(projectState, "Failed to load project %s", notes.getProjectName());
+
+ ApprovalsUtil approvalsUtil = approvalsUtilProvider.get();
+ for (PatchSetApproval ap :
+ approvalsUtil.byPatchSet(
+ dbProvider.get(), notes, user, change.currentPatchSetId(), null, null)) {
+ LabelType type = projectState.getLabelTypes(notes, user).byLabel(ap.getLabel());
+ if (type != null
+ && ap.getValue() == 1
+ && type.getFunction() == LabelFunction.PATCH_SET_LOCK) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Returns the full commit message for the given project at the given patchset revision */
+ public String getFullCommitMessage(Project.NameKey project, PatchSet patchSet)
+ throws IOException {
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk rw = new RevWalk(repo)) {
+ RevCommit src = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
+ rw.parseBody(src);
+ return src.getFullMessage();
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/account/AccountLoader.java b/java/com/google/gerrit/server/account/AccountLoader.java
index a137256b..7be51a0 100644
--- a/java/com/google/gerrit/server/account/AccountLoader.java
+++ b/java/com/google/gerrit/server/account/AccountLoader.java
@@ -69,7 +69,7 @@
provided = new ArrayList<>();
}
- public AccountInfo get(Account.Id id) {
+ public synchronized AccountInfo get(Account.Id id) {
if (id == null) {
return null;
}
@@ -81,7 +81,7 @@
return info;
}
- public void put(AccountInfo info) {
+ public synchronized void put(AccountInfo info) {
checkArgument(info._accountId != null, "_accountId field required");
provided.add(info);
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index b9b2abb..c375067 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -17,21 +17,13 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.MultimapBuilder.SetMultimapBuilder;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.gerrit.common.Nullable;
@@ -50,11 +42,8 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.BlobBasedConfig;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -228,8 +217,7 @@
private List<CacheUpdate> cacheUpdates = new ArrayList<>();
private Runnable afterReadRevision;
- private boolean disableCheckForNewDuplicateEmails;
- private boolean readOnly;
+ private boolean readOnly = false;
private ExternalIdNotes(
ExternalIdCache externalIdCache,
@@ -247,12 +235,6 @@
return this;
}
- public ExternalIdNotes setDisableCheckForNewDuplicateEmails(
- boolean disableCheckForNewDuplicateEmails) {
- this.disableCheckForNewDuplicateEmails = disableCheckForNewDuplicateEmails;
- return this;
- }
-
private ExternalIdNotes setReadOnly() {
this.readOnly = true;
return this;
@@ -680,8 +662,6 @@
try (RevWalk rw = new RevWalk(reader)) {
Set<String> footers = new HashSet<>();
- Multimap<String, ExternalId.Key> extIdsByEmailBefore =
- !disableCheckForNewDuplicateEmails ? getExternalIdsByEmail(rw, noteMap) : null;
for (NoteMapUpdate noteMapUpdate : noteMapUpdates) {
try {
noteMapUpdate.execute(rw, noteMap, footers);
@@ -689,10 +669,6 @@
throw new IOException(e);
}
}
- if (!disableCheckForNewDuplicateEmails) {
- Multimap<String, ExternalId.Key> extIdsByEmailAfter = getExternalIdsByEmail(rw, noteMap);
- checkForNewDuplicateEmails(extIdsByEmailBefore, extIdsByEmailAfter);
- }
noteMapUpdates.clear();
if (!footers.isEmpty()) {
commit.setMessage(
@@ -840,114 +816,6 @@
return extId;
}
- private static Multimap<String, ExternalId.Key> getExternalIdsByEmail(RevWalk rw, NoteMap noteMap)
- throws IOException {
- ListMultimap<String, ExternalId.Key> extIdsByEmail =
- MultimapBuilder.hashKeys().arrayListValues().build();
- for (Note note : noteMap) {
- byte[] raw = readNoteData(rw, note.getData());
- try {
- ExternalId extId = ExternalId.parse(note.getName(), raw, note.getData());
- if (extId.email() != null) {
- extIdsByEmail.put(extId.email(), extId.key());
- }
- } catch (ConfigInvalidException e) {
- continue;
- }
- }
- return extIdsByEmail;
- }
-
- /**
- * Checks that the external ID updates didn't result in new duplicate emails.
- *
- * <p>Duplicate emails that existed before the external IDs updates are ignored.
- *
- * @param extIdsByEmailBefore external IDs by email before the external ID updates have been
- * performed
- * @param extIdsByEmailAfter external IDs by email after the external ID updates have been
- * performed
- * @throws ConfigInvalidException if new duplicate emails are found
- */
- private static void checkForNewDuplicateEmails(
- Multimap<String, ExternalId.Key> extIdsByEmailBefore,
- Multimap<String, ExternalId.Key> extIdsByEmailAfter)
- throws ConfigInvalidException {
- List<String> problems = new ArrayList<>();
-
- // Find all emails that are assigned to multiple external IDs after the external ID updates have
- // been performed.
- SetMultimap<String, ExternalId.Key> duplicateEmails =
- extIdsByEmailAfter
- .asMap()
- .entrySet()
- .stream()
- .filter(e -> e.getValue().size() > 1)
- .collect(
- Multimaps.flatteningToMultimap(
- Map.Entry::getKey,
- v -> v.getValue().stream(),
- SetMultimapBuilder.hashKeys().hashSetValues()::build));
-
- for (Map.Entry<String, Collection<ExternalId.Key>> e : duplicateEmails.asMap().entrySet()) {
- String email = e.getKey();
-
- // External IDs that have this email assigned after the external ID updates have been
- // performed.
- SortedSet<ExternalId.Key> extIdsAfter = new TreeSet<>(comparing(a -> a.get()));
- extIdsAfter.addAll(e.getValue());
-
- // External IDs that already had this email assigned before the external ID updates had been
- // performed.
- SortedSet<ExternalId.Key> extIdsBefore = new TreeSet<>(comparing(a -> a.get()));
- extIdsBefore.addAll((extIdsByEmailBefore.get(email)));
-
- if (extIdsBefore.isEmpty()) {
- // No external ID had this email before. This means the updates try to assign it to multiple
- // external IDs now.
- problems.add(
- String.format(
- "cannot assign email %s to multiple external IDs: %s", email, extIdsAfter));
- continue;
- }
-
- // External IDs that get this email newly assigned.
- Set<ExternalId.Key> extIdsNew = Sets.difference(extIdsAfter, extIdsBefore);
- if (extIdsNew.isEmpty()) {
- // Ignore, this inconsistency already existed before the external ID updates had been
- // performed.
- continue;
- }
-
- // External IDs that had this email assigned before the external ID updates had been
- // performed and that still have this email.
- Set<ExternalId.Key> extIdsOld = Sets.intersection(extIdsBefore, extIdsAfter);
- if (extIdsOld.isEmpty()) {
- // None of the external IDs that had this email before the external ID updates had been
- // performed still has this email.
- // This means all external IDs that have this email now have it newly assigned.
- problems.add(
- String.format(
- "cannot assign email %s to multiple external IDs: %s", email, extIdsAfter));
- continue;
- }
-
- // Some external IDs had this email before and the updates try to assign it to additional
- // external IDs.
- problems.add(
- String.format(
- "cannot assign email %s to external ID(s) %s,"
- + " it is already assigned to external ID(s) %s",
- email, extIdsNew, extIdsOld));
- }
-
- if (!problems.isEmpty()) {
- Collections.sort(problems);
- throw new ConfigInvalidException(
- "Ambiguous emails:\n - " + Joiner.on(",\n - ").join(problems));
- }
- }
-
private static void addFooters(Set<String> footers, ExternalId extId) {
footers.add("Account: " + extId.accountId().get());
if (extId.email() != null) {
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 501b3a4..46d9180 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -48,7 +48,6 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ProjectJson;
@@ -88,7 +87,6 @@
ProjectApiImpl create(String name);
}
- private final CurrentUser user;
private final PermissionBackend permissionBackend;
private final CreateProject.Factory createProjectFactory;
private final ProjectApiImpl.Factory projectApi;
@@ -123,7 +121,6 @@
@AssistedInject
ProjectApiImpl(
- CurrentUser user,
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
@@ -155,7 +152,6 @@
SetParent setParent,
@Assisted ProjectResource project) {
this(
- user,
permissionBackend,
createProjectFactory,
projectApi,
@@ -191,7 +187,6 @@
@AssistedInject
ProjectApiImpl(
- CurrentUser user,
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
@@ -223,7 +218,6 @@
SetParent setParent,
@Assisted String name) {
this(
- user,
permissionBackend,
createProjectFactory,
projectApi,
@@ -258,7 +252,6 @@
}
private ProjectApiImpl(
- CurrentUser user,
PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
@@ -290,7 +283,6 @@
GetParent getParent,
SetParent setParent,
String name) {
- this.user = user;
this.permissionBackend = permissionBackend;
this.createProjectFactory = createProjectFactory;
this.projectApi = projectApi;
@@ -339,7 +331,7 @@
throw new BadRequestException("name must match input.name");
}
CreateProject impl = createProjectFactory.create(name);
- permissionBackend.user(user).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
+ permissionBackend.currentUser().checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
impl.apply(TopLevelResource.INSTANCE, in);
return projectApi.create(projects.parse(name));
} catch (Exception e) {
diff --git a/java/com/google/gerrit/server/auth/ldap/Helper.java b/java/com/google/gerrit/server/auth/ldap/Helper.java
index 795686c..5af730f 100644
--- a/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -406,9 +406,7 @@
throw new IllegalArgumentException("No variables in ldap.groupMemberPattern");
}
- for (String name : groupMemberQuery.getParameters()) {
- accountAtts.add(name);
- }
+ accountAtts.addAll(groupMemberQuery.getParameters());
groupMemberQueryList.add(groupMemberQuery);
}
diff --git a/java/com/google/gerrit/server/cache/CacheBinding.java b/java/com/google/gerrit/server/cache/CacheBinding.java
index abb0f32..1c0d7ef 100644
--- a/java/com/google/gerrit/server/cache/CacheBinding.java
+++ b/java/com/google/gerrit/server/cache/CacheBinding.java
@@ -16,8 +16,6 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
-import com.google.gerrit.common.Nullable;
-import com.google.inject.TypeLiteral;
import java.util.concurrent.TimeUnit;
/** Configure a cache declared within a {@link CacheModule} instance. */
@@ -25,9 +23,6 @@
/** Set the total size of the cache. */
CacheBinding<K, V> maximumWeight(long weight);
- /** Set the total on-disk limit of the cache */
- CacheBinding<K, V> diskLimit(long limit);
-
/** Set the time an element lives before being expired. */
CacheBinding<K, V> expireAfterWrite(long duration, TimeUnit durationUnits);
@@ -37,22 +32,10 @@
/** Algorithm to weigh an object with a method other than the unit weight 1. */
CacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz);
- String name();
-
- TypeLiteral<K> keyType();
-
- TypeLiteral<V> valueType();
-
- long maximumWeight();
-
- long diskLimit();
-
- @Nullable
- Long expireAfterWrite(TimeUnit unit);
-
- @Nullable
- Weigher<K, V> weigher();
-
- @Nullable
- CacheLoader<K, V> loader();
+ /**
+ * Set the config name to something other than the cache name.
+ *
+ * @see CacheDef#configKey()
+ */
+ CacheBinding<K, V> configKey(String configKey);
}
diff --git a/java/com/google/gerrit/server/cache/CacheDef.java b/java/com/google/gerrit/server/cache/CacheDef.java
new file mode 100644
index 0000000..adb7585
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/CacheDef.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2018 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.server.cache;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.Weigher;
+import com.google.gerrit.common.Nullable;
+import com.google.inject.TypeLiteral;
+import java.util.concurrent.TimeUnit;
+
+public interface CacheDef<K, V> {
+ /**
+ * Unique name for this cache.
+ *
+ * <p>The name can be used in a binding annotation {@code @Named(name)} to inject the cache
+ * configured with this binding.
+ */
+ String name();
+
+ /**
+ * Key to use when looking up configuration for this cache.
+ *
+ * <p>Typically, this will match the result of {@link #name()}, so that configuration is keyed by
+ * the actual cache name. However, it may be changed, for example to reuse the size limits of some
+ * other cache.
+ */
+ String configKey();
+
+ TypeLiteral<K> keyType();
+
+ TypeLiteral<V> valueType();
+
+ long maximumWeight();
+
+ @Nullable
+ Long expireAfterWrite(TimeUnit unit);
+
+ @Nullable
+ Weigher<K, V> weigher();
+
+ @Nullable
+ CacheLoader<K, V> loader();
+}
diff --git a/java/com/google/gerrit/server/cache/CacheModule.java b/java/com/google/gerrit/server/cache/CacheModule.java
index 0e0c16f..ca399e7 100644
--- a/java/com/google/gerrit/server/cache/CacheModule.java
+++ b/java/com/google/gerrit/server/cache/CacheModule.java
@@ -24,9 +24,9 @@
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.util.Types;
-import java.io.Serializable;
import java.lang.reflect.Type;
/** Miniature DSL to support binding {@link Cache} instances in Guice. */
@@ -67,15 +67,9 @@
*/
protected <K, V> CacheBinding<K, V> cache(
String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
- Type type = Types.newParameterizedType(Cache.class, keyType.getType(), valType.getType());
-
- @SuppressWarnings("unchecked")
- Key<Cache<K, V>> key = (Key<Cache<K, V>>) Key.get(type, Names.named(name));
-
CacheProvider<K, V> m = new CacheProvider<>(this, name, keyType, valType);
- bind(key).toProvider(m).asEagerSingleton();
- bind(ANY_CACHE).annotatedWith(Exports.named(name)).to(key);
- return m.maximumWeight(1024);
+ bindCache(m, name, keyType, valType);
+ return m;
}
<K, V> Provider<CacheLoader<K, V>> bindCacheLoader(
@@ -126,7 +120,7 @@
* @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
- protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
+ protected <K, V> PersistentCacheBinding<K, V> persist(
String name, Class<K> keyType, Class<V> valType) {
return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType));
}
@@ -138,7 +132,7 @@
* @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
- protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
+ protected <K, V> PersistentCacheBinding<K, V> persist(
String name, Class<K> keyType, TypeLiteral<V> valType) {
return persist(name, TypeLiteral.get(keyType), valType);
}
@@ -150,8 +144,40 @@
* @param <V> type of value stored by the cache.
* @return binding to describe the cache.
*/
- protected <K extends Serializable, V extends Serializable> CacheBinding<K, V> persist(
+ protected <K, V> PersistentCacheBinding<K, V> persist(
String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
- return ((CacheProvider<K, V>) cache(name, keyType, valType)).persist(true);
+ PersistentCacheProvider<K, V> m = new PersistentCacheProvider<>(this, name, keyType, valType);
+ bindCache(m, name, keyType, valType);
+
+ Type cacheDefType =
+ Types.newParameterizedType(PersistentCacheDef.class, keyType.getType(), valType.getType());
+ @SuppressWarnings("unchecked")
+ Key<PersistentCacheDef<K, V>> cacheDefKey =
+ (Key<PersistentCacheDef<K, V>>) Key.get(cacheDefType, Names.named(name));
+ bind(cacheDefKey).toInstance(m);
+
+ // TODO(dborowitz): Once default Java serialization is removed, leave no default.
+ return m.version(0)
+ .keySerializer(new JavaCacheSerializer<>())
+ .valueSerializer(new JavaCacheSerializer<>());
+ }
+
+ private <K, V> void bindCache(
+ CacheProvider<K, V> m, String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
+ Type type = Types.newParameterizedType(Cache.class, keyType.getType(), valType.getType());
+ Named named = Names.named(name);
+
+ @SuppressWarnings("unchecked")
+ Key<Cache<K, V>> key = (Key<Cache<K, V>>) Key.get(type, named);
+ bind(key).toProvider(m).asEagerSingleton();
+ bind(ANY_CACHE).annotatedWith(Exports.named(name)).to(key);
+
+ Type cacheDefType =
+ Types.newParameterizedType(CacheDef.class, keyType.getType(), valType.getType());
+ @SuppressWarnings("unchecked")
+ Key<CacheDef<K, V>> cacheDefKey = (Key<CacheDef<K, V>>) Key.get(cacheDefType, named);
+ bind(cacheDefKey).toInstance(m);
+
+ m.maximumWeight(1024);
}
}
diff --git a/java/com/google/gerrit/server/cache/CacheProvider.java b/java/com/google/gerrit/server/cache/CacheProvider.java
index 86df104..ce94bfa 100644
--- a/java/com/google/gerrit/server/cache/CacheProvider.java
+++ b/java/com/google/gerrit/server/cache/CacheProvider.java
@@ -14,9 +14,10 @@
package com.google.gerrit.server.cache;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static java.util.concurrent.TimeUnit.SECONDS;
-import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheLoader;
@@ -28,21 +29,19 @@
import com.google.inject.TypeLiteral;
import java.util.concurrent.TimeUnit;
-class CacheProvider<K, V> implements Provider<Cache<K, V>>, CacheBinding<K, V> {
+class CacheProvider<K, V> implements Provider<Cache<K, V>>, CacheBinding<K, V>, CacheDef<K, V> {
private final CacheModule module;
final String name;
private final TypeLiteral<K> keyType;
private final TypeLiteral<V> valType;
- private boolean persist;
+ private String configKey;
private long maximumWeight;
- private long diskLimit;
private Long expireAfterWrite;
private Provider<CacheLoader<K, V>> loader;
private Provider<Weigher<K, V>> weigher;
private String plugin;
private MemoryCacheFactory memoryCacheFactory;
- private PersistentCacheFactory persistentCacheFactory;
private boolean frozen;
CacheProvider(CacheModule module, String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
@@ -62,54 +61,42 @@
this.memoryCacheFactory = factory;
}
- @Inject(optional = true)
- void setPersistentCacheFactory(@Nullable PersistentCacheFactory factory) {
- this.persistentCacheFactory = factory;
- }
-
- CacheBinding<K, V> persist(boolean p) {
- Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
- persist = p;
- return this;
- }
-
@Override
public CacheBinding<K, V> maximumWeight(long weight) {
- Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ checkNotFrozen();
maximumWeight = weight;
return this;
}
@Override
- public CacheBinding<K, V> diskLimit(long limit) {
- Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
- Preconditions.checkState(persist, "diskLimit supported for persistent caches only");
- diskLimit = limit;
- return this;
- }
-
- @Override
public CacheBinding<K, V> expireAfterWrite(long duration, TimeUnit unit) {
- Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ checkNotFrozen();
expireAfterWrite = SECONDS.convert(duration, unit);
return this;
}
@Override
public CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> impl) {
- Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ checkNotFrozen();
loader = module.bindCacheLoader(this, impl);
return this;
}
@Override
public CacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> impl) {
- Preconditions.checkState(!frozen, "binding frozen, cannot be modified");
+ checkNotFrozen();
weigher = module.bindWeigher(this, impl);
return this;
}
@Override
+ public CacheBinding<K, V> configKey(String name) {
+ checkNotFrozen();
+ configKey = checkNotNull(name);
+ return this;
+ }
+
+ @Override
public String name() {
if (!Strings.isNullOrEmpty(plugin)) {
return plugin + "." + name;
@@ -118,6 +105,11 @@
}
@Override
+ public String configKey() {
+ return configKey != null ? configKey : name();
+ }
+
+ @Override
public TypeLiteral<K> keyType() {
return keyType;
}
@@ -133,14 +125,6 @@
}
@Override
- public long diskLimit() {
- if (diskLimit > 0) {
- return diskLimit;
- }
- return 128 << 20;
- }
-
- @Override
@Nullable
public Long expireAfterWrite(TimeUnit unit) {
return expireAfterWrite != null ? unit.convert(expireAfterWrite, SECONDS) : null;
@@ -160,18 +144,16 @@
@Override
public Cache<K, V> get() {
- frozen = true;
+ freeze();
+ CacheLoader<K, V> ldr = loader();
+ return ldr != null ? memoryCacheFactory.build(this, ldr) : memoryCacheFactory.build(this);
+ }
- if (loader != null) {
- CacheLoader<K, V> ldr = loader.get();
- if (persist && persistentCacheFactory != null) {
- return persistentCacheFactory.build(this, ldr);
- }
- return memoryCacheFactory.build(this, ldr);
- } else if (persist && persistentCacheFactory != null) {
- return persistentCacheFactory.build(this);
- } else {
- return memoryCacheFactory.build(this);
- }
+ protected void checkNotFrozen() {
+ checkState(!frozen, "binding frozen, cannot be modified");
+ }
+
+ protected void freeze() {
+ frozen = true;
}
}
diff --git a/java/com/google/gerrit/server/cache/CacheSerializer.java b/java/com/google/gerrit/server/cache/CacheSerializer.java
new file mode 100644
index 0000000..6bd1322
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/CacheSerializer.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 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.server.cache;
+
+import java.io.IOException;
+
+/** Interface for serializing/deserializing a type to/from a persistent cache. */
+public interface CacheSerializer<T> {
+ /** Serializes the object to a new byte array. */
+ byte[] serialize(T object) throws IOException;
+
+ /** Deserializes a single object from the given byte array. */
+ T deserialize(byte[] in) throws IOException;
+}
diff --git a/java/com/google/gerrit/server/cache/EnumCacheSerializer.java b/java/com/google/gerrit/server/cache/EnumCacheSerializer.java
new file mode 100644
index 0000000..6ea61215
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/EnumCacheSerializer.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 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.server.cache;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Enums;
+import java.io.IOException;
+
+public class EnumCacheSerializer<E extends Enum<E>> implements CacheSerializer<E> {
+ private final Class<E> clazz;
+
+ public EnumCacheSerializer(Class<E> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public byte[] serialize(E object) throws IOException {
+ return object.name().getBytes(UTF_8);
+ }
+
+ @Override
+ public E deserialize(byte[] in) throws IOException {
+ String name = new String(in, UTF_8);
+ return Enums.getIfPresent(clazz, name)
+ .toJavaUtil()
+ .orElseThrow(() -> new IOException("Invalid " + clazz.getName() + " value: " + name));
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/JavaCacheSerializer.java b/java/com/google/gerrit/server/cache/JavaCacheSerializer.java
new file mode 100644
index 0000000..750c5df
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/JavaCacheSerializer.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2018 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.server.cache;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Serializer that uses default Java serialization.
+ *
+ * @param <T> type to serialize. Must implement {@code Serializable}, but due to implementation
+ * details this is only checked at runtime.
+ */
+public class JavaCacheSerializer<T> implements CacheSerializer<T> {
+ @Override
+ public byte[] serialize(T object) throws IOException {
+ try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ObjectOutputStream oout = new ObjectOutputStream(bout)) {
+ oout.writeObject(object);
+ oout.flush();
+ return bout.toByteArray();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T deserialize(byte[] in) throws IOException {
+ Object object;
+ try (ByteArrayInputStream bin = new ByteArrayInputStream(in);
+ ObjectInputStream oin = new ObjectInputStream(bin)) {
+ object = oin.readObject();
+ } catch (ClassNotFoundException e) {
+ throw new IOException("Failed to deserialize object of type", e);
+ }
+ return (T) object;
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/MemoryCacheFactory.java b/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
index 49fcd5b..fc55753 100644
--- a/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
@@ -19,7 +19,7 @@
import com.google.common.cache.LoadingCache;
public interface MemoryCacheFactory {
- <K, V> Cache<K, V> build(CacheBinding<K, V> def);
+ <K, V> Cache<K, V> build(CacheDef<K, V> def);
- <K, V> LoadingCache<K, V> build(CacheBinding<K, V> def, CacheLoader<K, V> loader);
+ <K, V> LoadingCache<K, V> build(CacheDef<K, V> def, CacheLoader<K, V> loader);
}
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheBinding.java b/java/com/google/gerrit/server/cache/PersistentCacheBinding.java
new file mode 100644
index 0000000..429f5ab
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/PersistentCacheBinding.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2018 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.server.cache;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.Weigher;
+import java.util.concurrent.TimeUnit;
+
+/** Configure a persistent cache declared within a {@link CacheModule} instance. */
+public interface PersistentCacheBinding<K, V> extends CacheBinding<K, V> {
+ @Override
+ PersistentCacheBinding<K, V> maximumWeight(long weight);
+
+ @Override
+ PersistentCacheBinding<K, V> expireAfterWrite(long duration, TimeUnit durationUnits);
+
+ @Override
+ PersistentCacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz);
+
+ @Override
+ PersistentCacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz);
+
+ PersistentCacheBinding<K, V> version(int version);
+
+ /** Set the total on-disk limit of the cache */
+ PersistentCacheBinding<K, V> diskLimit(long limit);
+
+ PersistentCacheBinding<K, V> keySerializer(CacheSerializer<K> keySerializer);
+
+ PersistentCacheBinding<K, V> valueSerializer(CacheSerializer<V> valueSerializer);
+}
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheDef.java b/java/com/google/gerrit/server/cache/PersistentCacheDef.java
new file mode 100644
index 0000000..9bd120f
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/PersistentCacheDef.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 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.server.cache;
+
+public interface PersistentCacheDef<K, V> extends CacheDef<K, V> {
+ long diskLimit();
+
+ int version();
+
+ CacheSerializer<K> keySerializer();
+
+ CacheSerializer<V> valueSerializer();
+}
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheFactory.java b/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
index d134adf..27fa9ca 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
@@ -19,9 +19,9 @@
import com.google.common.cache.LoadingCache;
public interface PersistentCacheFactory {
- <K, V> Cache<K, V> build(CacheBinding<K, V> def);
+ <K, V> Cache<K, V> build(PersistentCacheDef<K, V> def);
- <K, V> LoadingCache<K, V> build(CacheBinding<K, V> def, CacheLoader<K, V> loader);
+ <K, V> LoadingCache<K, V> build(PersistentCacheDef<K, V> def, CacheLoader<K, V> loader);
void onStop(String plugin);
}
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheProvider.java b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
new file mode 100644
index 0000000..405de4f
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2018 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.server.cache;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.Weigher;
+import com.google.gerrit.common.Nullable;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+class PersistentCacheProvider<K, V> extends CacheProvider<K, V>
+ implements Provider<Cache<K, V>>, PersistentCacheBinding<K, V>, PersistentCacheDef<K, V> {
+ private int version;
+ private long diskLimit;
+ private CacheSerializer<K> keySerializer;
+ private CacheSerializer<V> valueSerializer;
+
+ private PersistentCacheFactory persistentCacheFactory;
+
+ PersistentCacheProvider(
+ CacheModule module, String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
+ super(module, name, keyType, valType);
+ version = -1;
+ }
+
+ @Inject(optional = true)
+ void setPersistentCacheFactory(@Nullable PersistentCacheFactory factory) {
+ this.persistentCacheFactory = factory;
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> maximumWeight(long weight) {
+ return (PersistentCacheBinding<K, V>) super.maximumWeight(weight);
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> expireAfterWrite(long duration, TimeUnit durationUnits) {
+ return (PersistentCacheBinding<K, V>) super.expireAfterWrite(duration, durationUnits);
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz) {
+ return (PersistentCacheBinding<K, V>) super.loader(clazz);
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz) {
+ return (PersistentCacheBinding<K, V>) super.weigher(clazz);
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> version(int version) {
+ this.version = version;
+ return this;
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> keySerializer(CacheSerializer<K> keySerializer) {
+ this.keySerializer = keySerializer;
+ return this;
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> valueSerializer(CacheSerializer<V> valueSerializer) {
+ this.valueSerializer = valueSerializer;
+ return this;
+ }
+
+ @Override
+ public PersistentCacheBinding<K, V> diskLimit(long limit) {
+ checkNotFrozen();
+ diskLimit = limit;
+ return this;
+ }
+
+ @Override
+ public long diskLimit() {
+ if (diskLimit > 0) {
+ return diskLimit;
+ }
+ return 128 << 20;
+ }
+
+ @Override
+ public int version() {
+ return version;
+ }
+
+ @Override
+ public CacheSerializer<K> keySerializer() {
+ return keySerializer;
+ }
+
+ @Override
+ public CacheSerializer<V> valueSerializer() {
+ return valueSerializer;
+ }
+
+ @Override
+ public Cache<K, V> get() {
+ if (persistentCacheFactory == null) {
+ return super.get();
+ }
+ checkState(version >= 0, "version is required");
+ checkSerializer(keyType(), keySerializer, "key");
+ checkSerializer(valueType(), valueSerializer, "value");
+ freeze();
+ CacheLoader<K, V> ldr = loader();
+ return ldr != null
+ ? persistentCacheFactory.build(this, ldr)
+ : persistentCacheFactory.build(this);
+ }
+
+ private static <T> void checkSerializer(
+ TypeLiteral<T> type, CacheSerializer<T> serializer, String name) {
+ checkState(serializer != null, "%sSerializer is required", name);
+ if (serializer instanceof JavaCacheSerializer) {
+ checkState(
+ Serializable.class.isAssignableFrom(type.getRawType()),
+ "%s type %s must implement Serializable",
+ name,
+ type);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/ProtoCacheSerializers.java b/java/com/google/gerrit/server/cache/ProtoCacheSerializers.java
new file mode 100644
index 0000000..795df72
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/ProtoCacheSerializers.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2018 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.server.cache;
+
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.MessageLite;
+import java.io.IOException;
+
+/** Static utilities for writing protobuf-based {@link CacheSerializer} implementations. */
+public class ProtoCacheSerializers {
+ /**
+ * Serializes a proto to a byte array.
+ *
+ * <p>Guarantees deterministic serialization and thus is suitable for use as a persistent cache
+ * key. Should be used in preference to {@link MessageLite#toByteArray()}, which is not guaranteed
+ * deterministic.
+ *
+ * @param message the proto message to serialize.
+ * @return a byte array with the message contents.
+ */
+ public static byte[] toByteArray(MessageLite message) {
+ byte[] bytes = new byte[message.getSerializedSize()];
+ CodedOutputStream cout = CodedOutputStream.newInstance(bytes);
+ cout.useDeterministicSerialization();
+ try {
+ message.writeTo(cout);
+ cout.checkNoSpaceLeft();
+ return bytes;
+ } catch (IOException e) {
+ throw new IllegalStateException("exception writing to byte array");
+ }
+ }
+
+ private ProtoCacheSerializers() {}
+}
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheBindingProxy.java b/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
similarity index 68%
rename from java/com/google/gerrit/server/cache/h2/H2CacheBindingProxy.java
rename to java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
index 0d1cf20..7b3da31 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheBindingProxy.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheDefProxy.java
@@ -16,18 +16,16 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
-import com.google.gerrit.server.cache.CacheBinding;
+import com.google.gerrit.server.cache.CacheSerializer;
+import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
import com.google.inject.TypeLiteral;
import java.util.concurrent.TimeUnit;
-class H2CacheBindingProxy<K, V> implements CacheBinding<K, V> {
- private static final String MSG_NOT_SUPPORTED =
- "This is read-only wrapper. Modifications are not supported";
+class H2CacheDefProxy<K, V> implements PersistentCacheDef<K, V> {
+ private final PersistentCacheDef<K, V> source;
- private final CacheBinding<K, V> source;
-
- H2CacheBindingProxy(CacheBinding<K, V> source) {
+ H2CacheDefProxy(PersistentCacheDef<K, V> source) {
this.source = source;
}
@@ -61,6 +59,11 @@
}
@Override
+ public String configKey() {
+ return source.configKey();
+ }
+
+ @Override
public TypeLiteral<K> keyType() {
return source.keyType();
}
@@ -86,27 +89,17 @@
}
@Override
- public CacheBinding<K, V> maximumWeight(long weight) {
- throw new RuntimeException(MSG_NOT_SUPPORTED);
+ public int version() {
+ return source.version();
}
@Override
- public CacheBinding<K, V> diskLimit(long limit) {
- throw new RuntimeException(MSG_NOT_SUPPORTED);
+ public CacheSerializer<K> keySerializer() {
+ return source.keySerializer();
}
@Override
- public CacheBinding<K, V> expireAfterWrite(long duration, TimeUnit durationUnits) {
- throw new RuntimeException(MSG_NOT_SUPPORTED);
- }
-
- @Override
- public CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz) {
- throw new RuntimeException(MSG_NOT_SUPPORTED);
- }
-
- @Override
- public CacheBinding<K, V> weigher(Class<? extends Weigher<K, V>> clazz) {
- throw new RuntimeException(MSG_NOT_SUPPORTED);
+ public CacheSerializer<V> valueSerializer() {
+ return source.valueSerializer();
}
}
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 2240c7d..451540d 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -20,8 +20,8 @@
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.server.cache.CacheBinding;
import com.google.gerrit.server.cache.MemoryCacheFactory;
+import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.PersistentCacheFactory;
import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore;
import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
@@ -30,7 +30,6 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -154,16 +153,15 @@
@SuppressWarnings({"unchecked"})
@Override
- public <K, V> Cache<K, V> build(CacheBinding<K, V> in) {
- long limit = config.getLong("cache", in.name(), "diskLimit", in.diskLimit());
+ public <K, V> Cache<K, V> build(PersistentCacheDef<K, V> in) {
+ long limit = config.getLong("cache", in.configKey(), "diskLimit", in.diskLimit());
if (cacheDir == null || limit <= 0) {
return memCacheFactory.build(in);
}
- H2CacheBindingProxy<K, V> def = new H2CacheBindingProxy<>(in);
- SqlStore<K, V> store =
- newSqlStore(def.name(), def.keyType(), limit, def.expireAfterWrite(TimeUnit.SECONDS));
+ H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
+ SqlStore<K, V> store = newSqlStore(def, limit);
H2CacheImpl<K, V> cache =
new H2CacheImpl<>(
executor, store, def.keyType(), (Cache<K, ValueHolder<V>>) memCacheFactory.build(def));
@@ -175,16 +173,15 @@
@SuppressWarnings("unchecked")
@Override
- public <K, V> LoadingCache<K, V> build(CacheBinding<K, V> in, CacheLoader<K, V> loader) {
- long limit = config.getLong("cache", in.name(), "diskLimit", in.diskLimit());
+ public <K, V> LoadingCache<K, V> build(PersistentCacheDef<K, V> in, CacheLoader<K, V> loader) {
+ long limit = config.getLong("cache", in.configKey(), "diskLimit", in.diskLimit());
if (cacheDir == null || limit <= 0) {
return memCacheFactory.build(in, loader);
}
- H2CacheBindingProxy<K, V> def = new H2CacheBindingProxy<>(in);
- SqlStore<K, V> store =
- newSqlStore(def.name(), def.keyType(), limit, def.expireAfterWrite(TimeUnit.SECONDS));
+ H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
+ SqlStore<K, V> store = newSqlStore(def, limit);
Cache<K, ValueHolder<V>> mem =
(Cache<K, ValueHolder<V>>)
memCacheFactory.build(
@@ -208,10 +205,9 @@
}
}
- private <V, K> SqlStore<K, V> newSqlStore(
- String name, TypeLiteral<K> keyType, long maxSize, Long expireAfterWrite) {
+ private <V, K> SqlStore<K, V> newSqlStore(PersistentCacheDef<K, V> def, long maxSize) {
StringBuilder url = new StringBuilder();
- url.append("jdbc:h2:").append(cacheDir.resolve(name).toUri());
+ url.append("jdbc:h2:").append(cacheDir.resolve(def.name()).toUri());
if (h2CacheSize >= 0) {
url.append(";CACHE_SIZE=");
// H2 CACHE_SIZE is always given in KB
@@ -220,9 +216,13 @@
if (h2AutoServer) {
url.append(";AUTO_SERVER=TRUE");
}
+ Long expireAfterWrite = def.expireAfterWrite(TimeUnit.SECONDS);
return new SqlStore<>(
url.toString(),
- keyType,
+ def.keyType(),
+ def.keySerializer(),
+ def.valueSerializer(),
+ def.version(),
maxSize,
expireAfterWrite == null ? 0 : expireAfterWrite.longValue());
}
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index eaa9af9..7db1a35 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -22,23 +22,18 @@
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.BloomFilter;
-import com.google.common.hash.Funnel;
-import com.google.common.hash.Funnels;
-import com.google.common.hash.PrimitiveSink;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.cache.CacheSerializer;
import com.google.gerrit.server.cache.PersistentCache;
import com.google.inject.TypeLiteral;
import java.io.IOException;
import java.io.InvalidClassException;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
-import java.sql.Types;
import java.util.Calendar;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
@@ -254,74 +249,11 @@
}
}
- private static class KeyType<K> {
- String columnType() {
- return "OTHER";
- }
-
- @SuppressWarnings("unchecked")
- K get(ResultSet rs, int col) throws SQLException {
- return (K) rs.getObject(col);
- }
-
- void set(PreparedStatement ps, int col, K value) throws SQLException {
- ps.setObject(col, value, Types.JAVA_OBJECT);
- }
-
- Funnel<K> funnel() {
- return new Funnel<K>() {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void funnel(K from, PrimitiveSink into) {
- try (ObjectOutputStream ser = new ObjectOutputStream(new SinkOutputStream(into))) {
- ser.writeObject(from);
- ser.flush();
- } catch (IOException err) {
- throw new RuntimeException("Cannot hash as Serializable", err);
- }
- }
- };
- }
-
- @SuppressWarnings("unchecked")
- static <K> KeyType<K> create(TypeLiteral<K> type) {
- if (type.getRawType() == String.class) {
- return (KeyType<K>) STRING;
- }
- return (KeyType<K>) OTHER;
- }
-
- static final KeyType<?> OTHER = new KeyType<>();
- static final KeyType<String> STRING =
- new KeyType<String>() {
- @Override
- String columnType() {
- return "VARCHAR(4096)";
- }
-
- @Override
- String get(ResultSet rs, int col) throws SQLException {
- return rs.getString(col);
- }
-
- @Override
- void set(PreparedStatement ps, int col, String value) throws SQLException {
- ps.setString(col, value);
- }
-
- @SuppressWarnings("unchecked")
- @Override
- Funnel<String> funnel() {
- Funnel<?> s = Funnels.unencodedCharsFunnel();
- return (Funnel<String>) s;
- }
- };
- }
-
static class SqlStore<K, V> {
private final String url;
private final KeyType<K> keyType;
+ private final CacheSerializer<V> valueSerializer;
+ private final int version;
private final long maxSize;
private final long expireAfterWrite;
private final BlockingQueue<SqlHandle> handles;
@@ -330,9 +262,18 @@
private volatile BloomFilter<K> bloomFilter;
private int estimatedSize;
- SqlStore(String jdbcUrl, TypeLiteral<K> keyType, long maxSize, long expireAfterWrite) {
+ SqlStore(
+ String jdbcUrl,
+ TypeLiteral<K> keyType,
+ CacheSerializer<K> keySerializer,
+ CacheSerializer<V> valueSerializer,
+ int version,
+ long maxSize,
+ long expireAfterWrite) {
this.url = jdbcUrl;
- this.keyType = KeyType.create(keyType);
+ this.keyType = createKeyType(keyType, keySerializer);
+ this.valueSerializer = valueSerializer;
+ this.version = version;
this.maxSize = maxSize;
this.expireAfterWrite = expireAfterWrite;
@@ -341,6 +282,15 @@
this.handles = new ArrayBlockingQueue<>(keep);
}
+ @SuppressWarnings("unchecked")
+ private static <T> KeyType<T> createKeyType(
+ TypeLiteral<T> type, CacheSerializer<T> serializer) {
+ if (type.getRawType() == String.class) {
+ return (KeyType<T>) StringKeyTypeImpl.INSTANCE;
+ }
+ return new ObjectKeyTypeImpl<>(serializer);
+ }
+
synchronized void open() {
if (bloomFilter == null) {
bloomFilter = buildBloomFilter();
@@ -372,33 +322,43 @@
SqlHandle c = null;
try {
c = acquire();
- try (Statement s = c.conn.createStatement()) {
- if (estimatedSize <= 0) {
- try (ResultSet r = s.executeQuery("SELECT COUNT(*) FROM data")) {
+ if (estimatedSize <= 0) {
+ try (PreparedStatement ps =
+ c.conn.prepareStatement("SELECT COUNT(*) FROM data WHERE version=?")) {
+ ps.setInt(1, version);
+ try (ResultSet r = ps.executeQuery()) {
estimatedSize = r.next() ? r.getInt(1) : 0;
}
}
+ }
- BloomFilter<K> b = newBloomFilter();
- try (ResultSet r = s.executeQuery("SELECT k FROM data")) {
+ BloomFilter<K> b = newBloomFilter();
+ try (PreparedStatement ps = c.conn.prepareStatement("SELECT k FROM data WHERE version=?")) {
+ ps.setInt(1, version);
+ try (ResultSet r = ps.executeQuery()) {
while (r.next()) {
b.put(keyType.get(r, 1));
}
- } catch (JdbcSQLException e) {
- if (e.getCause() instanceof InvalidClassException) {
- log.warn(
- "Entries cached for "
- + url
- + " have an incompatible class and can't be deserialized. "
- + "Cache is flushed.");
- invalidateAll();
- } else {
- throw e;
- }
}
- return b;
+ } catch (JdbcSQLException e) {
+ if (e.getCause() instanceof InvalidClassException) {
+ // If deserialization failed using default Java serialization, this means we are using
+ // the old serialVersionUID-based invalidation strategy. In that case, authors are
+ // most likely bumping serialVersionUID rather than using the new versioning in the
+ // CacheBinding. That's ok; we'll continue to support both for now.
+ // TODO(dborowitz): Remove this case when Java serialization is no longer used.
+ log.warn(
+ "Entries cached for "
+ + url
+ + " have an incompatible class and can't be deserialized. "
+ + "Cache is flushed.");
+ invalidateAll();
+ } else {
+ throw e;
+ }
}
- } catch (SQLException e) {
+ return b;
+ } catch (IOException | SQLException e) {
log.warn("Cannot build BloomFilter for " + url + ": " + e.getMessage());
c = close(c);
return null;
@@ -412,9 +372,14 @@
try {
c = acquire();
if (c.get == null) {
- c.get = c.conn.prepareStatement("SELECT v, created FROM data WHERE k=?");
+ c.get = c.conn.prepareStatement("SELECT v, created FROM data WHERE k=? AND version=?");
}
keyType.set(c.get, 1, key);
+
+ // Silently no results when the only value in the database is an older version. This will
+ // result in put overwriting the stored value with the new version, which is intended.
+ c.get.setInt(2, version);
+
try (ResultSet r = c.get.executeQuery()) {
if (!r.next()) {
missCount.incrementAndGet();
@@ -428,8 +393,7 @@
return null;
}
- @SuppressWarnings("unchecked")
- V val = (V) r.getObject(1);
+ V val = valueSerializer.deserialize(r.getBytes(1));
ValueHolder<V> h = new ValueHolder<>(val);
h.clean = true;
hitCount.incrementAndGet();
@@ -438,7 +402,7 @@
} finally {
c.get.clearParameters();
}
- } catch (SQLException e) {
+ } catch (IOException | SQLException e) {
if (!isOldClassNameError(e)) {
log.warn("Cannot read cache " + url + " for " + key, e);
}
@@ -466,13 +430,14 @@
return 1000 * expireAfterWrite < age;
}
- private void touch(SqlHandle c, K key) throws SQLException {
+ private void touch(SqlHandle c, K key) throws IOException, SQLException {
if (c.touch == null) {
- c.touch = c.conn.prepareStatement("UPDATE data SET accessed=? WHERE k=?");
+ c.touch = c.conn.prepareStatement("UPDATE data SET accessed=? WHERE k=? AND version=?");
}
try {
c.touch.setTimestamp(1, TimeUtil.nowTs());
keyType.set(c.touch, 2, key);
+ c.touch.setInt(3, version);
c.touch.executeUpdate();
} finally {
c.touch.clearParameters();
@@ -495,19 +460,21 @@
c = acquire();
if (c.put == null) {
c.put =
- c.conn.prepareStatement("MERGE INTO data (k, v, created, accessed) VALUES(?,?,?,?)");
+ c.conn.prepareStatement(
+ "MERGE INTO data (k, v, version, created, accessed) VALUES(?,?,?,?,?)");
}
try {
keyType.set(c.put, 1, key);
- c.put.setObject(2, holder.value, Types.JAVA_OBJECT);
- c.put.setTimestamp(3, new Timestamp(holder.created));
- c.put.setTimestamp(4, TimeUtil.nowTs());
+ c.put.setBytes(2, valueSerializer.serialize(holder.value));
+ c.put.setInt(3, version);
+ c.put.setTimestamp(4, new Timestamp(holder.created));
+ c.put.setTimestamp(5, TimeUtil.nowTs());
c.put.executeUpdate();
holder.clean = true;
} finally {
c.put.clearParameters();
}
- } catch (SQLException e) {
+ } catch (IOException | SQLException e) {
log.warn("Cannot put into cache " + url, e);
c = close(c);
} finally {
@@ -520,7 +487,7 @@
try {
c = acquire();
invalidate(c, key);
- } catch (SQLException e) {
+ } catch (IOException | SQLException e) {
log.warn("Cannot invalidate cache " + url, e);
c = close(c);
} finally {
@@ -528,12 +495,13 @@
}
}
- private void invalidate(SqlHandle c, K key) throws SQLException {
+ private void invalidate(SqlHandle c, K key) throws IOException, SQLException {
if (c.invalidate == null) {
- c.invalidate = c.conn.prepareStatement("DELETE FROM data WHERE k=?");
+ c.invalidate = c.conn.prepareStatement("DELETE FROM data WHERE k=? and version=?");
}
try {
keyType.set(c.invalidate, 1, key);
+ c.invalidate.setInt(2, version);
c.invalidate.executeUpdate();
} finally {
c.invalidate.clearParameters();
@@ -560,7 +528,15 @@
SqlHandle c = null;
try {
c = acquire();
+ try (PreparedStatement ps = c.conn.prepareStatement("DELETE FROM data WHERE version!=?")) {
+ ps.setInt(1, version);
+ int oldEntries = ps.executeUpdate();
+ log.info(
+ "Pruned {} entries not matching version {} from cache {}", oldEntries, version, url);
+ }
try (Statement s = c.conn.createStatement()) {
+ // Compute size without restricting to version (although obsolete data was just pruned
+ // anyway).
long used = 0;
try (ResultSet r = s.executeQuery("SELECT SUM(space) FROM data")) {
used = r.next() ? r.getLong(1) : 0;
@@ -570,8 +546,7 @@
}
try (ResultSet r =
- s.executeQuery(
- "SELECT" + " k" + ",space" + ",created" + " FROM data" + " ORDER BY accessed")) {
+ s.executeQuery("SELECT k, space, created FROM data ORDER BY accessed")) {
while (maxSize < used && r.next()) {
K key = keyType.get(r, 1);
Timestamp created = r.getTimestamp(3);
@@ -584,7 +559,7 @@
}
}
}
- } catch (SQLException e) {
+ } catch (IOException | SQLException e) {
log.warn("Cannot prune cache " + url, e);
c = close(c);
} finally {
@@ -599,7 +574,8 @@
try {
c = acquire();
try (Statement s = c.conn.createStatement();
- ResultSet r = s.executeQuery("SELECT" + " COUNT(*)" + ",SUM(space)" + " FROM data")) {
+ // Stats include total size regardless of version.
+ ResultSet r = s.executeQuery("SELECT COUNT(*), SUM(space) FROM data")) {
if (r.next()) {
size = r.getLong(1);
space = r.getLong(2);
@@ -662,6 +638,7 @@
stmt.addBatch(
"ALTER TABLE data ADD COLUMN IF NOT EXISTS "
+ "space BIGINT AS OCTET_LENGTH(k) + OCTET_LENGTH(v)");
+ stmt.addBatch("ALTER TABLE data ADD COLUMN IF NOT EXISTS version INT DEFAULT 0 NOT NULL");
stmt.executeBatch();
}
}
@@ -694,22 +671,4 @@
return null;
}
}
-
- private static class SinkOutputStream extends OutputStream {
- private final PrimitiveSink sink;
-
- SinkOutputStream(PrimitiveSink sink) {
- this.sink = sink;
- }
-
- @Override
- public void write(int b) {
- sink.putByte((byte) b);
- }
-
- @Override
- public void write(byte[] b, int p, int n) {
- sink.putBytes(b, p, n);
- }
- }
}
diff --git a/java/com/google/gerrit/server/cache/h2/KeyType.java b/java/com/google/gerrit/server/cache/h2/KeyType.java
new file mode 100644
index 0000000..3045e20
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/h2/KeyType.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2018 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.server.cache.h2;
+
+import com.google.common.hash.Funnel;
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+interface KeyType<K> {
+ String columnType();
+
+ K get(ResultSet rs, int col) throws IOException, SQLException;
+
+ void set(PreparedStatement ps, int col, K key) throws IOException, SQLException;
+
+ Funnel<K> funnel();
+}
diff --git a/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java b/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
new file mode 100644
index 0000000..b1a65fe
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/h2/ObjectKeyTypeImpl.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2018 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.server.cache.h2;
+
+import com.google.common.hash.Funnel;
+import com.google.common.hash.Funnels;
+import com.google.common.hash.PrimitiveSink;
+import com.google.gerrit.server.cache.CacheSerializer;
+import java.io.IOException;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+class ObjectKeyTypeImpl<K> implements KeyType<K> {
+ private final CacheSerializer<K> serializer;
+
+ ObjectKeyTypeImpl(CacheSerializer<K> serializer) {
+ this.serializer = serializer;
+ }
+
+ @Override
+ public String columnType() {
+ return "OTHER";
+ }
+
+ @Override
+ public K get(ResultSet rs, int col) throws IOException, SQLException {
+ return serializer.deserialize(rs.getBytes(col));
+ }
+
+ @Override
+ public void set(PreparedStatement ps, int col, K key) throws IOException, SQLException {
+ ps.setBytes(col, serializer.serialize(key));
+ }
+
+ @Override
+ public Funnel<K> funnel() {
+ return new Funnel<K>() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void funnel(K from, PrimitiveSink into) {
+ try {
+ Funnels.byteArrayFunnel().funnel(serializer.serialize(from), into);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot hash", e);
+ }
+ }
+ };
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/h2/StringKeyTypeImpl.java b/java/com/google/gerrit/server/cache/h2/StringKeyTypeImpl.java
new file mode 100644
index 0000000..8083ea5
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/h2/StringKeyTypeImpl.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2018 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.server.cache.h2;
+
+import com.google.common.hash.Funnel;
+import com.google.common.hash.Funnels;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+enum StringKeyTypeImpl implements KeyType<String> {
+ INSTANCE;
+
+ @Override
+ public String columnType() {
+ return "VARCHAR(4096)";
+ }
+
+ @Override
+ public String get(ResultSet rs, int col) throws SQLException {
+ return rs.getString(col);
+ }
+
+ @Override
+ public void set(PreparedStatement ps, int col, String value) throws SQLException {
+ ps.setString(col, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Funnel<String> funnel() {
+ Funnel<?> s = Funnels.unencodedCharsFunnel();
+ return (Funnel<String>) s;
+ }
+}
diff --git a/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java b/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
index 114e893..f16912a 100644
--- a/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
@@ -20,7 +20,7 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.Weigher;
-import com.google.gerrit.server.cache.CacheBinding;
+import com.google.gerrit.server.cache.CacheDef;
import com.google.gerrit.server.cache.ForwardingRemovalListener;
import com.google.gerrit.server.cache.MemoryCacheFactory;
import com.google.gerrit.server.config.ConfigUtil;
@@ -42,20 +42,21 @@
}
@Override
- public <K, V> Cache<K, V> build(CacheBinding<K, V> def) {
+ public <K, V> Cache<K, V> build(CacheDef<K, V> def) {
return create(def).build();
}
@Override
- public <K, V> LoadingCache<K, V> build(CacheBinding<K, V> def, CacheLoader<K, V> loader) {
+ public <K, V> LoadingCache<K, V> build(CacheDef<K, V> def, CacheLoader<K, V> loader) {
return create(def).build(loader);
}
@SuppressWarnings("unchecked")
- private <K, V> CacheBuilder<K, V> create(CacheBinding<K, V> def) {
+ private <K, V> CacheBuilder<K, V> create(CacheDef<K, V> def) {
CacheBuilder<K, V> builder = newCacheBuilder();
builder.recordStats();
- builder.maximumWeight(cfg.getLong("cache", def.name(), "memoryLimit", def.maximumWeight()));
+ builder.maximumWeight(
+ cfg.getLong("cache", def.configKey(), "memoryLimit", def.maximumWeight()));
builder = builder.removalListener(forwardingRemovalListenerFactory.create(def.name()));
@@ -66,10 +67,10 @@
builder.weigher(weigher);
Long age = def.expireAfterWrite(TimeUnit.SECONDS);
- if (has(def.name(), "maxAge")) {
+ if (has(def.configKey(), "maxAge")) {
builder.expireAfterWrite(
ConfigUtil.getTimeUnit(
- cfg, "cache", def.name(), "maxAge", age != null ? age : 0, TimeUnit.SECONDS),
+ cfg, "cache", def.configKey(), "maxAge", age != null ? age : 0, TimeUnit.SECONDS),
TimeUnit.SECONDS);
} else if (age != null) {
builder.expireAfterWrite(age, TimeUnit.SECONDS);
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index a08203e..34cbea3 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -39,7 +39,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.config.SendEmailExecutor;
import com.google.gerrit.server.events.CommitReceivedEvent;
@@ -93,7 +92,6 @@
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
- private final IdentifiedUser.GenericFactory userFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchSetUtil psUtil;
private final ApprovalsUtil approvalsUtil;
@@ -143,7 +141,6 @@
ChangeInserter(
PermissionBackend permissionBackend,
ProjectCache projectCache,
- IdentifiedUser.GenericFactory userFactory,
PatchSetInfoFactory patchSetInfoFactory,
PatchSetUtil psUtil,
ApprovalsUtil approvalsUtil,
@@ -159,7 +156,6 @@
@Assisted String refName) {
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
- this.userFactory = userFactory;
this.patchSetInfoFactory = patchSetInfoFactory;
this.psUtil = psUtil;
this.approvalsUtil = approvalsUtil;
@@ -465,9 +461,8 @@
.filter(
accountId -> {
try {
- IdentifiedUser user = userFactory.create(accountId);
return permissionBackend
- .user(user)
+ .absentUser(accountId)
.change(notes)
.database(db)
.test(ChangePermission.READ)
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 82affe0..6c03912 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -116,11 +116,11 @@
import com.google.gerrit.server.account.AccountInfoComparator;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.GpgApiAdapter;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.TrackingFooters;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
@@ -158,6 +158,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -269,11 +270,11 @@
private final ChangeNotes.Factory notesFactory;
private final ChangeResource.Factory changeResourceFactory;
private final ChangeKindCache changeKindCache;
- private final ChangeIndexCollection indexes;
private final ApprovalsUtil approvalsUtil;
private final RemoveReviewerControl removeReviewerControl;
private final TrackingFooters trackingFooters;
private final Metrics metrics;
+ private final boolean enableParallelFormatting;
private final ExecutorService fanOutExecutor;
private boolean lazyLoad = true;
@@ -304,11 +305,11 @@
ChangeNotes.Factory notesFactory,
ChangeResource.Factory changeResourceFactory,
ChangeKindCache changeKindCache,
- ChangeIndexCollection indexes,
ApprovalsUtil approvalsUtil,
RemoveReviewerControl removeReviewerControl,
TrackingFooters trackingFooters,
Metrics metrics,
+ @GerritServerConfig Config config,
@FanOutExecutor ExecutorService fanOutExecutor,
@Assisted Iterable<ListChangesOption> options) {
this.db = db;
@@ -332,11 +333,11 @@
this.notesFactory = notesFactory;
this.changeResourceFactory = changeResourceFactory;
this.changeKindCache = changeKindCache;
- this.indexes = indexes;
this.approvalsUtil = approvalsUtil;
this.removeReviewerControl = removeReviewerControl;
this.trackingFooters = trackingFooters;
this.metrics = metrics;
+ this.enableParallelFormatting = config.getBoolean("change", "enableParallelFormatting", false);
this.fanOutExecutor = fanOutExecutor;
this.options = Sets.immutableEnumSet(options);
}
@@ -507,7 +508,7 @@
}
long numProjects = changes.stream().map(c -> c.project()).distinct().count();
- if (!lazyLoad || changes.size() < 3 || numProjects < 2) {
+ if (!enableParallelFormatting || !lazyLoad || changes.size() < 3 || numProjects < 2) {
// Format these changes in the request thread as the multithreading overhead would be too
// high.
List<ChangeInfo> result = new ArrayList<>(changes.size());
@@ -606,11 +607,7 @@
out.project = in.getProject().get();
out.branch = in.getDest().getShortName();
out.topic = in.getTopic();
- if (indexes.getSearchIndex().getSchema().hasField(ChangeField.ASSIGNEE)) {
- if (in.getAssignee() != null) {
- out.assignee = accountLoader.get(in.getAssignee());
- }
- }
+ out.assignee = in.getAssignee() != null ? accountLoader.get(in.getAssignee()) : null;
out.hashtags = cd.hashtags();
out.changeId = in.getKey().get();
if (in.getStatus().isOpen()) {
diff --git a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 7b44d8f..6449662 100644
--- a/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -16,8 +16,6 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import static org.eclipse.jgit.lib.ObjectIdSerializer.readWithoutMarker;
-import static org.eclipse.jgit.lib.ObjectIdSerializer.writeWithoutMarker;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
@@ -30,6 +28,10 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.CacheSerializer;
+import com.google.gerrit.server.cache.EnumCacheSerializer;
+import com.google.gerrit.server.cache.ProtoCacheSerializers;
+import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.InMemoryInserter;
@@ -39,10 +41,8 @@
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.name.Named;
+import com.google.protobuf.ByteString;
import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
@@ -51,6 +51,7 @@
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
@@ -72,7 +73,10 @@
bind(ChangeKindCache.class).to(ChangeKindCacheImpl.class);
persist(ID_CACHE, Key.class, ChangeKind.class)
.maximumWeight(2 << 20)
- .weigher(ChangeKindWeigher.class);
+ .weigher(ChangeKindWeigher.class)
+ .version(1)
+ .keySerializer(new Key.Serializer())
+ .valueSerializer(new EnumCacheSerializer<>(ChangeKind.class));
}
};
}
@@ -122,9 +126,7 @@
}
}
- public static class Key implements Serializable {
- private static final long serialVersionUID = 1L;
-
+ public static class Key {
private transient ObjectId prior;
private transient ObjectId next;
private transient String strategyName;
@@ -171,16 +173,28 @@
return Objects.hash(prior, next, strategyName);
}
- private void writeObject(ObjectOutputStream out) throws IOException {
- writeWithoutMarker(out, prior);
- writeWithoutMarker(out, next);
- out.writeUTF(strategyName);
- }
+ @VisibleForTesting
+ static class Serializer implements CacheSerializer<Key> {
+ @Override
+ public byte[] serialize(Key object) throws IOException {
+ byte[] buf = new byte[Constants.OBJECT_ID_LENGTH];
+ ChangeKindKeyProto.Builder b = ChangeKindKeyProto.newBuilder();
+ object.getPrior().copyRawTo(buf, 0);
+ b.setPrior(ByteString.copyFrom(buf));
+ object.getNext().copyRawTo(buf, 0);
+ b.setNext(ByteString.copyFrom(buf));
+ b.setStrategyName(object.getStrategyName());
+ return ProtoCacheSerializers.toByteArray(b.build());
+ }
- private void readObject(ObjectInputStream in) throws IOException {
- prior = readWithoutMarker(in);
- next = readWithoutMarker(in);
- strategyName = in.readUTF();
+ @Override
+ public Key deserialize(byte[] in) throws IOException {
+ ChangeKindKeyProto proto = ChangeKindKeyProto.parseFrom(in);
+ return new Key(
+ ObjectId.fromRaw(proto.getPrior().toByteArray()),
+ ObjectId.fromRaw(proto.getNext().toByteArray()),
+ proto.getStrategyName());
+ }
}
}
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java
index 3a32f8f..d05d133 100644
--- a/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -311,7 +311,11 @@
}
private void validate(RepoContext ctx)
- throws AuthException, ResourceConflictException, IOException, PermissionBackendException {
+ throws AuthException, ResourceConflictException, IOException, PermissionBackendException,
+ OrmException {
+ // Not allowed to create a new patch set if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(origNotes, ctx.getUser());
+
if (checkAddPatchSetPermission) {
permissionBackend
.user(ctx.getUser())
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index 83f0120..80d1cd1 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -394,7 +394,8 @@
}
private void assertCanEdit(ChangeNotes notes)
- throws AuthException, PermissionBackendException, IOException, ResourceConflictException {
+ throws AuthException, PermissionBackendException, IOException, ResourceConflictException,
+ OrmException {
if (!currentUser.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
@@ -406,6 +407,8 @@
"change %s is %s", c.getChangeId(), c.getStatus().toString().toLowerCase()));
}
+ // Not allowed to edit if the current patch set is locked.
+ patchSetUtil.checkPatchSetNotLocked(notes, currentUser.get());
try {
permissionBackend
.currentUser()
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index bc2c674..e2bec34 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -298,7 +298,7 @@
public static String createConflictMessage(List<String> conflicts) {
StringBuilder sb = new StringBuilder("merge conflict(s)");
for (String c : conflicts) {
- sb.append('\n' + c);
+ sb.append('\n').append(c);
}
return sb.toString();
}
diff --git a/java/com/google/gerrit/server/git/TagCache.java b/java/com/google/gerrit/server/git/TagCache.java
index d346997..b8acd0a 100644
--- a/java/com/google/gerrit/server/git/TagCache.java
+++ b/java/com/google/gerrit/server/git/TagCache.java
@@ -103,7 +103,7 @@
cache.put(name.get(), val);
}
- static class EntryVal implements Serializable {
+ public static class EntryVal implements Serializable {
static final long serialVersionUID = 1L;
transient TagSetHolder holder;
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 17e804f..dbb6bcf 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2452,6 +2452,13 @@
RevCommit newCommit = rp.getRevWalk().parseCommit(newCommitId);
RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
+
+ // Not allowed to create a new patch set if the current patch set is locked.
+ if (psUtil.isPatchSetLocked(notes, user)) {
+ reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
+ return false;
+ }
+
try {
permissions.change(notes).database(db).check(ChangePermission.ADD_PATCH_SET);
} catch (AuthException no) {
@@ -2984,13 +2991,7 @@
id,
mergedByPushOpFactory
.create(requestScopePropagator, req.psId, refName)
- .setPatchSetProvider(
- new Provider<PatchSet>() {
- @Override
- public PatchSet get() {
- return req.replaceOp.getPatchSet();
- }
- }));
+ .setPatchSetProvider(req.replaceOp::getPatchSet));
bu.addOp(id, new ChangeProgressOp(closeProgress));
}
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index e766e82..b76d9d50 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -335,9 +335,9 @@
StringBuilder sb = new StringBuilder();
sb.append("ERROR: ").append(errMsg);
- if (c.getFullMessage().indexOf(CHANGE_ID_PREFIX) >= 0) {
+ if (c.getFullMessage().contains(CHANGE_ID_PREFIX)) {
String lastLine = Iterables.getLast(Splitter.on('\n').split(c.getFullMessage()), "");
- if (lastLine.indexOf(CHANGE_ID_PREFIX) == -1) {
+ if (!lastLine.contains(CHANGE_ID_PREFIX)) {
sb.append('\n');
sb.append('\n');
sb.append("Hint: A potential ");
diff --git a/java/com/google/gerrit/server/index/OnlineReindexer.java b/java/com/google/gerrit/server/index/OnlineReindexer.java
index ed1d8b5..ee2a76e 100644
--- a/java/com/google/gerrit/server/index/OnlineReindexer.java
+++ b/java/com/google/gerrit/server/index/OnlineReindexer.java
@@ -63,6 +63,8 @@
try {
reindex();
ok = true;
+ } catch (IOException e) {
+ log.error("Online reindex of {} schema version {} failed", name, version(index), e);
} finally {
running.set(false);
if (!ok) {
@@ -91,7 +93,7 @@
return i.getSchema().getVersion();
}
- private void reindex() {
+ private void reindex() throws IOException {
for (OnlineUpgradeListener listener : listeners) {
listener.onStart(name, oldVersion, newVersion);
}
@@ -106,6 +108,10 @@
name,
version(indexes.getSearchIndex()),
version(index));
+
+ if (oldVersion != newVersion) {
+ index.deleteAll();
+ }
SiteIndexer.Result result = batchIndexer.indexAll(index);
if (!result.success()) {
log.error(
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 6c05ecc..11f50a9 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -27,7 +27,6 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
import com.google.gerrit.server.mail.MailHeader;
@@ -100,8 +99,7 @@
/** Is the from user in an email squelching group? */
try {
- IdentifiedUser user = args.identifiedUserFactory.create(id);
- args.permissionBackend.user(user).check(GlobalPermission.EMAIL_REVIEWERS);
+ args.permissionBackend.absentUser(id).check(GlobalPermission.EMAIL_REVIEWERS);
} catch (AuthException | PermissionBackendException e) {
emailOnlyAuthors = true;
}
diff --git a/java/com/google/gerrit/server/mail/send/EmailHeader.java b/java/com/google/gerrit/server/mail/send/EmailHeader.java
index 0bfe428..29354f2 100644
--- a/java/com/google/gerrit/server/mail/send/EmailHeader.java
+++ b/java/com/google/gerrit/server/mail/send/EmailHeader.java
@@ -23,7 +23,6 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -185,11 +184,7 @@
}
void remove(java.lang.String email) {
- for (Iterator<Address> i = list.iterator(); i.hasNext(); ) {
- if (i.next().getEmail().equals(email)) {
- i.remove();
- }
- }
+ list.removeIf(address -> address.getEmail().equals(email));
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/NotificationEmail.java b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
index 1b2c205..2a24f38 100644
--- a/java/com/google/gerrit/server/mail/send/NotificationEmail.java
+++ b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
@@ -111,8 +111,11 @@
// shortProjectName is the project name with the path abbreviated.
soyContext.put("shortProjectName", getShortProjectName(projectName));
+ // instanceAndProjectName is the instance's name followed by the abbreviated project path
+ soyContext.put(
+ "instanceAndProjectName",
+ getInstanceAndProjectName(args.instanceNameProvider.get(), projectName));
soyContext.put("addInstanceNameInSubject", args.addInstanceNameInSubject);
- soyContext.put("instanceName", args.instanceNameProvider.get());
soyContextEmailData.put("sshHost", getSshHost());
@@ -133,4 +136,14 @@
return "..." + projectName.substring(lastIndexSlash + 1);
}
+
+ @VisibleForTesting
+ protected static String getInstanceAndProjectName(String instanceName, String projectName) {
+ if (instanceName == null || instanceName.isEmpty()) {
+ return getShortProjectName(projectName);
+ }
+ // Extract the project name (everything after the last slash) and prepends it with gerrit's
+ // instance name
+ return instanceName + "/" + projectName.substring(projectName.lastIndexOf('/') + 1);
+ }
}
diff --git a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
index b08e594..10e8b0b 100644
--- a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
+++ b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
@@ -114,9 +114,7 @@
smtpPass = cfg.getString("sendemail", null, "smtppass");
Set<String> rcpt = new HashSet<>();
- for (String addr : cfg.getStringList("sendemail", null, "allowrcpt")) {
- rcpt.add(addr);
- }
+ Collections.addAll(rcpt, cfg.getStringList("sendemail", null, "allowrcpt"));
allowrcpt = Collections.unmodifiableSet(rcpt);
importance = cfg.getString("sendemail", null, "importance");
expiryDays = cfg.getInt("sendemail", null, "expiryDays", 0);
diff --git a/java/com/google/gerrit/server/notedb/RepoSequence.java b/java/com/google/gerrit/server/notedb/RepoSequence.java
index 3554f4b..47fec59 100644
--- a/java/com/google/gerrit/server/notedb/RepoSequence.java
+++ b/java/com/google/gerrit/server/notedb/RepoSequence.java
@@ -92,6 +92,7 @@
private final Project.NameKey projectName;
private final String refName;
private final Seed seed;
+ private final int floor;
private final int batchSize;
private final Runnable afterReadRef;
private final Retryer<RefUpdate.Result> retryer;
@@ -119,7 +120,28 @@
seed,
batchSize,
Runnables.doNothing(),
- RETRYER);
+ RETRYER,
+ 0);
+ }
+
+ public RepoSequence(
+ GitRepositoryManager repoManager,
+ GitReferenceUpdated gitRefUpdated,
+ Project.NameKey projectName,
+ String name,
+ Seed seed,
+ int batchSize,
+ int floor) {
+ this(
+ repoManager,
+ gitRefUpdated,
+ projectName,
+ name,
+ seed,
+ batchSize,
+ Runnables.doNothing(),
+ RETRYER,
+ floor);
}
@VisibleForTesting
@@ -132,6 +154,19 @@
int batchSize,
Runnable afterReadRef,
Retryer<RefUpdate.Result> retryer) {
+ this(repoManager, gitRefUpdated, projectName, name, seed, batchSize, afterReadRef, retryer, 0);
+ }
+
+ RepoSequence(
+ GitRepositoryManager repoManager,
+ GitReferenceUpdated gitRefUpdated,
+ Project.NameKey projectName,
+ String name,
+ Seed seed,
+ int batchSize,
+ Runnable afterReadRef,
+ Retryer<RefUpdate.Result> retryer,
+ int floor) {
this.repoManager = checkNotNull(repoManager, "repoManager");
this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
this.projectName = checkNotNull(projectName, "projectName");
@@ -145,6 +180,7 @@
this.refName = RefNames.REFS_SEQUENCES + name;
this.seed = checkNotNull(seed, "seed");
+ this.floor = floor;
checkArgument(batchSize > 0, "expected batchSize > 0, got: %s", batchSize);
this.batchSize = batchSize;
@@ -282,6 +318,7 @@
oldId = ref.getObjectId();
next = parse(rw, oldId);
}
+ next = Math.max(floor, next);
return store(repo, rw, oldId, next + count);
}
}
diff --git a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
index b408355..59fdde7 100644
--- a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
@@ -560,6 +560,8 @@
throws OrmException, IOException {
try (ReviewDb db = schemaFactory.open()) {
@SuppressWarnings("deprecation")
+ final int nextChangeId = db.nextChangeId();
+
RepoSequence seq =
new RepoSequence(
repoManager,
@@ -569,8 +571,9 @@
// If sequenceGap is 0, this writes into the sequence ref the same ID that is returned
// by the call to seq.next() below. If we actually used this as a change ID, that
// would be a problem, but we just discard it, so this is safe.
- () -> db.nextChangeId() + sequenceGap - 1,
- 1);
+ () -> nextChangeId + sequenceGap - 1,
+ 1,
+ nextChangeId);
seq.next();
}
return saveState(prev, READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY);
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index 990c318..b13d921 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -21,19 +21,14 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.LabelFunction;
-import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
import com.google.gerrit.server.query.change.ChangeData;
@@ -52,19 +47,11 @@
static class Factory {
private final ChangeData.Factory changeDataFactory;
private final ChangeNotes.Factory notesFactory;
- private final ApprovalsUtil approvalsUtil;
- private final PatchSetUtil patchSetUtil;
@Inject
- Factory(
- ChangeData.Factory changeDataFactory,
- ChangeNotes.Factory notesFactory,
- ApprovalsUtil approvalsUtil,
- PatchSetUtil patchSetUtil) {
+ Factory(ChangeData.Factory changeDataFactory, ChangeNotes.Factory notesFactory) {
this.changeDataFactory = changeDataFactory;
this.notesFactory = notesFactory;
- this.approvalsUtil = approvalsUtil;
- this.patchSetUtil = patchSetUtil;
}
ChangeControl create(
@@ -74,27 +61,19 @@
}
ChangeControl create(RefControl refControl, ChangeNotes notes) {
- return new ChangeControl(changeDataFactory, approvalsUtil, refControl, notes, patchSetUtil);
+ return new ChangeControl(changeDataFactory, refControl, notes);
}
}
private final ChangeData.Factory changeDataFactory;
- private final ApprovalsUtil approvalsUtil;
private final RefControl refControl;
private final ChangeNotes notes;
- private final PatchSetUtil patchSetUtil;
private ChangeControl(
- ChangeData.Factory changeDataFactory,
- ApprovalsUtil approvalsUtil,
- RefControl refControl,
- ChangeNotes notes,
- PatchSetUtil patchSetUtil) {
+ ChangeData.Factory changeDataFactory, RefControl refControl, ChangeNotes notes) {
this.changeDataFactory = changeDataFactory;
- this.approvalsUtil = approvalsUtil;
this.refControl = refControl;
this.notes = notes;
- this.patchSetUtil = patchSetUtil;
}
ForChange asForChange(@Nullable ChangeData cd, @Nullable Provider<ReviewDb> db) {
@@ -105,8 +84,7 @@
if (getUser().equals(who)) {
return this;
}
- return new ChangeControl(
- changeDataFactory, approvalsUtil, refControl.forUser(who), notes, patchSetUtil);
+ return new ChangeControl(changeDataFactory, refControl.forUser(who), notes);
}
private CurrentUser getUser() {
@@ -130,26 +108,24 @@
}
/** Can this user abandon this change? */
- private boolean canAbandon(ReviewDb db) throws OrmException {
- return (isOwner() // owner (aka creator) of the change can abandon
- || refControl.isOwner() // branch owner can abandon
- || getProjectControl().isOwner() // project owner can abandon
- || refControl.canPerform(Permission.ABANDON) // user can abandon a specific ref
- || getProjectControl().isAdmin())
- && !isPatchSetLocked(db);
+ private boolean canAbandon() {
+ return isOwner() // owner (aka creator) of the change can abandon
+ || refControl.isOwner() // branch owner can abandon
+ || getProjectControl().isOwner() // project owner can abandon
+ || refControl.canPerform(Permission.ABANDON) // user can abandon a specific ref
+ || getProjectControl().isAdmin();
}
/** Can this user rebase this change? */
- private boolean canRebase(ReviewDb db) throws OrmException {
+ private boolean canRebase() {
return (isOwner() || refControl.canSubmit(isOwner()) || refControl.canRebase())
- && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)
- && !isPatchSetLocked(db);
+ && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE);
}
/** Can this user restore this change? */
- private boolean canRestore(ReviewDb db) throws OrmException {
+ private boolean canRestore() {
// Anyone who can abandon the change can restore it, as long as they can create changes.
- return canAbandon(db) && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE);
+ return canAbandon() && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE);
}
/** The range of permitted values associated with a label permission. */
@@ -158,8 +134,8 @@
}
/** Can this user add a patch set to this change? */
- private boolean canAddPatchSet(ReviewDb db) throws OrmException {
- if (!(refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)) || isPatchSetLocked(db)) {
+ private boolean canAddPatchSet() {
+ if (!refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)) {
return false;
}
if (isOwner()) {
@@ -168,29 +144,6 @@
return refControl.canAddPatchSet();
}
- /** Is the current patch set locked against state changes? */
- private boolean isPatchSetLocked(ReviewDb db) throws OrmException {
- if (getChange().getStatus() == Change.Status.MERGED) {
- return false;
- }
-
- for (PatchSetApproval ap :
- approvalsUtil.byPatchSet(
- db, notes, getUser(), getChange().currentPatchSetId(), null, null)) {
- LabelType type =
- getProjectControl()
- .getProjectState()
- .getLabelTypes(notes, getUser())
- .byLabel(ap.getLabel());
- if (type != null
- && ap.getValue() == 1
- && type.getFunction() == LabelFunction.PATCH_SET_LOCK) {
- return true;
- }
- }
- return false;
- }
-
/** Is this user the owner of the change? */
private boolean isOwner() {
if (getUser().isIdentifiedUser()) {
@@ -355,12 +308,12 @@
case READ:
return isVisible(db(), changeData());
case ABANDON:
- return canAbandon(db());
+ return canAbandon();
case DELETE:
return (isOwner() && refControl.canPerform(Permission.DELETE_OWN_CHANGES))
|| getProjectControl().isAdmin();
case ADD_PATCH_SET:
- return canAddPatchSet(db());
+ return canAddPatchSet();
case EDIT_ASSIGNEE:
return canEditAssignee();
case EDIT_DESCRIPTION:
@@ -370,9 +323,9 @@
case EDIT_TOPIC_NAME:
return canEditTopicName();
case REBASE:
- return canRebase(db());
+ return canRebase();
case RESTORE:
- return canRestore(db());
+ return canRestore();
case SUBMIT:
return refControl.canSubmit(isOwner());
diff --git a/java/com/google/gerrit/server/permissions/ChangePermission.java b/java/com/google/gerrit/server/permissions/ChangePermission.java
index 6a23cdd..ba1785d 100644
--- a/java/com/google/gerrit/server/permissions/ChangePermission.java
+++ b/java/com/google/gerrit/server/permissions/ChangePermission.java
@@ -20,15 +20,39 @@
public enum ChangePermission implements ChangePermissionOrLabel {
READ,
+ /**
+ * The change can't be restored if its current patch set is locked.
+ *
+ * <p>Before checking this permission, the caller should first verify the current patch set of the
+ * change is not locked by calling {@code PatchSetUtil.isPatchSetLocked}.
+ */
RESTORE,
DELETE,
+ /**
+ * The change can't be abandoned if its current patch set is locked.
+ *
+ * <p>Before checking this permission, the caller should first verify the current patch set of the
+ * change is not locked by calling {@code PatchSetUtil.isPatchSetLocked}.
+ */
ABANDON,
EDIT_ASSIGNEE,
EDIT_DESCRIPTION,
EDIT_HASHTAGS,
EDIT_TOPIC_NAME,
REMOVE_REVIEWER,
+ /**
+ * A new patch set can't be added if the patch set is locked for the change.
+ *
+ * <p>Before checking this permission, the caller should first verify the current patch set of the
+ * change is not locked by calling {@code PatchSetUtil.isPatchSetLocked}.
+ */
ADD_PATCH_SET,
+ /**
+ * The change can't be rebased if its current patch set is locked.
+ *
+ * <p>Before checking this permission, the caller should first verify the current patch set of the
+ * change is not locked by calling {@code PatchSetUtil.isPatchSetLocked}.
+ */
REBASE,
SUBMIT,
SUBMIT_AS("submit on behalf of other users");
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 4975c55..296965a 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -52,7 +52,8 @@
public class ProjectCacheImpl implements ProjectCache {
private static final Logger log = LoggerFactory.getLogger(ProjectCacheImpl.class);
- private static final String CACHE_NAME = "projects";
+ public static final String CACHE_NAME = "projects";
+
private static final String CACHE_LIST = "project_list";
public static Module module() {
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index e064265..28f620b 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -47,14 +47,9 @@
import com.google.gerrit.server.git.BranchOrderSection;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.rules.PrologEnvironment;
-import com.google.gerrit.server.rules.RulesCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import com.googlecode.prolog_cafe.exceptions.CompileException;
-import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
import java.io.IOException;
-import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -87,18 +82,13 @@
private final SitePaths sitePaths;
private final AllProjectsName allProjectsName;
private final ProjectCache projectCache;
- private final PrologEnvironment.Factory envFactory;
private final GitRepositoryManager gitMgr;
- private final RulesCache rulesCache;
private final List<CommentLinkInfo> commentLinks;
private final ProjectConfig config;
private final Map<String, ProjectLevelConfig> configs;
private final Set<AccountGroup.UUID> localOwners;
- /** Prolog rule state. */
- private volatile PrologMachineCopy rulesMachine;
-
/** Last system time the configuration's revision was examined. */
private volatile long lastCheckGeneration;
@@ -120,9 +110,7 @@
final ProjectCache projectCache,
final AllProjectsName allProjectsName,
final AllUsersName allUsersName,
- final PrologEnvironment.Factory envFactory,
final GitRepositoryManager gitMgr,
- final RulesCache rulesCache,
final List<CommentLinkInfo> commentLinks,
final CapabilityCollection.Factory limitsFactory,
@Assisted final ProjectConfig config) {
@@ -131,9 +119,7 @@
this.isAllProjects = config.getProject().getNameKey().equals(allProjectsName);
this.isAllUsers = config.getProject().getNameKey().equals(allUsersName);
this.allProjectsName = allProjectsName;
- this.envFactory = envFactory;
this.gitMgr = gitMgr;
- this.rulesCache = rulesCache;
this.commentLinks = commentLinks;
this.config = config;
this.configs = new HashMap<>();
@@ -215,29 +201,6 @@
.anyMatch(Objects::nonNull);
}
- /** @return Construct a new PrologEnvironment for the calling thread. */
- public PrologEnvironment newPrologEnvironment() throws CompileException {
- PrologMachineCopy pmc = rulesMachine;
- if (pmc == null) {
- pmc = rulesCache.loadMachine(getNameKey(), config.getRulesId());
- rulesMachine = pmc;
- }
- return envFactory.create(pmc);
- }
-
- /**
- * Like {@link #newPrologEnvironment()} but instead of reading the rules.pl read the provided
- * input stream.
- *
- * @param name a name of the input stream. Could be any name.
- * @param in stream to read prolog rules from
- * @throws CompileException
- */
- public PrologEnvironment newPrologEnvironment(String name, Reader in) throws CompileException {
- PrologMachineCopy pmc = rulesCache.loadMachine(name, in);
- return envFactory.create(pmc);
- }
-
public Project getProject() {
return config.getProject();
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index 5d5a95f..89f302b 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -17,6 +17,7 @@
import com.google.gerrit.index.query.IsVisibleToPredicate;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -40,19 +41,22 @@
protected final CurrentUser user;
protected final PermissionBackend permissionBackend;
protected final ProjectCache projectCache;
+ private final Provider<AnonymousUser> anonymousUserProvider;
public ChangeIsVisibleToPredicate(
Provider<ReviewDb> db,
ChangeNotes.Factory notesFactory,
CurrentUser user,
PermissionBackend permissionBackend,
- ProjectCache projectCache) {
+ ProjectCache projectCache,
+ Provider<AnonymousUser> anonymousUserProvider) {
super(ChangeQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
this.db = db;
this.notesFactory = notesFactory;
this.user = user;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
+ this.anonymousUserProvider = anonymousUserProvider;
}
@Override
@@ -81,13 +85,12 @@
}
boolean visible;
+ PermissionBackend.WithUser withUser =
+ user.isIdentifiedUser()
+ ? permissionBackend.absentUser(user.getAccountId())
+ : permissionBackend.user(anonymousUserProvider.get());
try {
- visible =
- permissionBackend
- .user(user)
- .indexedChange(cd, notes)
- .database(db)
- .test(ChangePermission.READ);
+ visible = withUser.indexedChange(cd, notes).database(db).test(ChangePermission.READ);
} catch (PermissionBackendException e) {
Throwable cause = e.getCause();
if (cause instanceof RepositoryNotFoundException) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 630f2f3..68b139f 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -44,6 +44,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
@@ -214,6 +215,7 @@
final StarredChangesUtil starredChangesUtil;
final SubmitDryRun submitDryRun;
final GroupMembers groupMembers;
+ final Provider<AnonymousUser> anonymousUserProvider;
private final Provider<CurrentUser> self;
@@ -246,7 +248,8 @@
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
NotesMigration notesMigration,
- GroupMembers groupMembers) {
+ GroupMembers groupMembers,
+ Provider<AnonymousUser> anonymousUserProvider) {
this(
db,
queryProvider,
@@ -274,7 +277,8 @@
starredChangesUtil,
accountCache,
notesMigration,
- groupMembers);
+ groupMembers,
+ anonymousUserProvider);
}
private Arguments(
@@ -304,7 +308,8 @@
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
NotesMigration notesMigration,
- GroupMembers groupMembers) {
+ GroupMembers groupMembers,
+ Provider<AnonymousUser> anonymousUserProvider) {
this.db = db;
this.queryProvider = queryProvider;
this.rewriter = rewriter;
@@ -332,6 +337,7 @@
this.hasOperands = hasOperands;
this.notesMigration = notesMigration;
this.groupMembers = groupMembers;
+ this.anonymousUserProvider = anonymousUserProvider;
}
Arguments asUser(CurrentUser otherUser) {
@@ -362,7 +368,8 @@
starredChangesUtil,
accountCache,
notesMigration,
- groupMembers);
+ groupMembers,
+ anonymousUserProvider);
}
Arguments asUser(Account.Id otherId) {
@@ -909,7 +916,12 @@
public Predicate<ChangeData> visibleto(CurrentUser user) {
return new ChangeIsVisibleToPredicate(
- args.db, args.notesFactory, user, args.permissionBackend, args.projectCache);
+ args.db,
+ args.notesFactory,
+ user,
+ args.permissionBackend,
+ args.projectCache,
+ args.anonymousUserProvider);
}
public Predicate<ChangeData> is_visible() throws QueryParseException {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index 5fe918e..21d7b2d 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -26,6 +26,7 @@
import com.google.gerrit.index.query.QueryProcessor;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountLimits;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -65,6 +66,7 @@
private final DynamicMap<ChangeAttributeFactory> attributeFactories;
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
+ private final Provider<AnonymousUser> anonymousUserProvider;
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
@@ -85,7 +87,8 @@
ChangeNotes.Factory notesFactory,
DynamicMap<ChangeAttributeFactory> attributeFactories,
PermissionBackend permissionBackend,
- ProjectCache projectCache) {
+ ProjectCache projectCache,
+ Provider<AnonymousUser> anonymousUserProvider) {
super(
metricMaker,
ChangeSchemaDefinitions.INSTANCE,
@@ -100,6 +103,7 @@
this.attributeFactories = attributeFactories;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
+ this.anonymousUserProvider = anonymousUserProvider;
}
@Override
@@ -143,7 +147,12 @@
return new AndChangeSource(
pred,
new ChangeIsVisibleToPredicate(
- db, notesFactory, userProvider.get(), permissionBackend, projectCache),
+ db,
+ notesFactory,
+ userProvider.get(),
+ permissionBackend,
+ projectCache,
+ anonymousUserProvider),
start);
}
}
diff --git a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index 7240452..19fd4a1 100644
--- a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -123,7 +123,7 @@
// Check the user has 'READ' permission.
try {
PermissionBackend.ForChange perm =
- permissionBackend.user(reviewer).database(dbProvider).change(cd);
+ permissionBackend.absentUser(approver).database(dbProvider).change(cd);
ProjectState projectState = projectCache.checkedGet(cd.project());
return projectState != null
&& projectState.statePermitsRead()
diff --git a/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java b/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
index 1894b06..a0aa8b5 100644
--- a/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
@@ -18,6 +18,7 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gwtorm.server.OrmException;
public class ReviewerinPredicate extends PostFilterPredicate<ChangeData> {
@@ -36,7 +37,7 @@
@Override
public boolean match(ChangeData object) throws OrmException {
- for (Account.Id accountId : object.reviewers().all()) {
+ for (Account.Id accountId : object.reviewers().byState(ReviewerStateInternal.REVIEWER)) {
IdentifiedUser reviewer = userFactory.create(accountId);
if (reviewer.getEffectiveGroups().contains(uuid)) {
return true;
diff --git a/java/com/google/gerrit/server/restapi/change/Abandon.java b/java/com/google/gerrit/server/restapi/change/Abandon.java
index 5485cf6..116cbdb 100644
--- a/java/com/google/gerrit/server/restapi/change/Abandon.java
+++ b/java/com/google/gerrit/server/restapi/change/Abandon.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.restapi.change;
-import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
-
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.gerrit.common.TimeUtil;
@@ -29,6 +27,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.change.AbandonOp;
import com.google.gerrit.server.change.ChangeJson;
@@ -47,14 +46,19 @@
import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@Singleton
public class Abandon extends RetryingRestModifyView<ChangeResource, AbandonInput, ChangeInfo>
implements UiAction<ChangeResource> {
+ private static final Logger log = LoggerFactory.getLogger(Abandon.class);
+
private final Provider<ReviewDb> dbProvider;
private final ChangeJson.Factory json;
private final AbandonOp.Factory abandonOpFactory;
private final NotifyUtil notifyUtil;
+ private final PatchSetUtil patchSetUtil;
@Inject
Abandon(
@@ -62,27 +66,32 @@
ChangeJson.Factory json,
RetryHelper retryHelper,
AbandonOp.Factory abandonOpFactory,
- NotifyUtil notifyUtil) {
+ NotifyUtil notifyUtil,
+ PatchSetUtil patchSetUtil) {
super(retryHelper);
this.dbProvider = dbProvider;
this.json = json;
this.abandonOpFactory = abandonOpFactory;
this.notifyUtil = notifyUtil;
+ this.patchSetUtil = patchSetUtil;
}
@Override
protected ChangeInfo applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource req, AbandonInput input)
+ BatchUpdate.Factory updateFactory, ChangeResource rsrc, AbandonInput input)
throws RestApiException, UpdateException, OrmException, PermissionBackendException,
IOException, ConfigInvalidException {
- req.permissions().database(dbProvider).check(ChangePermission.ABANDON);
+ // Not allowed to abandon if the current patch set is locked.
+ patchSetUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
- NotifyHandling notify = input.notify == null ? defaultNotify(req.getChange()) : input.notify;
+ rsrc.permissions().database(dbProvider).check(ChangePermission.ABANDON);
+
+ NotifyHandling notify = input.notify == null ? defaultNotify(rsrc.getChange()) : input.notify;
Change change =
abandon(
updateFactory,
- req.getNotes(),
- req.getUser(),
+ rsrc.getNotes(),
+ rsrc.getUser(),
input.message,
notify,
notifyUtil.resolveAccounts(input.notifyDetails));
@@ -135,13 +144,29 @@
@Override
public UiAction.Description getDescription(ChangeResource rsrc) {
+ UiAction.Description description =
+ new UiAction.Description()
+ .setLabel("Abandon")
+ .setTitle("Abandon the change")
+ .setVisible(false);
+
Change change = rsrc.getChange();
- return new UiAction.Description()
- .setLabel("Abandon")
- .setTitle("Abandon the change")
- .setVisible(
- and(
- change.getStatus().isOpen(),
- rsrc.permissions().database(dbProvider).testCond(ChangePermission.ABANDON)));
+ if (!change.getStatus().isOpen()) {
+ return description;
+ }
+
+ try {
+ if (patchSetUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+ return description;
+ }
+ } catch (OrmException | IOException e) {
+ log.error(
+ String.format(
+ "Failed to check if the current patch set of change %s is locked", change.getId()),
+ e);
+ return description;
+ }
+
+ return description.setVisible(rsrc.permissions().testOrFalse(ChangePermission.ABANDON));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index 374aaec..ff85880 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -50,7 +50,6 @@
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.project.CommitsCollection;
@@ -126,8 +125,11 @@
@Override
protected Response<ChangeInfo> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, MergePatchSetInput in)
- throws OrmException, IOException, InvalidChangeOperationException, RestApiException,
- UpdateException, PermissionBackendException {
+ throws OrmException, IOException, RestApiException, UpdateException,
+ PermissionBackendException {
+ // Not allowed to create a new patch set if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+
rsrc.permissions().database(db).check(ChangePermission.ADD_PATCH_SET);
ProjectState projectState = projectCache.checkedGet(rsrc.getProject());
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index 2ad954d..6e049438 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -134,6 +134,9 @@
throw new ResourceConflictException("Change is already destined for the specified branch");
}
+ // Not allowed to move if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+
// Move requires abandoning this change, and creating a new change.
try {
rsrc.permissions().database(dbProvider).check(ABANDON);
@@ -279,24 +282,41 @@
@Override
public UiAction.Description getDescription(ChangeResource rsrc) {
+ UiAction.Description description =
+ new UiAction.Description()
+ .setLabel("Move Change")
+ .setTitle("Move change to a different branch")
+ .setVisible(false);
+
Change change = rsrc.getChange();
- boolean projectStatePermitsWrite = false;
+ if (!change.getStatus().isOpen()) {
+ return description;
+ }
+
try {
- projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
+ if (!projectCache.checkedGet(rsrc.getProject()).statePermitsWrite()) {
+ return description;
+ }
} catch (IOException e) {
log.error("Failed to check if project state permits write: " + rsrc.getProject(), e);
+ return description;
}
- return new UiAction.Description()
- .setLabel("Move Change")
- .setTitle("Move change to a different branch")
- .setVisible(
- and(
- change.getStatus().isOpen() && projectStatePermitsWrite,
- and(
- permissionBackend
- .user(rsrc.getUser())
- .ref(change.getDest())
- .testCond(CREATE_CHANGE),
- rsrc.permissions().database(dbProvider).testCond(ABANDON))));
+
+ try {
+ if (psUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+ return description;
+ }
+ } catch (OrmException | IOException e) {
+ log.error(
+ String.format(
+ "Failed to check if the current patch set of change %s is locked", change.getId()),
+ e);
+ return description;
+ }
+
+ return description.setVisible(
+ and(
+ permissionBackend.user(rsrc.getUser()).ref(change.getDest()).testCond(CREATE_CHANGE),
+ rsrc.permissions().database(dbProvider).testCond(ABANDON)));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/PutMessage.java b/java/com/google/gerrit/server/restapi/change/PutMessage.java
index c9c43cb..eb46521 100644
--- a/java/com/google/gerrit/server/restapi/change/PutMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/PutMessage.java
@@ -33,7 +33,6 @@
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.NotifyUtil;
import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.edit.UnchangedCommitMessageException;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -68,7 +67,7 @@
extends RetryingRestModifyView<ChangeResource, CommitMessageInput, Response<?>> {
private final GitRepositoryManager repositoryManager;
- private final Provider<CurrentUser> currentUserProvider;
+ private final Provider<CurrentUser> userProvider;
private final Provider<ReviewDb> db;
private final TimeZone tz;
private final PatchSetInserter.Factory psInserterFactory;
@@ -81,7 +80,7 @@
PutMessage(
RetryHelper retryHelper,
GitRepositoryManager repositoryManager,
- Provider<CurrentUser> currentUserProvider,
+ Provider<CurrentUser> userProvider,
Provider<ReviewDb> db,
PatchSetInserter.Factory psInserterFactory,
PermissionBackend permissionBackend,
@@ -91,7 +90,7 @@
ProjectCache projectCache) {
super(retryHelper);
this.repositoryManager = repositoryManager;
- this.currentUserProvider = currentUserProvider;
+ this.userProvider = userProvider;
this.db = db;
this.psInserterFactory = psInserterFactory;
this.tz = gerritIdent.getTimeZone();
@@ -104,8 +103,8 @@
@Override
protected Response<String> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource resource, CommitMessageInput input)
- throws IOException, UnchangedCommitMessageException, RestApiException, UpdateException,
- PermissionBackendException, OrmException, ConfigInvalidException {
+ throws IOException, RestApiException, UpdateException, PermissionBackendException,
+ OrmException, ConfigInvalidException {
PatchSet ps = psUtil.current(db.get(), resource.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
@@ -140,7 +139,7 @@
Timestamp ts = TimeUtil.nowTs();
try (BatchUpdate bu =
updateFactory.create(
- db.get(), resource.getChange().getProject(), currentUserProvider.get(), ts)) {
+ db.get(), resource.getChange().getProject(), userProvider.get(), ts)) {
// Ensure that BatchUpdate will update the same repo
bu.setRepository(repository, new RevWalk(objectInserter.newReader()), objectInserter);
@@ -170,8 +169,7 @@
builder.setTreeId(basePatchSetCommit.getTree());
builder.setParentIds(basePatchSetCommit.getParents());
builder.setAuthor(basePatchSetCommit.getAuthorIdent());
- builder.setCommitter(
- currentUserProvider.get().asIdentifiedUser().newCommitterIdent(timestamp, tz));
+ builder.setCommitter(userProvider.get().asIdentifiedUser().newCommitterIdent(timestamp, tz));
builder.setMessage(commitMessage);
ObjectId newCommitId = objectInserter.insert(builder);
objectInserter.flush();
@@ -179,13 +177,17 @@
}
private void ensureCanEditCommitMessage(ChangeNotes changeNotes)
- throws AuthException, PermissionBackendException, IOException, ResourceConflictException {
- if (!currentUserProvider.get().isIdentifiedUser()) {
+ throws AuthException, PermissionBackendException, IOException, ResourceConflictException,
+ OrmException {
+ if (!userProvider.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
+
+ // Not allowed to put message if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(changeNotes, userProvider.get());
try {
permissionBackend
- .user(currentUserProvider.get())
+ .user(userProvider.get())
.database(db.get())
.change(changeNotes)
.check(ChangePermission.ADD_PATCH_SET);
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 767ef7b..02e7c18 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -14,16 +14,12 @@
package com.google.gerrit.server.restapi.change;
-import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
-
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RebaseInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -81,6 +77,7 @@
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
+ private final PatchSetUtil patchSetUtil;
@Inject
public Rebase(
@@ -91,7 +88,8 @@
ChangeJson.Factory json,
Provider<ReviewDb> dbProvider,
PermissionBackend permissionBackend,
- ProjectCache projectCache) {
+ ProjectCache projectCache,
+ PatchSetUtil patchSetUtil) {
super(retryHelper);
this.repoManager = repoManager;
this.rebaseFactory = rebaseFactory;
@@ -100,13 +98,17 @@
this.dbProvider = dbProvider;
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
+ this.patchSetUtil = patchSetUtil;
}
@Override
protected ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource rsrc, RebaseInput input)
- throws EmailException, OrmException, UpdateException, RestApiException, IOException,
- NoSuchChangeException, PermissionBackendException {
+ throws OrmException, UpdateException, RestApiException, IOException,
+ PermissionBackendException {
+ // Not allowed to rebase if the current patch set is locked.
+ patchSetUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+
rsrc.permissions().database(dbProvider).check(ChangePermission.REBASE);
projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
@@ -203,40 +205,54 @@
}
@Override
- public UiAction.Description getDescription(RevisionResource resource) {
- PatchSet patchSet = resource.getPatchSet();
- Change change = resource.getChange();
- Branch.NameKey dest = change.getDest();
- boolean visible = change.getStatus().isOpen() && resource.isCurrent();
- boolean enabled = false;
+ public UiAction.Description getDescription(RevisionResource rsrc) {
+ UiAction.Description description =
+ new UiAction.Description()
+ .setLabel("Rebase")
+ .setTitle("Rebase onto tip of branch or parent change")
+ .setVisible(false);
+
+ Change change = rsrc.getChange();
+ if (!(change.getStatus().isOpen() && rsrc.isCurrent())) {
+ return description;
+ }
try {
- visible &= projectCache.checkedGet(resource.getProject()).statePermitsWrite();
- } catch (IOException e) {
- log.error("Failed to check if project state permits write: " + resource.getProject(), e);
- visible = false;
- }
-
- if (visible) {
- try (Repository repo = repoManager.openRepository(dest.getParentKey());
- RevWalk rw = new RevWalk(repo)) {
- visible = hasOneParent(rw, resource.getPatchSet());
- if (visible) {
- enabled = rebaseUtil.canRebase(patchSet, dest, repo, rw);
- }
- } catch (IOException e) {
- log.error("Failed to check if patch set can be rebased: " + resource.getPatchSet(), e);
- visible = false;
+ if (!projectCache.checkedGet(rsrc.getProject()).statePermitsWrite()) {
+ return description;
}
+ } catch (IOException e) {
+ log.error("Failed to check if project state permits write: " + rsrc.getProject(), e);
+ return description;
}
- BooleanCondition permissionCond =
- resource.permissions().database(dbProvider).testCond(ChangePermission.REBASE);
- return new UiAction.Description()
- .setLabel("Rebase")
- .setTitle("Rebase onto tip of branch or parent change")
- .setVisible(and(visible, permissionCond))
- .setEnabled(and(enabled, permissionCond));
+ try {
+ if (patchSetUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+ return description;
+ }
+ } catch (OrmException | IOException e) {
+ log.error(
+ String.format(
+ "Failed to check if the current patch set of change %s is locked", change.getId()),
+ e);
+ return description;
+ }
+
+ boolean enabled = false;
+ try (Repository repo = repoManager.openRepository(change.getDest().getParentKey());
+ RevWalk rw = new RevWalk(repo)) {
+ if (hasOneParent(rw, rsrc.getPatchSet())) {
+ enabled = rebaseUtil.canRebase(rsrc.getPatchSet(), change.getDest(), repo, rw);
+ }
+ } catch (IOException e) {
+ log.error("Failed to check if patch set can be rebased: " + rsrc.getPatchSet(), e);
+ return description;
+ }
+
+ if (rsrc.permissions().database(dbProvider).testOrFalse(ChangePermission.REBASE)) {
+ return description.setVisible(true).setEnabled(enabled);
+ }
+ return description;
}
public static class CurrentRevision
@@ -254,7 +270,7 @@
@Override
protected ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, RebaseInput input)
- throws EmailException, OrmException, UpdateException, RestApiException, IOException,
+ throws OrmException, UpdateException, RestApiException, IOException,
PermissionBackendException {
PatchSet ps = psUtil.current(rebase.dbProvider.get(), rsrc.getNotes());
if (ps == null) {
diff --git a/java/com/google/gerrit/server/restapi/change/Restore.java b/java/com/google/gerrit/server/restapi/change/Restore.java
index 642c35a..d5bfea1 100644
--- a/java/com/google/gerrit/server/restapi/change/Restore.java
+++ b/java/com/google/gerrit/server/restapi/change/Restore.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.restapi.change;
-import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
-
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.RestoreInput;
@@ -90,17 +88,20 @@
@Override
protected ChangeInfo applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource req, RestoreInput input)
+ BatchUpdate.Factory updateFactory, ChangeResource rsrc, RestoreInput input)
throws RestApiException, UpdateException, OrmException, PermissionBackendException,
IOException {
- req.permissions().database(dbProvider).check(ChangePermission.RESTORE);
- projectCache.checkedGet(req.getProject()).checkStatePermitsWrite();
+ // Not allowed to restore if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+
+ rsrc.permissions().database(dbProvider).check(ChangePermission.RESTORE);
+ projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
Op op = new Op(input);
try (BatchUpdate u =
updateFactory.create(
- dbProvider.get(), req.getChange().getProject(), req.getUser(), TimeUtil.nowTs())) {
- u.addOp(req.getId(), op).execute();
+ dbProvider.get(), rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
+ u.addOp(rsrc.getId(), op).execute();
}
return json.noOptions().format(op.change);
}
@@ -161,18 +162,39 @@
@Override
public UiAction.Description getDescription(ChangeResource rsrc) {
- boolean projectStatePermitsWrite = false;
+ UiAction.Description description =
+ new UiAction.Description()
+ .setLabel("Restore")
+ .setTitle("Restore the change")
+ .setVisible(false);
+
+ Change change = rsrc.getChange();
+ if (change.getStatus() != Status.ABANDONED) {
+ return description;
+ }
+
try {
- projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
+ if (!projectCache.checkedGet(rsrc.getProject()).statePermitsWrite()) {
+ return description;
+ }
} catch (IOException e) {
log.error("Failed to check if project state permits write: " + rsrc.getProject(), e);
+ return description;
}
- return new UiAction.Description()
- .setLabel("Restore")
- .setTitle("Restore the change")
- .setVisible(
- and(
- rsrc.getChange().getStatus() == Status.ABANDONED && projectStatePermitsWrite,
- rsrc.permissions().database(dbProvider).testCond(ChangePermission.RESTORE)));
+
+ try {
+ if (psUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+ return description;
+ }
+ } catch (OrmException | IOException e) {
+ log.error(
+ String.format(
+ "Failed to check if the current patch set of change %s is locked", change.getId()),
+ e);
+ return description;
+ }
+
+ boolean visible = rsrc.permissions().database(dbProvider).testOrFalse(ChangePermission.RESTORE);
+ return description.setVisible(visible);
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewerJson.java b/java/com/google/gerrit/server/restapi/change/ReviewerJson.java
index d74be7d..cfd20c2 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewerJson.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewerJson.java
@@ -80,7 +80,10 @@
ReviewerInfo info =
format(
new ReviewerInfo(rsrc.getReviewerUser().getAccountId().get()),
- permissionBackend.user(rsrc.getReviewerUser()).database(db).change(cd),
+ permissionBackend
+ .absentUser(rsrc.getReviewerUser().getAccountId())
+ .database(db)
+ .change(cd),
cd);
loader.put(info);
infos.add(info);
diff --git a/java/com/google/gerrit/server/restapi/config/ListTasks.java b/java/com/google/gerrit/server/restapi/config/ListTasks.java
index fb2819c..d700028 100644
--- a/java/com/google/gerrit/server/restapi/config/ListTasks.java
+++ b/java/com/google/gerrit/server/restapi/config/ListTasks.java
@@ -20,7 +20,6 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.git.TaskInfoFactory;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.ProjectTask;
import com.google.gerrit.server.git.WorkQueue.Task;
@@ -107,14 +106,7 @@
}
private List<TaskInfo> getTasks() {
- List<TaskInfo> taskInfos =
- workQueue.getTaskInfos(
- new TaskInfoFactory<TaskInfo>() {
- @Override
- public TaskInfo getTaskInfo(Task<?> task) {
- return new TaskInfo(task);
- }
- });
+ List<TaskInfo> taskInfos = workQueue.getTaskInfos(TaskInfo::new);
Collections.sort(
taskInfos,
new Comparator<TaskInfo>() {
diff --git a/java/com/google/gerrit/server/rules/PrologEnvironment.java b/java/com/google/gerrit/server/rules/PrologEnvironment.java
index 170ff23..9dd0b86 100644
--- a/java/com/google/gerrit/server/rules/PrologEnvironment.java
+++ b/java/com/google/gerrit/server/rules/PrologEnvironment.java
@@ -16,6 +16,7 @@
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchListCache;
@@ -175,6 +176,7 @@
private final Provider<AnonymousUser> anonymousUser;
private final int reductionLimit;
private final int compileLimit;
+ private final PatchSetUtil patchsetUtil;
@Inject
Args(
@@ -185,7 +187,8 @@
PatchSetInfoFactory patchSetInfoFactory,
IdentifiedUser.GenericFactory userFactory,
Provider<AnonymousUser> anonymousUser,
- @GerritServerConfig Config config) {
+ @GerritServerConfig Config config,
+ PatchSetUtil patchsetUtil) {
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
this.repositoryManager = repositoryManager;
@@ -193,6 +196,7 @@
this.patchSetInfoFactory = patchSetInfoFactory;
this.userFactory = userFactory;
this.anonymousUser = anonymousUser;
+ this.patchsetUtil = patchsetUtil;
int limit = config.getInt("rules", null, "reductionLimit", 100000);
reductionLimit = limit <= 0 ? Integer.MAX_VALUE : limit;
@@ -240,5 +244,9 @@
public AnonymousUser getAnonymousUser() {
return anonymousUser.get();
}
+
+ public PatchSetUtil getPatchsetUtil() {
+ return patchsetUtil;
+ }
}
}
diff --git a/java/com/google/gerrit/server/rules/PrologModule.java b/java/com/google/gerrit/server/rules/PrologModule.java
index e6de229..37dbba6 100644
--- a/java/com/google/gerrit/server/rules/PrologModule.java
+++ b/java/com/google/gerrit/server/rules/PrologModule.java
@@ -22,6 +22,7 @@
@Override
protected void configure() {
install(new EnvironmentModule());
+ install(new RulesCache.Module());
bind(PrologEnvironment.Args.class);
factory(PrologRuleEvaluator.Factory.class);
diff --git a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
index f2d3c19..0cc907d 100644
--- a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
+++ b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
@@ -40,6 +40,7 @@
import com.googlecode.prolog_cafe.lang.IntegerTerm;
import com.googlecode.prolog_cafe.lang.ListTerm;
import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
import com.googlecode.prolog_cafe.lang.StructureTerm;
import com.googlecode.prolog_cafe.lang.SymbolTerm;
import com.googlecode.prolog_cafe.lang.Term;
@@ -79,6 +80,8 @@
private final AccountCache accountCache;
private final Accounts accounts;
private final Emails emails;
+ private final RulesCache rulesCache;
+ private final PrologEnvironment.Factory envFactory;
private final ChangeData cd;
private final ProjectState projectState;
private final SubmitRuleOptions opts;
@@ -89,12 +92,16 @@
AccountCache accountCache,
Accounts accounts,
Emails emails,
+ RulesCache rulesCache,
+ PrologEnvironment.Factory envFactory,
ProjectCache projectCache,
@Assisted ChangeData cd,
@Assisted SubmitRuleOptions options) {
this.accountCache = accountCache;
this.accounts = accounts;
this.emails = emails;
+ this.rulesCache = rulesCache;
+ this.envFactory = envFactory;
this.cd = cd;
this.opts = options;
@@ -440,11 +447,15 @@
private PrologEnvironment getPrologEnvironment() throws RuleEvalException {
PrologEnvironment env;
try {
+ PrologMachineCopy pmc;
if (opts.rule() == null) {
- env = projectState.newPrologEnvironment();
+ pmc =
+ rulesCache.loadMachine(
+ projectState.getNameKey(), projectState.getConfig().getRulesId());
} else {
- env = projectState.newPrologEnvironment("stdin", new StringReader(opts.rule()));
+ pmc = rulesCache.loadMachine("stdin", new StringReader(opts.rule()));
}
+ env = envFactory.create(pmc);
} catch (CompileException err) {
String msg;
if (opts.rule() == null) {
@@ -477,7 +488,10 @@
for (ProjectState parentState : projectState.parents()) {
PrologEnvironment parentEnv;
try {
- parentEnv = parentState.newPrologEnvironment();
+ parentEnv =
+ envFactory.create(
+ rulesCache.loadMachine(
+ parentState.getNameKey(), parentState.getConfig().getRulesId()));
} catch (CompileException err) {
throw new RuleEvalException("Cannot consult rules.pl for " + parentState.getName(), err);
}
diff --git a/java/com/google/gerrit/server/rules/RulesCache.java b/java/com/google/gerrit/server/rules/RulesCache.java
index 6ef11fa..c26975b 100644
--- a/java/com/google/gerrit/server/rules/RulesCache.java
+++ b/java/com/google/gerrit/server/rules/RulesCache.java
@@ -17,15 +17,19 @@
import static com.googlecode.prolog_cafe.lang.PrologMachineCopy.save;
import com.google.common.base.Joiner;
+import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCacheImpl;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.google.inject.name.Named;
import com.googlecode.prolog_cafe.exceptions.CompileException;
import com.googlecode.prolog_cafe.exceptions.SyntaxException;
import com.googlecode.prolog_cafe.exceptions.TermException;
@@ -42,9 +46,6 @@
import java.io.PushbackReader;
import java.io.Reader;
import java.io.StringReader;
-import java.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -52,9 +53,8 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
+import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
@@ -71,17 +71,19 @@
*/
@Singleton
public class RulesCache {
+ public static class Module extends CacheModule {
+ @Override
+ protected void configure() {
+ cache(RulesCache.CACHE_NAME, ObjectId.class, PrologMachineCopy.class)
+ // This cache is auxiliary to the project cache, so size it the same.
+ .configKey(ProjectCacheImpl.CACHE_NAME);
+ }
+ }
+
private static final ImmutableList<String> PACKAGE_LIST =
ImmutableList.of(Prolog.BUILTIN, "gerrit");
- private static final class MachineRef extends WeakReference<PrologMachineCopy> {
- final ObjectId key;
-
- MachineRef(ObjectId key, PrologMachineCopy pcm, ReferenceQueue<PrologMachineCopy> queue) {
- super(pcm, queue);
- this.key = key;
- }
- }
+ static final String CACHE_NAME = "prolog_rules";
private final boolean enableProjectRules;
private final int maxDbSize;
@@ -92,15 +94,15 @@
private final DynamicSet<PredicateProvider> predicateProviders;
private final ClassLoader systemLoader;
private final PrologMachineCopy defaultMachine;
- private final Map<ObjectId, MachineRef> machineCache = new HashMap<>();
- private final ReferenceQueue<PrologMachineCopy> dead = new ReferenceQueue<>();
+ private final Cache<ObjectId, PrologMachineCopy> machineCache;
@Inject
protected RulesCache(
@GerritServerConfig Config config,
SitePaths site,
GitRepositoryManager gm,
- DynamicSet<PredicateProvider> predicateProviders) {
+ DynamicSet<PredicateProvider> predicateProviders,
+ @Named(CACHE_NAME) Cache<ObjectId, PrologMachineCopy> machineCache) {
maxDbSize = config.getInt("rules", null, "maxPrologDatabaseSize", 256);
maxSrcBytes = config.getInt("rules", null, "maxSourceBytes", 128 << 10);
enableProjectRules = config.getBoolean("rules", null, "enable", true) && maxSrcBytes > 0;
@@ -108,6 +110,7 @@
rulesDir = cacheDir != null ? cacheDir.resolve("rules") : null;
gitMgr = gm;
this.predicateProviders = predicateProviders;
+ this.machineCache = machineCache;
systemLoader = getClass().getClassLoader();
defaultMachine = save(newEmptyMachine(systemLoader));
@@ -129,23 +132,14 @@
return defaultMachine;
}
- Reference<? extends PrologMachineCopy> ref = machineCache.get(rulesId);
- if (ref != null) {
- PrologMachineCopy pmc = ref.get();
- if (pmc != null) {
- return pmc;
+ try {
+ return machineCache.get(rulesId, () -> createMachine(project, rulesId));
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof CompileException) {
+ throw new CompileException(e.getCause().getMessage(), e);
}
-
- machineCache.remove(rulesId);
- ref.enqueue();
+ throw new CompileException("Error while consulting rules from " + project, e);
}
-
- gc();
-
- PrologMachineCopy pcm = createMachine(project, rulesId);
- MachineRef newRef = new MachineRef(rulesId, pcm, dead);
- machineCache.put(rulesId, newRef);
- return pcm;
}
public PrologMachineCopy loadMachine(String name, Reader in) throws CompileException {
@@ -156,16 +150,6 @@
return pmc;
}
- private void gc() {
- Reference<?> ref;
- while ((ref = dead.poll()) != null) {
- ObjectId key = ((MachineRef) ref).key;
- if (machineCache.get(key) == ref) {
- machineCache.remove(key);
- }
- }
- }
-
private PrologMachineCopy createMachine(Project.NameKey project, ObjectId rulesId)
throws CompileException {
// If the rules are available as a complied JAR on local disk, prefer
diff --git a/java/com/google/gerrit/server/rules/StoredValues.java b/java/com/google/gerrit/server/rules/StoredValues.java
index 9fc3557..6770732 100644
--- a/java/com/google/gerrit/server/rules/StoredValues.java
+++ b/java/com/google/gerrit/server/rules/StoredValues.java
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.Emails;
@@ -89,6 +90,22 @@
}
};
+ public static final StoredValue<String> COMMIT_MESSAGE =
+ new StoredValue<String>() {
+ @Override
+ public String createValue(Prolog engine) {
+ Change change = getChange(engine);
+ PatchSet ps = getPatchSet(engine);
+ PrologEnvironment env = (PrologEnvironment) engine.control;
+ PatchSetUtil patchSetUtil = env.getArgs().getPatchsetUtil();
+ try {
+ return patchSetUtil.getFullCommitMessage(change.getProject(), ps);
+ } catch (IOException e) {
+ throw new SystemException(e.getMessage());
+ }
+ }
+ };
+
public static final StoredValue<PatchList> PATCH_LIST =
new StoredValue<PatchList>() {
@Override
diff --git a/java/com/google/gerrit/server/submit/SubmoduleOp.java b/java/com/google/gerrit/server/submit/SubmoduleOp.java
index d09615f..22d463d 100644
--- a/java/com/google/gerrit/server/submit/SubmoduleOp.java
+++ b/java/com/google/gerrit/server/submit/SubmoduleOp.java
@@ -38,6 +38,7 @@
import com.google.gerrit.server.update.RepoOnlyOp;
import com.google.gerrit.server.update.UpdateException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayDeque;
@@ -95,7 +96,7 @@
@Singleton
public static class Factory {
private final GitModules.Factory gitmodulesFactory;
- private final PersonIdent myIdent;
+ private final Provider<PersonIdent> serverIdent;
private final Config cfg;
private final ProjectCache projectCache;
private final BatchUpdate.Factory batchUpdateFactory;
@@ -103,12 +104,12 @@
@Inject
Factory(
GitModules.Factory gitmodulesFactory,
- @GerritPersonIdent PersonIdent myIdent,
+ @GerritPersonIdent Provider<PersonIdent> serverIdent,
@GerritServerConfig Config cfg,
ProjectCache projectCache,
BatchUpdate.Factory batchUpdateFactory) {
this.gitmodulesFactory = gitmodulesFactory;
- this.myIdent = myIdent;
+ this.serverIdent = serverIdent;
this.cfg = cfg;
this.projectCache = projectCache;
this.batchUpdateFactory = batchUpdateFactory;
@@ -117,7 +118,13 @@
public SubmoduleOp create(Set<Branch.NameKey> updatedBranches, MergeOpRepoManager orm)
throws SubmoduleException {
return new SubmoduleOp(
- gitmodulesFactory, myIdent, cfg, projectCache, batchUpdateFactory, updatedBranches, orm);
+ gitmodulesFactory,
+ serverIdent.get(),
+ cfg,
+ projectCache,
+ batchUpdateFactory,
+ updatedBranches,
+ orm);
}
}
diff --git a/java/com/google/gerrit/sshd/AliasCommand.java b/java/com/google/gerrit/sshd/AliasCommand.java
index 0ac7765..cb61651 100644
--- a/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/java/com/google/gerrit/sshd/AliasCommand.java
@@ -18,7 +18,6 @@
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -33,7 +32,6 @@
/** Command that executes some other command. */
public class AliasCommand extends BaseCommand {
private final DispatchCommandProvider root;
- private final CurrentUser currentUser;
private final PermissionBackend permissionBackend;
private final CommandName command;
private final AtomicReference<Command> atomicCmd;
@@ -41,11 +39,9 @@
AliasCommand(
@CommandName(Commands.ROOT) DispatchCommandProvider root,
PermissionBackend permissionBackend,
- CurrentUser currentUser,
CommandName command) {
this.root = root;
this.permissionBackend = permissionBackend;
- this.currentUser = currentUser;
this.command = command;
this.atomicCmd = Atomics.newReference();
}
@@ -114,7 +110,7 @@
try {
Set<GlobalOrPluginPermission> check = GlobalPermission.fromAnnotation(cmd.getClass());
try {
- permissionBackend.user(currentUser).checkAny(check);
+ permissionBackend.currentUser().checkAny(check);
} catch (AuthException err) {
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, "fatal: " + err.getMessage());
}
diff --git a/java/com/google/gerrit/sshd/AliasCommandProvider.java b/java/com/google/gerrit/sshd/AliasCommandProvider.java
index 0ef0473..085b6d6 100644
--- a/java/com/google/gerrit/sshd/AliasCommandProvider.java
+++ b/java/com/google/gerrit/sshd/AliasCommandProvider.java
@@ -14,7 +14,6 @@
package com.google.gerrit.sshd;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -29,7 +28,6 @@
private DispatchCommandProvider root;
@Inject private PermissionBackend permissionBackend;
- @Inject private CurrentUser currentUser;
public AliasCommandProvider(CommandName command) {
this.command = command;
@@ -37,6 +35,6 @@
@Override
public Command get() {
- return new AliasCommand(root, permissionBackend, currentUser, command);
+ return new AliasCommand(root, permissionBackend, command);
}
}
diff --git a/java/com/google/gerrit/sshd/BaseCommand.java b/java/com/google/gerrit/sshd/BaseCommand.java
index cbe1625..3da8b5c 100644
--- a/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/java/com/google/gerrit/sshd/BaseCommand.java
@@ -172,7 +172,7 @@
String arg = argv[i];
int indexOfMultiLine = arg.indexOf("\n");
if (indexOfMultiLine > -1) {
- arg = arg.substring(0, indexOfMultiLine).concat(" [trimmed]");
+ arg = arg.substring(0, indexOfMultiLine) + " [trimmed]";
}
trimmedArgv[i] = arg;
}
diff --git a/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index 39ee13a..9fc4cda 100644
--- a/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -20,7 +20,6 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeFinder;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -39,7 +38,6 @@
import java.util.Map;
public class ChangeArgumentParser {
- private final CurrentUser currentUser;
private final ChangesCollection changesCollection;
private final ChangeFinder changeFinder;
private final ReviewDb db;
@@ -48,13 +46,11 @@
@Inject
ChangeArgumentParser(
- CurrentUser currentUser,
ChangesCollection changesCollection,
ChangeFinder changeFinder,
ReviewDb db,
ChangeNotes.Factory changeNotesFactory,
PermissionBackend permissionBackend) {
- this.currentUser = currentUser;
this.changesCollection = changesCollection;
this.changeFinder = changeFinder;
this.db = db;
@@ -83,7 +79,7 @@
List<ChangeNotes> toAdd = new ArrayList<>(changes.size());
boolean canMaintainServer;
try {
- permissionBackend.user(currentUser).check(GlobalPermission.MAINTAIN_SERVER);
+ permissionBackend.currentUser().check(GlobalPermission.MAINTAIN_SERVER);
canMaintainServer = true;
} catch (AuthException | PermissionBackendException e) {
canMaintainServer = false;
@@ -93,7 +89,7 @@
&& inProject(projectState, notes.getProjectName())
&& (canMaintainServer
|| (permissionBackend
- .user(currentUser)
+ .currentUser()
.change(notes)
.database(db)
.test(ChangePermission.READ)
diff --git a/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java b/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
index f729b931..59185bf 100644
--- a/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
+++ b/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
@@ -19,9 +19,11 @@
import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.eclipse.jgit.lib.Config;
+@Singleton
public class CommandExecutorQueueProvider implements QueueProvider {
private int poolSize;
diff --git a/java/com/google/gerrit/sshd/DispatchCommand.java b/java/com/google/gerrit/sshd/DispatchCommand.java
index 3f2e258..490dd52 100644
--- a/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -19,7 +19,6 @@
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.args4j.SubcommandHandler;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -42,7 +41,6 @@
DispatchCommand create(Map<String, CommandProvider> map);
}
- private final CurrentUser currentUser;
private final PermissionBackend permissionBackend;
private final Map<String, CommandProvider> commands;
private final AtomicReference<Command> atomicCmd;
@@ -54,11 +52,7 @@
private List<String> args = new ArrayList<>();
@Inject
- DispatchCommand(
- CurrentUser user,
- PermissionBackend permissionBackend,
- @Assisted Map<String, CommandProvider> all) {
- this.currentUser = user;
+ DispatchCommand(PermissionBackend permissionBackend, @Assisted Map<String, CommandProvider> all) {
this.permissionBackend = permissionBackend;
commands = all;
atomicCmd = Atomics.newReference();
@@ -125,7 +119,7 @@
}
try {
permissionBackend
- .user(currentUser)
+ .currentUser()
.checkAny(GlobalPermission.fromAnnotation(pluginName, cmd.getClass()));
} catch (AuthException e) {
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, e.getMessage());
diff --git a/java/com/google/gerrit/sshd/SshDaemon.java b/java/com/google/gerrit/sshd/SshDaemon.java
index 8f50601a..e61919a 100644
--- a/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/java/com/google/gerrit/sshd/SshDaemon.java
@@ -20,7 +20,6 @@
import static org.apache.sshd.common.channel.ChannelOutputStream.WAIT_FOR_SPACE_TIMEOUT;
import com.google.common.base.Strings;
-import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.Version;
import com.google.gerrit.extensions.events.LifecycleListener;
@@ -242,12 +241,7 @@
"sshd/sessions/connected",
Integer.class,
new Description("Currently connected SSH sessions").setGauge().setUnit("sessions"),
- new Supplier<Integer>() {
- @Override
- public Integer get() {
- return connected.get();
- }
- });
+ connected::get);
final Counter0 sessionsCreated =
metricMaker.newCounter(
diff --git a/java/com/google/gerrit/sshd/SshModule.java b/java/com/google/gerrit/sshd/SshModule.java
index 27a431c..f047017 100644
--- a/java/com/google/gerrit/sshd/SshModule.java
+++ b/java/com/google/gerrit/sshd/SshModule.java
@@ -89,7 +89,7 @@
.annotatedWith(StreamCommandExecutor.class)
.toProvider(StreamCommandExecutorProvider.class)
.in(SINGLETON);
- bind(QueueProvider.class).to(CommandExecutorQueueProvider.class).in(SINGLETON);
+ bind(QueueProvider.class).to(CommandExecutorQueueProvider.class);
bind(GSSAuthenticator.class).to(GerritGSSAuthenticator.class);
bind(PublickeyAuthenticator.class).to(CachingPublicKeyAuthenticator.class);
diff --git a/java/com/google/gerrit/sshd/SshUtil.java b/java/com/google/gerrit/sshd/SshUtil.java
index b37ca8b..ce35422 100644
--- a/java/com/google/gerrit/sshd/SshUtil.java
+++ b/java/com/google/gerrit/sshd/SshUtil.java
@@ -108,9 +108,7 @@
strBuf.append(' ');
strBuf.append("converted-key");
return strBuf.toString();
- } catch (IOException e) {
- return keyStr;
- } catch (RuntimeException re) {
+ } catch (IOException | RuntimeException e) {
return keyStr;
}
}
diff --git a/java/com/google/gerrit/sshd/commands/AdminQueryShell.java b/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
index ef1cd81..c520e79 100644
--- a/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
+++ b/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
@@ -17,7 +17,6 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -34,7 +33,6 @@
final class AdminQueryShell extends SshCommand {
@Inject private PermissionBackend permissionBackend;
@Inject private QueryShell.Factory factory;
- @Inject private IdentifiedUser currentUser;
@Option(name = "--format", usage = "Set output format")
private QueryShell.OutputFormat format = QueryShell.OutputFormat.PRETTY;
@@ -45,7 +43,7 @@
@Override
protected void run() throws Failure {
try {
- permissionBackend.user(currentUser).check(GlobalPermission.ACCESS_DATABASE);
+ permissionBackend.currentUser().check(GlobalPermission.ACCESS_DATABASE);
} catch (AuthException err) {
throw die(err.getMessage());
} catch (PermissionBackendException e) {
diff --git a/java/com/google/gerrit/sshd/commands/StreamEvents.java b/java/com/google/gerrit/sshd/commands/StreamEvents.java
index db9f519..9e8e85e 100644
--- a/java/com/google/gerrit/sshd/commands/StreamEvents.java
+++ b/java/com/google/gerrit/sshd/commands/StreamEvents.java
@@ -110,7 +110,7 @@
StringBuilder b = new StringBuilder();
b.append("Stream Events");
if (currentUser.getUserName().isPresent()) {
- b.append(" (" + currentUser.getUserName().get() + ")");
+ b.append(" (").append(currentUser.getUserName().get()).append(")");
}
return b.toString();
}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index b63830e..7d79829 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -59,6 +59,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.PerThreadRequestScope;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
+import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
import com.google.gerrit.server.index.account.AllAccountsIndexer;
@@ -275,8 +276,8 @@
@Provides
@Singleton
@FanOutExecutor
- public ExecutorService createChangeJsonExecutor() {
- return MoreExecutors.newDirectExecutorService();
+ public ExecutorService createFanOutExecutor(WorkQueue queues) {
+ return queues.createQueue(2, "FanOut");
}
@Provides
diff --git a/java/com/google/gwtexpui/globalkey/client/GlobalKey.java b/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
index 3eac789..cbaca61 100644
--- a/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
+++ b/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
@@ -14,6 +14,7 @@
package com.google.gwtexpui.globalkey.client;
+import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
@@ -27,13 +28,7 @@
import com.google.gwt.user.client.ui.Widget;
public class GlobalKey {
- public static final KeyPressHandler STOP_PROPAGATION =
- new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent event) {
- event.stopPropagation();
- }
- };
+ public static final KeyPressHandler STOP_PROPAGATION = DomEvent::stopPropagation;
private static State global;
static State active;
diff --git a/java/gerrit/PRED_commit_message_1.java b/java/gerrit/PRED_commit_message_1.java
index c5aa367..05bb4bb 100644
--- a/java/gerrit/PRED_commit_message_1.java
+++ b/java/gerrit/PRED_commit_message_1.java
@@ -14,7 +14,6 @@
package gerrit;
-import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.server.rules.StoredValues;
import com.googlecode.prolog_cafe.exceptions.PrologException;
import com.googlecode.prolog_cafe.lang.Operation;
@@ -41,9 +40,9 @@
engine.setB0();
Term a1 = arg1.dereference();
- PatchSetInfo psInfo = StoredValues.PATCH_SET_INFO.get(engine);
+ String commitMessage = StoredValues.COMMIT_MESSAGE.get(engine);
- SymbolTerm msg = SymbolTerm.create(psInfo.getMessage());
+ SymbolTerm msg = SymbolTerm.create(commitMessage);
if (!a1.unify(msg, engine.trail)) {
return engine.fail();
}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 73fbb08..3c8dba2 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -987,28 +987,24 @@
String email = "foo.bar@example.com";
String extId1 = "foo:bar";
String extId2 = "foo:baz";
-
- // Use ExternalIdNotes to insert two external IDs with the same email.
- // This allows us to verify that deleting the email removes this email from all external IDs.
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes =
- extIdNotesFactory.load(allUsersRepo).setDisableCheckForNewDuplicateEmails(true);
- extIdNotes.insert(ExternalId.createWithEmail(ExternalId.Key.parse(extId1), admin.id, email));
- extIdNotes.insert(ExternalId.createWithEmail(ExternalId.Key.parse(extId2), admin.id, email));
- extIdNotes.commit(md);
- extIdNotes.updateCaches();
- }
+ accountsUpdateProvider
+ .get()
+ .update(
+ "Add External IDs",
+ admin.id,
+ u ->
+ u.addExternalId(
+ ExternalId.createWithEmail(ExternalId.Key.parse(extId1), admin.id, email))
+ .addExternalId(
+ ExternalId.createWithEmail(ExternalId.Key.parse(extId2), admin.id, email)));
+ accountIndexedCounter.assertReindexOf(admin);
assertThat(
gApi.accounts().self().getExternalIds().stream().map(e -> e.identity).collect(toSet()))
.containsAllOf(extId1, extId2);
- externalIds.byEmail(email);
-
resetCurrentApiUser();
assertThat(getEmails()).contains(email);
- accountIndexedCounter.clear();
gApi.accounts().self().deleteEmail(email);
accountIndexedCounter.assertReindexOf(admin);
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index 6fbf11d..62138ca 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -273,7 +273,7 @@
.commit()
.insertChangeId()
.message("subject: adding new subscription")
- .add(".gitmodules", config.toText().toString())
+ .add(".gitmodules", config.toText())
.create();
repo.git()
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index ab4ea40..f031729 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -497,7 +497,7 @@
+ extIdWithInvalidEmail.email()));
ExternalId extIdWithDuplicateEmail = createExternalIdWithDuplicateEmail(nextId(scheme, i));
- insertExtIdWithDuplicateEmail(extIdWithDuplicateEmail);
+ insertExtId(extIdWithDuplicateEmail);
expectedProblems.add(
consistencyError(
"Email '"
@@ -924,18 +924,6 @@
}
}
- private void insertExtIdWithDuplicateEmail(ExternalId extId) throws Exception {
- // Cannot use AccountsUpdate to insert an external ID with duplicate email.
- try (Repository repo = repoManager.openRepository(allUsers);
- MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes =
- externalIdNotesFactory.load(repo).setDisableCheckForNewDuplicateEmails(true);
- extIdNotes.insert(extId);
- extIdNotes.commit(update);
- extIdNotes.updateCaches();
- }
- }
-
private void insertExtIdBehindGerritsBack(ExternalId extId) throws Exception {
try (Repository repo = repoManager.openRepository(allUsers)) {
// Inserting an external ID "behind Gerrit's back" means that the caches are not updated.
@@ -952,10 +940,7 @@
private void addExtId(TestRepository<?> testRepo, ExternalId... extIds)
throws IOException, OrmDuplicateKeyException, ConfigInvalidException {
- ExternalIdNotes extIdNotes =
- externalIdNotesFactory
- .load(testRepo.getRepository())
- .setDisableCheckForNewDuplicateEmails(true);
+ ExternalIdNotes extIdNotes = externalIdNotesFactory.load(testRepo.getRepository());
extIdNotes.insert(Arrays.asList(extIds));
try (MetaDataUpdate metaDataUpdate =
new MetaDataUpdate(GitReferenceUpdated.DISABLED, null, testRepo.getRepository())) {
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdNotesIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdNotesIT.java
deleted file mode 100644
index 60535c0..0000000
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdNotesIT.java
+++ /dev/null
@@ -1,287 +0,0 @@
-// Copyright (C) 2018 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.acceptance.rest.account;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
-import static org.hamcrest.CoreMatchers.instanceOf;
-
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdNotes;
-import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.git.meta.MetaDataUpdate;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.util.Optional;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Test;
-
-public class ExternalIdNotesIT extends AbstractDaemonTest {
- @Inject private ExternalIds externalIds;
- @Inject private ExternalIdNotes.Factory externalIdNotesFactory;
-
- @Test
- public void cannotAddExternalIdsWithSameEmail() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "xyz", user.id, email, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.insert(extId1);
- extIdNotes.insert(extId2);
-
- expectException(
- "cannot assign email "
- + email
- + " to multiple external IDs: ["
- + extId1.key().get()
- + ", "
- + extId2.key().get()
- + "]");
- extIdNotes.commit(md);
- }
- }
-
- @Test
- public void cannotAddExternalIdWithEmailThatIsAlreadyUsed() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.insert(extId1);
- extIdNotes.commit(md);
- }
-
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "xyz", user.id, email, null);
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.insert(extId2);
-
- expectException(
- "cannot assign email "
- + email
- + " to external ID(s) ["
- + extId2.key().get()
- + "],"
- + " it is already assigned to external ID(s) ["
- + extId1.key().get()
- + "]");
- extIdNotes.commit(md);
- }
- }
-
- @Test
- public void canAssignExistingEmailToNewExternalId() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.insert(extId1);
- extIdNotes.commit(md);
- }
-
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "xyz", user.id, email, null);
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.upsert(ExternalId.create(extId1.key(), user.id, null, null));
- extIdNotes.insert(extId2);
- extIdNotes.commit(md);
- }
-
- assertExternalIdWithoutEmail(extId1.key());
- assertExternalId(extId2.key(), email);
- }
-
- @Test
- public void canAssignExistingEmailToDifferentExternalId() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "xyz", user.id, null, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.insert(extId1);
- extIdNotes.insert(extId2);
- extIdNotes.commit(md);
- }
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.upsert(ExternalId.create(extId1.key(), user.id, null, null));
- extIdNotes.upsert(ExternalId.create(extId2.key(), user.id, email, null));
- extIdNotes.commit(md);
- }
-
- assertExternalIdWithoutEmail(extId1.key());
- assertExternalId(extId2.key(), email);
- }
-
- @Test
- public void cannotAssignExistingEmailToMultipleNewExternalIds() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.insert(extId1);
- extIdNotes.commit(md);
- }
-
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "efg", user.id, email, null);
- ExternalId extId3 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "hij", user.id, email, null);
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.upsert(ExternalId.create(extId1.key(), user.id, null, null));
- extIdNotes.insert(extId2);
- extIdNotes.insert(extId3);
-
- expectException(
- "cannot assign email "
- + email
- + " to multiple external IDs: ["
- + extId2.key().get()
- + ", "
- + extId3.key().get()
- + "]");
- extIdNotes.commit(md);
- }
- }
-
- @Test
- public void canUpdateExternalIdsIfDuplicateEmailsAlreadyExist() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "efg", user.id, email, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes =
- externalIdNotesFactory.load(allUsersRepo).setDisableCheckForNewDuplicateEmails(true);
- extIdNotes.insert(extId1);
- extIdNotes.insert(extId2);
- extIdNotes.commit(md);
- }
-
- String email2 = "bar@example.com";
- ExternalId extId3 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "hij", user.id, email2, null);
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.upsert(ExternalId.create(extId1.key(), admin.id, email, null));
- extIdNotes.insert(extId3);
- extIdNotes.commit(md);
- }
-
- assertExternalId(extId1.key(), email);
- assertExternalId(extId2.key(), email);
- assertExternalId(extId3.key(), email2);
- }
-
- @Test
- public void cannotAddExistingDuplicateEmailToAnotherExternalId() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "efg", user.id, email, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes =
- externalIdNotesFactory.load(allUsersRepo).setDisableCheckForNewDuplicateEmails(true);
- extIdNotes.insert(extId1);
- extIdNotes.insert(extId2);
- extIdNotes.commit(md);
- }
-
- ExternalId extId3 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "hij", user.id, email, null);
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.insert(extId3);
-
- expectException(
- "cannot assign email "
- + email
- + " to external ID(s) ["
- + extId3.key().get()
- + "],"
- + " it is already assigned to external ID(s) ["
- + extId1.key().get()
- + ", "
- + extId2.key().get()
- + "]");
- extIdNotes.commit(md);
- }
- }
-
- @Test
- public void canRemoveExistingDuplicateEmail() throws Exception {
- String email = "foo@example.com";
- ExternalId extId1 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "abc", user.id, email, null);
- ExternalId extId2 = ExternalId.create(ExternalId.SCHEME_EXTERNAL, "efg", user.id, email, null);
-
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes =
- externalIdNotesFactory.load(allUsersRepo).setDisableCheckForNewDuplicateEmails(true);
- extIdNotes.insert(extId1);
- extIdNotes.insert(extId2);
- extIdNotes.commit(md);
- }
-
- String email2 = "bar@example.com";
- try (Repository allUsersRepo = repoManager.openRepository(allUsers);
- MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
- ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
- extIdNotes.upsert(ExternalId.create(extId2.key(), user.id, email2, null));
- extIdNotes.commit(md);
- }
-
- assertExternalId(extId1.key(), email);
- assertExternalId(extId2.key(), email2);
- }
-
- private void assertExternalIdWithoutEmail(ExternalId.Key extIdKey) throws Exception {
- assertExternalId(extIdKey, null);
- }
-
- private void assertExternalId(ExternalId.Key extIdKey, @Nullable String expectedEmail)
- throws Exception {
- Optional<ExternalId> extId = externalIds.get(extIdKey);
- assertThat(extId).named(extIdKey.get()).isPresent();
- assertThat(extId.get().email()).named("email of " + extIdKey.get()).isEqualTo(expectedEmail);
- }
-
- private void expectException(String message) {
- exception.expect(IOException.class);
- exception.expectCause(instanceOf(ConfigInvalidException.class));
- exception.expectMessage("Ambiguous emails:");
- exception.expectMessage(message);
- }
-}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
index ee5d3b0..129d98a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -53,6 +53,7 @@
import com.google.gson.stream.JsonReader;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -839,9 +840,7 @@
private static void assertReviewers(
ChangeInfo c, ReviewerState reviewerState, TestAccount... accounts) throws Exception {
List<TestAccount> accountList = new ArrayList<>(accounts.length);
- for (TestAccount a : accounts) {
- accountList.add(a);
- }
+ Collections.addAll(accountList, accounts);
assertReviewers(c, reviewerState, accountList);
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
index 93ad2fe..be0879a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
@@ -229,8 +229,9 @@
grant(project, "refs/heads/*", Permission.LABEL + "Patch-Set-Lock");
revision(r).review(new ReviewInput().label("Patch-Set-Lock", 1));
- exception.expect(AuthException.class);
- exception.expectMessage("move not permitted");
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage(
+ String.format("The current patch set of change %s is locked", r.getChange().getId()));
move(r.getChangeId(), newBranch.get());
}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
index 27dc951..a5d78c6 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
@@ -333,7 +333,7 @@
@Test
public void enableSequencesNoGap() throws Exception {
- testEnableSequences(0, 2, "12");
+ testEnableSequences(0, 3, "13");
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
new file mode 100644
index 0000000..a4d9acb
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/rules/RulesIT.java
@@ -0,0 +1,123 @@
+// Copyright (C) 2018 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.acceptance.server.rules;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.project.SubmitRuleEvaluator;
+import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+import java.util.Collection;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests the Prolog rules to make sure they work even when the change and account indexes are not
+ * available.
+ */
+@NoHttpd
+public class RulesIT extends AbstractDaemonTest {
+ private static final String RULE_TEMPLATE =
+ "submit_rule(submit(W)) :- \n" + "%s,\n" + "W = label('OK', ok(user(1000000))).";
+
+ @Inject private SubmitRuleEvaluator.Factory evaluatorFactory;
+
+ @Before
+ public void setUp() {
+ // We don't want caches to interfere with our tests. If we didn't, the cache would take
+ // precedence over the index, which would never be called.
+ baseConfig.setString("cache", "changes", "memoryLimit", "0");
+ baseConfig.setString("cache", "projects", "memoryLimit", "0");
+ }
+
+ @Test
+ public void testUnresolvedCommentsCountPredicate() throws Exception {
+ modifySubmitRules("gerrit:unresolved_comments_count(0)");
+ assertThat(statusForRule()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testUploaderPredicate() throws Exception {
+ modifySubmitRules("gerrit:uploader(U)");
+ assertThat(statusForRule()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testUnresolvedCommentsCount() throws Exception {
+ modifySubmitRules("gerrit:commit_message_matches('.*')");
+ assertThat(statusForRule()).isEqualTo(SubmitRecord.Status.OK);
+ }
+
+ @Test
+ public void testUserPredicate() throws Exception {
+ // This test results in a RULE_ERROR as Prolog tries to find accounts by email, using the index.
+ // TODO(maximeg) get OK results
+ modifySubmitRules("commit_author(user(1000000), 'John Doe', 'john.doe@example.com')");
+ assertThat(statusForRule()).isEqualTo(SubmitRecord.Status.RULE_ERROR);
+ }
+
+ @Test
+ public void testCommitAuthorPredicate() throws Exception {
+ // This test results in a RULE_ERROR as Prolog tries to find accounts by email, using the index.
+ // TODO(maximeg) get OK results
+ modifySubmitRules("gerrit:commit_author(Id)");
+ assertThat(statusForRule()).isEqualTo(SubmitRecord.Status.RULE_ERROR);
+ }
+
+ private SubmitRecord.Status statusForRule() throws Exception {
+ String oldHead = getRemoteHead().name();
+ PushOneCommit.Result result1 =
+ pushFactory.create(db, user.getIdent(), testRepo).to("refs/for/master");
+ testRepo.reset(oldHead);
+ ChangeData cd = result1.getChange();
+
+ Collection<SubmitRecord> records;
+ try (AutoCloseable changeIndex = disableChangeIndex()) {
+ try (AutoCloseable accountIndex = disableAccountIndex()) {
+ SubmitRuleEvaluator ruleEvaluator = evaluatorFactory.create(SubmitRuleOptions.defaults());
+ records = ruleEvaluator.evaluate(cd);
+ }
+ }
+
+ assertThat(records).hasSize(1);
+ SubmitRecord record = records.iterator().next();
+ return record.status;
+ }
+
+ private void modifySubmitRules(String ruleTested) throws Exception {
+ String newContent = String.format(RULE_TEMPLATE, ruleTested);
+
+ try (Repository repo = repoManager.openRepository(project)) {
+ TestRepository<?> testRepo = new TestRepository<>((InMemoryRepository) repo);
+ testRepo
+ .branch(RefNames.REFS_CONFIG)
+ .commit()
+ .author(admin.getIdent())
+ .committer(admin.getIdent())
+ .add("rules.pl", newContent)
+ .message("Modify rules.pl")
+ .create();
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/common/data/SubmitRecordTest.java b/javatests/com/google/gerrit/common/data/SubmitRecordTest.java
index 6a90f45..5386b87 100644
--- a/javatests/com/google/gerrit/common/data/SubmitRecordTest.java
+++ b/javatests/com/google/gerrit/common/data/SubmitRecordTest.java
@@ -21,14 +21,17 @@
import org.junit.Test;
public class SubmitRecordTest {
-
- private static SubmitRecord OK_RECORD;
- private static SubmitRecord NOT_READY_RECORD;
+ private static final SubmitRecord OK_RECORD;
+ private static final SubmitRecord FORCED_RECORD;
+ private static final SubmitRecord NOT_READY_RECORD;
static {
OK_RECORD = new SubmitRecord();
OK_RECORD.status = SubmitRecord.Status.OK;
+ FORCED_RECORD = new SubmitRecord();
+ FORCED_RECORD.status = SubmitRecord.Status.FORCED;
+
NOT_READY_RECORD = new SubmitRecord();
NOT_READY_RECORD.status = SubmitRecord.Status.NOT_READY;
}
@@ -49,6 +52,14 @@
}
@Test
+ public void okWhenForced() {
+ Collection<SubmitRecord> submitRecords = new ArrayList<>();
+ submitRecords.add(FORCED_RECORD);
+
+ assertThat(SubmitRecord.allRecordsOK(submitRecords)).isTrue();
+ }
+
+ @Test
public void emptyResultIfInvalid() {
Collection<SubmitRecord> submitRecords = new ArrayList<>();
submitRecords.add(NOT_READY_RECORD);
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 2ddfaa9..3864676 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -53,6 +53,7 @@
"//lib:gson",
"//lib:guava-retrying",
"//lib:gwtorm",
+ "//lib:protobuf",
"//lib:truth-java8-extension",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
@@ -60,5 +61,6 @@
"//lib/guice",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//proto:cache_java_proto",
],
)
diff --git a/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java
new file mode 100644
index 0000000..0e04d32
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/EnumCacheSerializerTest.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2018 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.server.cache;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class EnumCacheSerializerTest {
+ @Test
+ public void serialize() throws Exception {
+ assertRoundTrip(MyEnum.FOO);
+ assertRoundTrip(MyEnum.BAR);
+ assertRoundTrip(MyEnum.BAZ);
+ }
+
+ private enum MyEnum {
+ FOO,
+ BAR,
+ BAZ;
+ }
+
+ private static void assertRoundTrip(MyEnum e) throws Exception {
+ CacheSerializer<MyEnum> s = new EnumCacheSerializer<>(MyEnum.class);
+ assertThat(s.deserialize(s.serialize(e))).isEqualTo(e);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/JavaCacheSerializerTest.java b/javatests/com/google/gerrit/server/cache/JavaCacheSerializerTest.java
new file mode 100644
index 0000000..41d07b9
--- /dev/null
+++ b/javatests/com/google/gerrit/server/cache/JavaCacheSerializerTest.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2018 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.server.cache;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.AutoValue;
+import java.io.Serializable;
+import org.junit.Test;
+
+public class JavaCacheSerializerTest {
+ @Test
+ public void builtInTypes() throws Exception {
+ assertRoundTrip("foo");
+ assertRoundTrip(Integer.valueOf(1234));
+ assertRoundTrip(Boolean.TRUE);
+ }
+
+ @Test
+ public void customType() throws Exception {
+ assertRoundTrip(new AutoValue_JavaCacheSerializerTest_MyType(123, "four five six"));
+ }
+
+ @AutoValue
+ abstract static class MyType implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ abstract Integer anInt();
+
+ abstract String aString();
+ }
+
+ private static <T extends Serializable> void assertRoundTrip(T input) throws Exception {
+ JavaCacheSerializer<T> s = new JavaCacheSerializer<>();
+ assertThat(s.deserialize(s.serialize(input))).isEqualTo(input);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/cache/h2/BUILD b/javatests/com/google/gerrit/server/cache/h2/BUILD
index 0ac4aef..e2b9257 100644
--- a/javatests/com/google/gerrit/server/cache/h2/BUILD
+++ b/javatests/com/google/gerrit/server/cache/h2/BUILD
@@ -9,6 +9,7 @@
"//lib:guava",
"//lib:h2",
"//lib:junit",
+ "//lib:truth",
"//lib/guice",
],
)
diff --git a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
index 80bca6d..32d0d27 100644
--- a/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
+++ b/javatests/com/google/gerrit/server/cache/h2/H2CacheTest.java
@@ -14,62 +14,134 @@
package com.google.gerrit.server.cache.h2;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.server.cache.CacheSerializer;
import com.google.gerrit.server.cache.h2.H2CacheImpl.SqlStore;
import com.google.gerrit.server.cache.h2.H2CacheImpl.ValueHolder;
import com.google.inject.TypeLiteral;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
-import org.junit.Before;
import org.junit.Test;
public class H2CacheTest {
+ private static final TypeLiteral<String> KEY_TYPE = new TypeLiteral<String>() {};
+ private static final int DEFAULT_VERSION = 1234;
private static int dbCnt;
- private Cache<String, ValueHolder<Boolean>> mem;
- private H2CacheImpl<String, Boolean> impl;
+ private static int nextDbId() {
+ return ++dbCnt;
+ }
- @Before
- public void setUp() {
- mem = CacheBuilder.newBuilder().build();
-
- TypeLiteral<String> keyType = new TypeLiteral<String>() {};
- SqlStore<String, Boolean> store =
- new SqlStore<>("jdbc:h2:mem:Test_" + (++dbCnt), keyType, 1 << 20, 0);
- impl = new H2CacheImpl<>(MoreExecutors.directExecutor(), store, keyType, mem);
+ private static H2CacheImpl<String, String> newH2CacheImpl(
+ int id, Cache<String, ValueHolder<String>> mem, int version) {
+ SqlStore<String, String> store =
+ new SqlStore<>(
+ "jdbc:h2:mem:Test_" + id,
+ KEY_TYPE,
+ StringSerializer.INSTANCE,
+ StringSerializer.INSTANCE,
+ version,
+ 1 << 20,
+ 0);
+ return new H2CacheImpl<>(MoreExecutors.directExecutor(), store, KEY_TYPE, mem);
}
@Test
public void get() throws ExecutionException {
- assertNull(impl.getIfPresent("foo"));
+ Cache<String, ValueHolder<String>> mem = CacheBuilder.newBuilder().build();
+ H2CacheImpl<String, String> impl = newH2CacheImpl(nextDbId(), mem, DEFAULT_VERSION);
- final AtomicBoolean called = new AtomicBoolean();
- assertTrue(
- impl.get(
- "foo",
- () -> {
- called.set(true);
- return true;
- }));
- assertTrue("used Callable", called.get());
- assertTrue("exists in cache", impl.getIfPresent("foo"));
+ assertThat(impl.getIfPresent("foo")).isNull();
+
+ AtomicBoolean called = new AtomicBoolean();
+ assertThat(
+ impl.get(
+ "foo",
+ () -> {
+ called.set(true);
+ return "bar";
+ }))
+ .isEqualTo("bar");
+ assertThat(called.get()).named("Callable was called").isTrue();
+ assertThat(impl.getIfPresent("foo")).named("in-memory value").isEqualTo("bar");
mem.invalidate("foo");
- assertTrue("exists on disk", impl.getIfPresent("foo"));
+ assertThat(impl.getIfPresent("foo")).named("persistent value").isEqualTo("bar");
called.set(false);
- assertTrue(
- impl.get(
- "foo",
- () -> {
- called.set(true);
- return true;
- }));
- assertFalse("did not invoke Callable", called.get());
+ assertThat(
+ impl.get(
+ "foo",
+ () -> {
+ called.set(true);
+ return "baz";
+ }))
+ .named("cached value")
+ .isEqualTo("bar");
+ assertThat(called.get()).named("Callable was called").isFalse();
+ }
+
+ @Test
+ public void stringSerializer() {
+ String input = "foo";
+ byte[] serialized = StringSerializer.INSTANCE.serialize(input);
+ assertThat(serialized).isEqualTo(new byte[] {'f', 'o', 'o'});
+ assertThat(StringSerializer.INSTANCE.deserialize(serialized)).isEqualTo(input);
+ }
+
+ @Test
+ public void version() throws Exception {
+ int id = nextDbId();
+ H2CacheImpl<String, String> oldImpl = newH2CacheImpl(id, disableMemCache(), DEFAULT_VERSION);
+ H2CacheImpl<String, String> newImpl =
+ newH2CacheImpl(id, disableMemCache(), DEFAULT_VERSION + 1);
+
+ assertThat(oldImpl.diskStats().space()).isEqualTo(0);
+ assertThat(newImpl.diskStats().space()).isEqualTo(0);
+ oldImpl.put("key", "val");
+ assertThat(oldImpl.getIfPresent("key")).isEqualTo("val");
+ assertThat(oldImpl.diskStats().space()).isEqualTo(12);
+ assertThat(oldImpl.diskStats().hitCount()).isEqualTo(1);
+
+ // Can't find key in cache with wrong version, but the data is still there.
+ assertThat(newImpl.diskStats().requestCount()).isEqualTo(0);
+ assertThat(newImpl.diskStats().space()).isEqualTo(12);
+ assertThat(newImpl.getIfPresent("key")).isNull();
+ assertThat(newImpl.diskStats().space()).isEqualTo(12);
+
+ // Re-putting it via the new cache works, and uses the same amount of space.
+ newImpl.put("key", "val2");
+ assertThat(newImpl.getIfPresent("key")).isEqualTo("val2");
+ assertThat(newImpl.diskStats().hitCount()).isEqualTo(1);
+ assertThat(newImpl.diskStats().space()).isEqualTo(14);
+
+ // Now it's no longer in the old cache.
+ assertThat(oldImpl.diskStats().space()).isEqualTo(14);
+ assertThat(oldImpl.getIfPresent("key")).isNull();
+ }
+
+ // TODO(dborowitz): Won't be necessary when we use a real StringSerializer in the server code.
+ private enum StringSerializer implements CacheSerializer<String> {
+ INSTANCE;
+
+ @Override
+ public byte[] serialize(String object) {
+ return object.getBytes(UTF_8);
+ }
+
+ @Override
+ public String deserialize(byte[] in) {
+ // TODO(dborowitz): Consider using CharsetDecoder directly in the real implementation, to get
+ // checked exceptions.
+ return new String(in, UTF_8);
+ }
+ }
+
+ private static <K, V> Cache<K, ValueHolder<V>> disableMemCache() {
+ return CacheBuilder.newBuilder().maximumSize(0).build();
}
}
diff --git a/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
new file mode 100644
index 0000000..4470f55
--- /dev/null
+++ b/javatests/com/google/gerrit/server/change/ChangeKindCacheImplTest.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2018 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.server.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.server.cache.CacheSerializer;
+import com.google.gerrit.server.cache.proto.Cache.ChangeKindKeyProto;
+import com.google.protobuf.ByteString;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class ChangeKindCacheImplTest {
+ @Test
+ public void keySerializer() throws Exception {
+ ChangeKindCacheImpl.Key key =
+ new ChangeKindCacheImpl.Key(
+ ObjectId.zeroId(),
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
+ "aStrategy");
+ CacheSerializer<ChangeKindCacheImpl.Key> s = new ChangeKindCacheImpl.Key.Serializer();
+ byte[] serialized = s.serialize(key);
+ assertThat(ChangeKindKeyProto.parseFrom(serialized))
+ .isEqualTo(
+ ChangeKindKeyProto.newBuilder()
+ .setPrior(bytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
+ .setNext(
+ bytes(
+ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef))
+ .setStrategyName("aStrategy")
+ .build());
+ assertThat(s.deserialize(serialized)).isEqualTo(key);
+ }
+
+ private static ByteString bytes(int... ints) {
+ byte[] bytes = new byte[ints.length];
+ for (int i = 0; i < ints.length; i++) {
+ bytes[i] = (byte) ints[i];
+ }
+ return ByteString.copyFrom(bytes);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index edd4abf..b525504 100644
--- a/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -27,7 +27,7 @@
new FakeQueryBuilder.Definition<>(FakeQueryBuilder.class),
new ChangeQueryBuilder.Arguments(
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, indexes, null, null, null, null, null, null, null));
+ null, null, null, null, null, indexes, null, null, null, null, null, null, null, null));
}
@Operator
diff --git a/javatests/com/google/gerrit/server/mail/receive/MailHeaderParserTest.java b/javatests/com/google/gerrit/server/mail/receive/MailHeaderParserTest.java
index b7277f3..071dc4b 100644
--- a/javatests/com/google/gerrit/server/mail/receive/MailHeaderParserTest.java
+++ b/javatests/com/google/gerrit/server/mail/receive/MailHeaderParserTest.java
@@ -65,11 +65,12 @@
b.subject("");
StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append(MailHeader.CHANGE_NUMBER.withDelimiter() + "123\r\n");
- stringBuilder.append("> " + MailHeader.PATCH_SET.withDelimiter() + "1\n");
- stringBuilder.append(MailHeader.MESSAGE_TYPE.withDelimiter() + "comment\n");
- stringBuilder.append(
- MailHeader.COMMENT_DATE.withDelimiter() + "Tue, 25 Oct 2016 02:11:35 -0700\r\n");
+ stringBuilder.append(MailHeader.CHANGE_NUMBER.withDelimiter()).append("123\r\n");
+ stringBuilder.append("> ").append(MailHeader.PATCH_SET.withDelimiter()).append("1\n");
+ stringBuilder.append(MailHeader.MESSAGE_TYPE.withDelimiter()).append("comment\n");
+ stringBuilder
+ .append(MailHeader.COMMENT_DATE.withDelimiter())
+ .append("Tue, 25 Oct 2016 02:11:35 -0700\r\n");
b.textContent(stringBuilder.toString());
Address author = new Address("Diffy", "test@gerritcodereview.com");
@@ -97,15 +98,20 @@
b.subject("");
StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append(
- "<div id\"someid\">" + MailHeader.CHANGE_NUMBER.withDelimiter() + "123</div>");
- stringBuilder.append("<div>" + MailHeader.PATCH_SET.withDelimiter() + "1</div>");
- stringBuilder.append("<div>" + MailHeader.MESSAGE_TYPE.withDelimiter() + "comment</div>");
- stringBuilder.append(
- "<div>"
- + MailHeader.COMMENT_DATE.withDelimiter()
- + "Tue, 25 Oct 2016 02:11:35 -0700"
- + "</div>");
+ stringBuilder
+ .append("<div id\"someid\">")
+ .append(MailHeader.CHANGE_NUMBER.withDelimiter())
+ .append("123</div>");
+ stringBuilder.append("<div>").append(MailHeader.PATCH_SET.withDelimiter()).append("1</div>");
+ stringBuilder
+ .append("<div>")
+ .append(MailHeader.MESSAGE_TYPE.withDelimiter())
+ .append("comment</div>");
+ stringBuilder
+ .append("<div>")
+ .append(MailHeader.COMMENT_DATE.withDelimiter())
+ .append("Tue, 25 Oct 2016 02:11:35 -0700")
+ .append("</div>");
b.htmlContent(stringBuilder.toString());
Address author = new Address("Diffy", "test@gerritcodereview.com");
diff --git a/javatests/com/google/gerrit/server/mail/send/NotificationEmailTest.java b/javatests/com/google/gerrit/server/mail/send/NotificationEmailTest.java
index f078f90..885f7cd 100644
--- a/javatests/com/google/gerrit/server/mail/send/NotificationEmailTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/NotificationEmailTest.java
@@ -19,6 +19,19 @@
import org.junit.Test;
public class NotificationEmailTest {
+
+ @Test
+ public void getInstanceAndProjectName_returnsTheRightValue() {
+ String instanceAndProjectName = NotificationEmail.getInstanceAndProjectName("test", "/my/api");
+ assertThat(instanceAndProjectName).isEqualTo("test/api");
+ }
+
+ @Test
+ public void getInstanceAndProjectName_handlesNull() {
+ String instanceAndProjectName = NotificationEmail.getInstanceAndProjectName(null, "/my/api");
+ assertThat(instanceAndProjectName).isEqualTo("...api");
+ }
+
@Test
public void getShortProjectName() {
assertThat(NotificationEmail.getShortProjectName("/api")).isEqualTo("api");
diff --git a/javatests/com/google/gerrit/server/patch/PatchListTest.java b/javatests/com/google/gerrit/server/patch/PatchListTest.java
index 0a7b97cc..6fbafb6 100644
--- a/javatests/com/google/gerrit/server/patch/PatchListTest.java
+++ b/javatests/com/google/gerrit/server/patch/PatchListTest.java
@@ -23,7 +23,6 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
-import java.util.Comparator;
import org.junit.Test;
public class PatchListTest {
@@ -36,16 +35,7 @@
Patch.COMMIT_MSG, Patch.MERGE_LIST, "/!xxx", "abc", "def/g", "qrx", "zzz",
};
- Arrays.sort(
- names,
- 0,
- names.length,
- new Comparator<String>() {
- @Override
- public int compare(String o1, String o2) {
- return PatchList.comparePaths(o1, o2);
- }
- });
+ Arrays.sort(names, 0, names.length, PatchList::comparePaths);
assertThat(names).isEqualTo(want);
}
@@ -58,16 +48,7 @@
Patch.COMMIT_MSG, "/!xxx", "abc", "def/g", "qrx", "zzz",
};
- Arrays.sort(
- names,
- 0,
- names.length,
- new Comparator<String>() {
- @Override
- public int compare(String o1, String o2) {
- return PatchList.comparePaths(o1, o2);
- }
- });
+ Arrays.sort(names, 0, names.length, PatchList::comparePaths);
assertThat(names).isEqualTo(want);
}
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index a14895b..c30803a 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -61,8 +61,6 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.project.testing.Util;
-import com.google.gerrit.server.rules.PrologEnvironment;
-import com.google.gerrit.server.rules.RulesCache;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -949,8 +947,6 @@
}
private InMemoryRepository add(ProjectConfig pc) {
- PrologEnvironment.Factory envFactory = null;
- RulesCache rulesCache = null;
SitePaths sitePaths = null;
List<CommentLinkInfo> commentLinks = null;
@@ -970,9 +966,7 @@
projectCache,
allProjectsName,
allUsersName,
- envFactory,
repoManager,
- rulesCache,
commentLinks,
capabilityCollectionFactory,
pc));
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 525e030..5843ab9 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1961,11 +1961,12 @@
public void reviewerin() throws Exception {
Account.Id user1 = accountManager.authenticate(AuthRequest.forUser("user1")).getAccountId();
Account.Id user2 = accountManager.authenticate(AuthRequest.forUser("user2")).getAccountId();
+ Account.Id user3 = accountManager.authenticate(AuthRequest.forUser("user3")).getAccountId();
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChange(repo));
Change change2 = insert(repo, newChange(repo));
- insert(repo, newChange(repo));
+ Change change3 = insert(repo, newChange(repo));
AddReviewerInput rin = new AddReviewerInput();
rin.reviewer = user1.toString();
@@ -1977,8 +1978,13 @@
rin.state = ReviewerState.REVIEWER;
gApi.changes().id(change2.getId().get()).addReviewer(rin);
+ rin = new AddReviewerInput();
+ rin.reviewer = user3.toString();
+ rin.state = ReviewerState.CC;
+ gApi.changes().id(change3.getId().get()).addReviewer(rin);
+
String group = gApi.groups().create("foo").get().name;
- gApi.groups().id(group).addMembers(user2.toString());
+ gApi.groups().id(group).addMembers(user2.toString(), user3.toString());
List<String> members =
gApi.groups()
@@ -1989,15 +1995,30 @@
.collect(toList());
assertThat(members).contains(user2.toString());
- assertQuery("reviewerin:\"Registered Users\"", change2, change1);
- assertQuery("reviewerin:" + group, change2);
+ if (notesMigration.readChanges()) {
+ // CC and REVIEWER are separate in NoteDB
+ assertQuery("reviewerin:\"Registered Users\"", change2, change1);
+ assertQuery("reviewerin:" + group, change2);
+ } else {
+ // CC and REVIEWER are the same in ReviewDb
+ assertQuery("reviewerin:\"Registered Users\"", change3, change2, change1);
+ assertQuery("reviewerin:" + group, change3, change2);
+ }
gApi.changes().id(change2.getId().get()).current().review(ReviewInput.approve());
gApi.changes().id(change2.getId().get()).current().submit();
- assertQuery("reviewerin:" + group, change2);
- assertQuery("project:repo reviewerin:" + group, change2);
- assertQuery("status:merged reviewerin:" + group, change2);
+ if (notesMigration.readChanges()) {
+ // CC and REVIEWER are separate in NoteDB
+ assertQuery("reviewerin:" + group, change2);
+ assertQuery("project:repo reviewerin:" + group, change2);
+ assertQuery("status:merged reviewerin:" + group, change2);
+ } else {
+ // CC and REVIEWER are the same in ReviewDb
+ assertQuery("reviewerin:" + group, change2, change3);
+ assertQuery("project:repo reviewerin:" + group, change2, change3);
+ assertQuery("status:merged reviewerin:" + group, change2);
+ }
}
@Test
diff --git a/javatests/com/google/gerrit/server/rules/GerritCommonTest.java b/javatests/com/google/gerrit/server/rules/GerritCommonTest.java
index 152b057..314941e 100644
--- a/javatests/com/google/gerrit/server/rules/GerritCommonTest.java
+++ b/javatests/com/google/gerrit/server/rules/GerritCommonTest.java
@@ -48,7 +48,8 @@
cfg.setInt("rules", null, "compileReductionLimit", (int) 1e6);
bind(PrologEnvironment.Args.class)
.toInstance(
- new PrologEnvironment.Args(null, null, null, null, null, null, null, cfg));
+ new PrologEnvironment.Args(
+ null, null, null, null, null, null, null, cfg, null));
}
});
}
diff --git a/plugins/hooks b/plugins/hooks
index 337a1e8..1bcd255 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 337a1e8ebbecd7a96f3a6676980fb1c5c0385f56
+Subproject commit 1bcd25599cd37a50c15d8ac9fe98785ce96337a0
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 8f946c5..32e1953 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -211,19 +211,19 @@
To run on all files, execute the following command:
```sh
-bazel test //polygerrit-ui/app:all --test_tag_filters=template --test_output errors
+./polygerrit-ui/app/run_template_test.sh
```
To run on a specific top level directory (ex: change-list)
```sh
-bazel test //polygerrit-ui/app:template_test_change-list --test_output errors
+TEMPLATE_NO_DEFAULT=true ./polygerrit-ui/app/run_template_test.sh //polygerrit-ui/app:template_test_change-list
```
To run on a specific file (ex: gr-change-list-view), execute the following command:
```sh
-bazel test //polygerrit-ui/app:template_test_<TOP_LEVEL_DIRECTORY> --test_arg=<VIEW_NAME> --test_output errors
+TEMPLATE_NO_DEFAULT=true ./polygerrit-ui/app/run_template_test.sh //polygerrit-ui/app:template_test_<TOP_LEVEL_DIRECTORY> --test_arg=<VIEW_NAME>
```
```sh
-bazel test //polygerrit-ui/app:template_test_change-list --test_arg=gr-change-list-view --test_output errors
+TEMPLATE_NO_DEFAULT=true ./polygerrit-ui/app/run_template_test.sh //polygerrit-ui/app:template_test_change-list --test_arg=gr-change-list-view
```
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
index 3779402..61df877 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.html
@@ -87,7 +87,7 @@
</style>
<style include="gr-form-styles"></style>
<fieldset id="section"
- class$="gr-form-styles [[_computeSectionClass(editing, _editingRef, _deleted)]]">
+ class$="gr-form-styles [[_computeSectionClass(editing, canUpload, ownerOf, _editingRef, _deleted)]]">
<div id="mainContainer">
<div class="header">
<div class="name">
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
index 4574e11..6fb7b0e 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
@@ -54,6 +54,8 @@
value: false,
observer: '_handleEditingChanged',
},
+ canUpload: Boolean,
+ ownerOf: Array,
_originalId: String,
_editingRef: {
type: Boolean,
@@ -214,9 +216,13 @@
this.$.editRefInput.focus();
},
- _computeSectionClass(editing, editingRef, deleted) {
+ _isEditEnabled(canUpload, ownerOf, sectionId) {
+ return canUpload || ownerOf.indexOf(sectionId) >= 0;
+ },
+
+ _computeSectionClass(editing, canUpload, ownerOf, editingRef, deleted) {
const classList = [];
- if (editing) {
+ if (editing && this._isEditEnabled(canUpload, ownerOf, this.section.id)) {
classList.push('editing');
}
if (editingRef) {
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
index ad401b8..21a426f 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
@@ -272,26 +272,37 @@
test('_computeSectionClass', () => {
let editingRef = false;
+ let canUpload = false;
+ let ownerOf = [];
let editing = false;
let deleted = false;
- assert.equal(element._computeSectionClass(editing, editingRef, deleted),
- '');
+ assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
+ editingRef, deleted), '');
editing = true;
- assert.equal(element._computeSectionClass(editing, editingRef, deleted),
- 'editing');
+ assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
+ editingRef, deleted), '');
+
+ ownerOf = ['refs/*'];
+ assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
+ editingRef, deleted), 'editing');
+
+ ownerOf = [];
+ canUpload = true;
+ assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
+ editingRef, deleted), 'editing');
editingRef = true;
- assert.equal(element._computeSectionClass(editing, editingRef, deleted),
- 'editing editingRef');
+ assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
+ editingRef, deleted), 'editing editingRef');
deleted = true;
- assert.equal(element._computeSectionClass(editing, editingRef, deleted),
- 'editing editingRef deleted');
+ assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
+ editingRef, deleted), 'editing editingRef deleted');
editingRef = false;
- assert.equal(element._computeSectionClass(editing, editingRef, deleted),
- 'editing deleted');
+ assert.equal(element._computeSectionClass(editing, canUpload, ownerOf,
+ editingRef, deleted), 'editing deleted');
});
test('_computeEditBtnClass', () => {
@@ -356,6 +367,8 @@
assert.isFalse(element.$.section.classList.contains('deleted'));
assert.isTrue(element.$.editBtn.classList.contains('global'));
element.editing = true;
+ element.canUpload = true;
+ element.ownerOf = [];
assert.equal(getComputedStyle(element.$.editBtn).display, 'none');
});
});
@@ -382,6 +395,9 @@
assert.isFalse(element.$.section.classList.contains('deleted'));
assert.isFalse(element.$.editBtn.classList.contains('global'));
element.editing = true;
+ element.canUpload = true;
+ element.ownerOf = [];
+ flushAsynchronousOperations();
assert.notEqual(getComputedStyle(element.$.editBtn).display, 'none');
});
@@ -440,6 +456,8 @@
});
test('edit section reference', () => {
+ element.canUpload = true;
+ element.ownerOf = [];
element.section = {id: 'refs/for/bar', value: {permissions: {}}};
assert.isFalse(element.$.section.classList.contains('editing'));
element.editing = true;
@@ -486,6 +504,8 @@
test('remove section', () => {
element.editing = true;
+ element.canUpload = true;
+ element.ownerOf = [];
assert.isFalse(element._deleted);
assert.isNotOk(element.section.value.deleted);
MockInteractions.tap(element.$.deleteBtn);
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
index c1ac650..2991a09 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
@@ -148,10 +148,15 @@
<template is="dom-repeat" items="[[_includedGroups]]">
<tr>
<td class="nameColumn">
- <a href$="[[_computeGroupUrl(item.url)]]"
- rel="noopener">
+ <template is="dom-if" if="[[item.url]]">
+ <a href$="[[_computeGroupUrl(item.url)]]"
+ rel="noopener">
+ [[item.name]]
+ </a>
+ </template>
+ <template is="dom-if" if="[[!item.url]]">
[[item.name]]
- </a>
+ </template>
</td>
<td>[[item.description]]</td>
<td class="deleteColumn">
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
index 553856a..8b6ce7a 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
@@ -122,6 +122,8 @@
},
_computeGroupUrl(url) {
+ if (!url) { return; }
+
const r = new RegExp(URL_REGEX, 'i');
if (r.test(url)) {
return url;
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
index ca854c0..29a3cca 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
@@ -261,6 +261,21 @@
assert.equal(element._computeLoadingClass(false), '');
});
+ test('_computeGroupUrl', () => {
+ assert.isUndefined(element._computeGroupUrl(undefined));
+
+ assert.isUndefined(element._computeGroupUrl(false));
+
+ let url = '#/admin/groups/uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
+ assert.equal(element._computeGroupUrl(url),
+ 'https://test/site/admin/groups/' +
+ 'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498');
+
+ url = 'https://gerrit.local/admin/groups/' +
+ 'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
+ assert.equal(element._computeGroupUrl(url), url);
+ });
+
test('fires page-error', done => {
groupStub.restore();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
index 0ceac7c..f718806 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
@@ -38,7 +38,8 @@
#inheritsFrom,
#editInheritFromInput,
.editing #inheritFromName,
- .weblinks{
+ .weblinks,
+ .editing .invisible{
display: none;
}
#inheritsFrom.show {
@@ -67,7 +68,7 @@
}
</style>
<style include="gr-menu-page-styles"></style>
- <main class$="[[_computeMainClass(_isAdmin, _canUpload, _editing)]]">
+ <main class$="[[_computeMainClass(_ownerOf, _canUpload, _editing)]]">
<div id="loading" class$="[[_computeLoadingClass(_loading)]]">
Loading...
</div>
@@ -97,9 +98,14 @@
on-tap="_handleEdit">[[_editOrCancel(_editing)]]</gr-button>
<gr-button id="saveBtn"
primary
+ class$="[[_computeSaveBtnClass(_ownerOf)]]"
+ on-tap="_handleSave"
+ disabled$="[[!_modified]]">Save</gr-button>
+ <gr-button id="saveReviewBtn"
+ primary
+ class$="[[_computeSaveReviewBtnClass(_canUpload)]]"
on-tap="_handleSaveForReview"
- disabled$="[[!_modified]]">
- Save for review</gr-button>
+ disabled$="[[!_modified]]">Save for review</gr-button>
<template
is="dom-repeat"
items="{{_sections}}"
@@ -110,7 +116,9 @@
capabilities="[[_capabilities]]"
section="{{section}}"
labels="[[_labels]]"
+ can-upload="[[_canUpload]]"
editing="[[_editing]]"
+ owner-of="[[_ownerOf]]"
groups="[[_groups]]"
on-added-section-removed="_handleAddedSectionRemoved"></gr-access-section>
</template>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index f307090..24d0c62 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -79,10 +79,6 @@
// The current path
path: String,
- _isAdmin: {
- type: Boolean,
- value: false,
- },
_canUpload: {
type: Boolean,
value: false,
@@ -139,12 +135,18 @@
_repoChanged(repo) {
if (!repo) { return Promise.resolve(); }
+ return this._reload(repo);
+ },
+
+ _reload(repo) {
const promises = [];
const errFn = response => {
this.fire('page-error', {response});
};
+ this._editing = false;
+
// Always reset sections when a project changes.
this._sections = [];
promises.push(this.$.restAPI.getRepoAccessRights(repo, errFn)
@@ -167,6 +169,7 @@
this._groups = res.groups;
this._weblinks = res.config_web_links || [];
this._canUpload = res.can_upload;
+ this._ownerOf = res.owner_of || [];
return this.toSortedArray(this._local);
}));
@@ -184,10 +187,6 @@
return res.labels;
}));
- promises.push(this.$.restAPI.getIsAdmin().then(isAdmin => {
- this._isAdmin = isAdmin;
- }));
-
return Promise.all(promises).then(([sections, capabilities, labels]) => {
this._capabilities = capabilities;
this._labels = labels;
@@ -393,7 +392,7 @@
.editReference();
},
- _handleSaveForReview() {
+ _getObjforSave() {
const addRemoveObj = this._computeAddAndRemove();
// If there are no changes, don't actually save.
if (!Object.keys(addRemoveObj.add).length &&
@@ -410,24 +409,38 @@
if (addRemoveObj.parent) {
obj.parent = addRemoveObj.parent;
}
- return this.$.restAPI.setProjectAccessRightsForReview(this.repo, obj)
+ return obj;
+ },
+
+ _handleSave() {
+ const obj = this._getObjforSave();
+ if (!obj) { return; }
+ return this.$.restAPI.setRepoAccessRights(this.repo, obj)
+ .then(() => {
+ this._reload(this.repo);
+ });
+ },
+
+ _handleSaveForReview() {
+ const obj = this._getObjforSave();
+ if (!obj) { return; }
+ return this.$.restAPI.setRepoAccessRightsForReview(this.repo, obj)
.then(change => {
Gerrit.Nav.navigateToChange(change);
});
},
- _computeShowSaveClass(editing) {
- if (!editing) { return ''; }
- return 'visible';
+ _computeSaveReviewBtnClass(canUpload) {
+ return !canUpload ? 'invisible' : '';
},
- _computeEditingClass(editing) {
- return editing ? 'editing': '';
+ _computeSaveBtnClass(ownerOf) {
+ return ownerOf.length < 0 ? 'invisible' : '';
},
- _computeMainClass(isAdmin, canUpload, editing) {
+ _computeMainClass(ownerOf, canUpload, editing) {
const classList = [];
- if (isAdmin || canUpload) {
+ if (ownerOf.length > 0 || canUpload) {
classList.push('admin');
}
if (editing) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index 2f0b1b8..e5b0a25 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -119,6 +119,8 @@
repoStub = sandbox.stub(element.$.restAPI, 'getRepo').returns(
Promise.resolve(repoRes));
element._loading = false;
+ element._ownerOf = [];
+ element._canUpload = false;
});
teardown(() => {
@@ -142,14 +144,11 @@
const capabilitiesStub = sandbox.stub(element.$.restAPI,
'getCapabilities');
capabilitiesStub.returns(Promise.resolve(capabilitiesRes));
- const adminStub = sandbox.stub(element.$.restAPI, 'getIsAdmin').returns(
- Promise.resolve(true));
element._repoChanged('New Repo').then(() => {
assert.isTrue(accessStub.called);
assert.isTrue(capabilitiesStub.called);
assert.isTrue(repoStub.called);
- assert.isTrue(adminStub.called);
assert.isNotOk(element._inheritsFrom);
assert.deepEqual(element._local, accessRes.local);
assert.deepEqual(element._sections,
@@ -195,20 +194,21 @@
});
test('_computeMainClass', () => {
- let isAdmin = true;
+ let ownerOf = ['refs/*'];
const editing = true;
const canUpload = false;
- assert.equal(element._computeMainClass(isAdmin, canUpload), 'admin');
- assert.equal(element._computeMainClass(isAdmin, canUpload, editing),
+ assert.equal(element._computeMainClass(ownerOf, canUpload), 'admin');
+ assert.equal(element._computeMainClass(ownerOf, canUpload, editing),
'admin editing');
- isAdmin = false;
- assert.equal(element._computeMainClass(isAdmin, canUpload), '');
- assert.equal(element._computeMainClass(isAdmin, canUpload, editing),
+ ownerOf = [];
+ assert.equal(element._computeMainClass(ownerOf, canUpload), '');
+ assert.equal(element._computeMainClass(ownerOf, canUpload, editing),
'editing');
});
test('inherit section', () => {
element._local = {};
+ element._ownerOf = [];
sandbox.stub(element, '_computeParentHref');
// Nothing should appear when no inherit from and not in edit mode.
assert.equal(getComputedStyle(element.$.inheritsFrom).display, 'none');
@@ -260,8 +260,9 @@
});
suite('with defined sections', () => {
- const testEditSaveCancelBtns = () => {
+ const testEditSaveCancelBtns = (shouldShowSave, shouldShowSaveReview) => {
// Edit button is visible and Save button is hidden.
+ assert.equal(getComputedStyle(element.$.saveReviewBtn).display, 'none');
assert.equal(getComputedStyle(element.$.saveBtn).display, 'none');
assert.notEqual(getComputedStyle(element.$.editBtn).display, 'none');
assert.equal(element.$.editBtn.innerText, 'EDIT');
@@ -280,20 +281,33 @@
// Edit button changes to Cancel button, and Save button is visible but
// disabled.
assert.equal(element.$.editBtn.innerText, 'CANCEL');
- assert.notEqual(getComputedStyle(element.$.saveBtn).display, 'none');
- assert.isTrue(element.$.saveBtn.disabled);
+ if (shouldShowSaveReview) {
+ assert.notEqual(getComputedStyle(element.$.saveReviewBtn).display,
+ 'none');
+ assert.isTrue(element.$.saveReviewBtn.disabled);
+ }
+ if (shouldShowSave) {
+ assert.notEqual(getComputedStyle(element.$.saveBtn).display, 'none');
+ assert.isTrue(element.$.saveBtn.disabled);
+ }
assert.notEqual(getComputedStyle(element.$$('#editInheritFromInput'))
.display, 'none');
// Save button should be enabled after access is modified
element.fire('access-modified');
- assert.isFalse(element.$.saveBtn.disabled);
+ if (shouldShowSaveReview) {
+ assert.isFalse(element.$.saveReviewBtn.disabled);
+ }
+ if (shouldShowSave) {
+ assert.isFalse(element.$.saveBtn.disabled);
+ }
};
setup(() => {
// Create deep copies of these objects so the originals are not modified
// by any tests.
element._local = JSON.parse(JSON.stringify(accessRes.local));
+ element._ownerOf = [];
element._sections = element.toSortedArray(element._local);
element._groups = JSON.parse(JSON.stringify(accessRes.groups));
element._capabilities = JSON.parse(JSON.stringify(capabilitiesRes));
@@ -309,19 +323,25 @@
assert.equal(element._sections.length, 0);
});
- test('button visibility for non admin', () => {
- assert.equal(getComputedStyle(element.$.saveBtn).display, 'none');
+ test('button visibility for non ref owner', () => {
+ assert.equal(getComputedStyle(element.$.saveReviewBtn).display, 'none');
assert.equal(getComputedStyle(element.$.editBtn).display, 'none');
});
- test('button visibility for non admin with upload privilege', () => {
+ test('button visibility for non ref owner with upload privilege', () => {
element._canUpload = true;
- testEditSaveCancelBtns();
+ testEditSaveCancelBtns(false, true);
});
- test('button visibility for admin', () => {
- element._isAdmin = true;
- testEditSaveCancelBtns();
+ test('button visibility for ref owner', () => {
+ element._ownerOf = ['refs/for/*'];
+ testEditSaveCancelBtns(true, false);
+ });
+
+ test('button visibility for ref owner and upload', () => {
+ element._ownerOf = ['refs/for/*'];
+ element._canUpload = true;
+ testEditSaveCancelBtns(true, false);
});
test('_handleAccessModified called with event fired', () => {
@@ -343,7 +363,7 @@
test('_handleSaveForReview', () => {
const saveStub =
- sandbox.stub(element.$.restAPI, 'setProjectAccessRightsForReview');
+ sandbox.stub(element.$.restAPI, 'setRepoAccessRightsForReview');
sandbox.stub(element, '_computeAddAndRemove').returns({
add: {},
remove: {},
@@ -1047,7 +1067,7 @@
Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
sandbox.stub(Gerrit.Nav, 'navigateToChange');
const saveForReviewStub = sandbox.stub(element.$.restAPI,
- 'setProjectAccessRightsForReview')
+ 'setRepoAccessRightsForReview')
.returns(Promise.resolve({_number: 1}));
element.repo = 'test-repo';
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html
index d57dd329..e9bc674 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.html
@@ -19,8 +19,8 @@
<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/gr-table-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
@@ -34,6 +34,7 @@
<dom-module id="gr-repo-detail-list">
<template>
<style include="gr-form-styles"></style>
+ <style include="gr-table-styles"></style>
<style include="shared-styles">
.tags td.name {
min-width: 25em;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index 1ee0b6e..de39478 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -108,7 +108,7 @@
font-size: 1.2rem;
}
.u-gray-background {
- background-color: #F5F5F5;
+ background-color: var(--table-header-background-color);
}
.comma,
.placeholder {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
index 0ce754d..3a7ff10 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
@@ -54,7 +54,7 @@
}
nav,
iron-icon {
- color: rgba(0, 0, 0, .87);
+ color: var(--deemphasized-text-color);
}
iron-icon {
height: 1.85rem;
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
index 6a3ea54..df09dfe 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
@@ -144,7 +144,9 @@
},
_getReviewerSuggestions(input) {
- if (!this.change) { return Promise.resolve([]); }
+ if (!this.change || !this.change._number) { return Promise.resolve([]); }
+
+ input = `cansee:${this.change._number} ${input}`;
const api = this.$.restAPI;
const xhr = this.allowAnyUser ?
api.getSuggestedAccounts(input) :
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
index 07c1554..c63f64d 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
@@ -80,6 +80,7 @@
element = fixture('basic');
element.change = {
+ _number: 42,
owner,
reviewers: {
CC: [existingReviewer1],
@@ -179,12 +180,14 @@
element._getReviewerSuggestions('').then(() => {
assert.isTrue(suggestReviewerStub.calledOnce);
+ assert.isTrue(suggestReviewerStub.calledWith(42, 'cansee:42 '));
assert.isFalse(suggestAccountStub.called);
element.allowAnyUser = true;
element._getReviewerSuggestions('').then(() => {
assert.isTrue(suggestReviewerStub.calledOnce);
assert.isTrue(suggestAccountStub.calledOnce);
+ assert.isTrue(suggestAccountStub.calledWith('cansee:42 '));
done();
});
});
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 6bc4a13..897bd21 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
@@ -251,11 +251,12 @@
});
test('submit change', done => {
+ element.$.confirmSubmitDialog.$.confirm.focus = done;
sandbox.stub(element.$.restAPI, 'getFromProjectLookup')
.returns(Promise.resolve('test'));
sandbox.stub(element, 'fetchChangeUpdates',
() => { return Promise.resolve({isLatest: true}); });
- const showDialogStub = sandbox.stub(element, '_showActionDialog');
+ sandbox.stub(element.$.overlay, 'open').returns(Promise.resolve());
element.change = {
revisions: {
rev1: {_number: 1},
@@ -264,16 +265,9 @@
};
element.latestPatchNum = '2';
- flush(() => {
- const submitButton = element.$$('gr-button[data-action-key="submit"]');
- assert.ok(submitButton);
- MockInteractions.tap(submitButton);
-
- assert.isTrue(showDialogStub.calledOnce);
- assert.equal(showDialogStub.lastCall.args[0],
- element.$.confirmSubmitDialog);
- done();
- });
+ const submitButton = element.$$('gr-button[data-action-key="submit"]');
+ assert.ok(submitButton);
+ MockInteractions.tap(submitButton);
});
test('_handleSubmitConfirm', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index e5728d8..7ef62ea 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -84,16 +84,16 @@
@apply --vote-chip-styles;
}
.max {
- background-color: var(--vote-color-max);
+ background-color: var(--vote-color-approved);
}
.min {
- background-color: var(--vote-color-min);
+ background-color: var(--vote-color-rejected);
}
.positive {
- background-color: var(--vote-color-positive);
+ background-color: var(--vote-color-recommended);
}
.negative {
- background-color: var(--vote-color-negative);
+ background-color: var(--vote-color-disliked);
}
.webLink {
display: block;
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
index 536b943..2a33343 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.html
@@ -18,6 +18,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
<dom-module id="gr-change-requirements">
<template strip-whitespace>
@@ -26,16 +27,15 @@
display: inline-block;
font-weight: initial;
text-align: center;
- width: 1em;
}
- .unsatisfied .status {
+ .unsatisfied .icon {
color: #FFA62F;
}
- .satisfied .status {
+ .satisfied .icon {
color: #388E3C;
}
.requirement {
- padding: .1em .3em;
+ padding: .1em 0;
}
.requirementContainer:not(:first-of-type) {
margin-top: .25em;
@@ -46,7 +46,7 @@
</style>
<template is="dom-if" if="[[_showWip]]">
<div class="requirement unsatisfied changeIsWip">
- <span class="status">â§—</span>
+ <span class="status"><iron-icon class="icon" icon="gr-icons:hourglass"></iron-icon></span>
Work in Progress
</div>
</template>
@@ -55,7 +55,9 @@
is="dom-repeat"
items="[[labels]]">
<div class$="requirement [[item.style]]">
- <span class="status">[[item.status]]</span>
+ <span class="status">
+ <iron-icon class="icon" icon="[[item.icon]]"></iron-icon>
+ </span>
Label <span class="labelName">[[item.label]]</span>
</div>
</template>
@@ -65,7 +67,7 @@
items="[[requirements]]">
<div class$="requirement [[_computeRequirementClass(item.satisfied)]]">
<span class="status">
- [[_computeRequirementStatus(item.satisfied)]]
+ <iron-icon id="icon" icon="[[_computeRequirementIcon(item.satisfied)]]"></iron-icon>
</span>
[[item.fallback_text]]
</div>
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
index 884e9cd..765f639 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
@@ -75,9 +75,9 @@
const obj = labels[label];
if (obj.optional) { continue; }
- const status = this._computeRequirementStatus(obj.approved);
+ const icon = this._computeRequirementIcon(obj.approved);
const style = this._computeRequirementClass(obj.approved);
- _labels.push({label, status, style});
+ _labels.push({label, icon, style});
}
return _labels;
@@ -91,11 +91,11 @@
}
},
- _computeRequirementStatus(requirementStatus) {
+ _computeRequirementIcon(requirementStatus) {
if (requirementStatus) {
- return '✓';
+ return 'gr-icons:check';
} else {
- return 'â§—';
+ return 'gr-icons:hourglass';
}
},
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
index 560a33d..efac0c2 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
@@ -51,8 +51,8 @@
assert.equal(element._computeRequirementClass(true), 'satisfied');
assert.equal(element._computeRequirementClass(false), 'unsatisfied');
- assert.equal(element._computeRequirementStatus(true), '✓');
- assert.equal(element._computeRequirementStatus(false), 'â§—');
+ assert.equal(element._computeRequirementIcon(true), 'gr-icons:check');
+ assert.equal(element._computeRequirementIcon(false), 'gr-icons:hourglass');
});
test('properly converts satisfied labels', () => {
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 0688435..519b15b 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
@@ -71,19 +71,11 @@
z-index: 99; /* Less than gr-overlay's backdrop */
}
.header.editMode {
- background-color: #ebf5fb;
+ background-color: var(--edit-mode-background-color);
}
.header .download {
margin-right: 1em;
}
- .header.pinned {
- border-bottom-color: transparent;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
- position: fixed;
- top: 0;
- transition: box-shadow 250ms linear;
- width: 100%;
- }
gr-change-status {
display: initial;
margin: .1em .1em .1em .4em;
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
index e9419e2..9085971 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
@@ -26,6 +26,7 @@
<template>
<style include="shared-styles">
:host {
+ background-color: var(--dialog-background-color);
display: block;
padding: 1em;
}
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index da7a16a..487d8c8 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -368,9 +368,11 @@
</label>
</div>
<div class="editFileControls showOnEdit">
- <gr-edit-file-controls
- class$="[[_computeClass('', file.__path)]]"
- file-path="[[file.__path]]"></gr-edit-file-controls>
+ <template is="dom-if" if="[[editMode]]">
+ <gr-edit-file-controls
+ class$="[[_computeClass('', file.__path)]]"
+ file-path="[[file.__path]]"></gr-edit-file-controls>
+ </template>
</div>
<div class="show-hide">
<label class="show-hide" data-path$="[[file.__path]]"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index c978281..2f30fb0 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -186,8 +186,8 @@
keyBindings: {
'shift+left': '_handleShiftLeftKey',
'shift+right': '_handleShiftRightKey',
- 'i': '_handleIKey',
- 'shift+i': '_handleCapitalIKey',
+ 'i:keyup': '_handleIKey',
+ 'shift+i:keyup': '_handleCapitalIKey',
'down j': '_handleDownKey',
'up k': '_handleUpKey',
'c': '_handleCKey',
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 3f656ae..9987521 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -530,24 +530,24 @@
const files = Polymer.dom(element.root).querySelectorAll('.file-row');
element.$.fileCursor.stops = files;
element.$.fileCursor.setCursorAtIndex(0);
- MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
+ MockInteractions.keyUpOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.include(element._expandedFilePaths, element.diffs[0].path);
- MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
+ MockInteractions.keyUpOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.notInclude(element._expandedFilePaths, element.diffs[0].path);
element.$.fileCursor.setCursorAtIndex(1);
- MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
+ MockInteractions.keyUpOn(element, 73, null, 'i');
flushAsynchronousOperations();
assert.include(element._expandedFilePaths, element.diffs[1].path);
- MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
+ MockInteractions.keyUpOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
for (const index in element.diffs) {
if (!element.diffs.hasOwnProperty(index)) { continue; }
assert.include(element._expandedFilePaths, element.diffs[index].path);
}
- MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
+ MockInteractions.keyUpOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
for (const index in element.diffs) {
if (!element.diffs.hasOwnProperty(index)) { continue; }
@@ -1338,7 +1338,7 @@
});
test('cursor with individually opened files', () => {
- MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
+ MockInteractions.keyUpOn(element, 73, null, 'i');
flushAsynchronousOperations();
let diffs = renderAndGetNewDiffs(0);
const diffStops = diffs[0].getCursorStops();
@@ -1364,7 +1364,7 @@
// The file cusor is now at 1.
assert.equal(element.$.fileCursor.index, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 73, null, 'i');
+ MockInteractions.keyUpOn(element, 73, null, 'i');
flushAsynchronousOperations();
diffs = renderAndGetNewDiffs(1);
@@ -1379,7 +1379,7 @@
});
test('cursor with toggle all files', () => {
- MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
+ MockInteractions.keyUpOn(element, 73, 'shift', 'i');
flushAsynchronousOperations();
const diffs = renderAndGetNewDiffs(0);
@@ -1426,7 +1426,7 @@
});
test('n key with some files expanded and no shift key', () => {
- MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, null, 'i');
+ MockInteractions.keyUpOn(fileRows[0], 73, null, 'i');
flushAsynchronousOperations();
assert.equal(nextChunkStub.callCount, 1);
@@ -1441,7 +1441,7 @@
});
test('n key with some files expanded and shift key', () => {
- MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, null, 'i');
+ MockInteractions.keyUpOn(fileRows[0], 73, null, 'i');
flushAsynchronousOperations();
assert.equal(nextChunkStub.callCount, 1);
@@ -1455,7 +1455,7 @@
});
test('n key without all files expanded and shift key', () => {
- MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, 'shift', 'i');
+ MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i');
flushAsynchronousOperations();
MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
@@ -1468,7 +1468,7 @@
});
test('n key without all files expanded and no shift key', () => {
- MockInteractions.pressAndReleaseKeyOn(fileRows[0], 73, 'shift', 'i');
+ MockInteractions.keyUpOn(fileRows[0], 73, 'shift', 'i');
flushAsynchronousOperations();
MockInteractions.pressAndReleaseKeyOn(element, 78, 'shift', 'n');
@@ -1542,12 +1542,17 @@
});
test('editing actions', () => {
+ // Edit controls are guarded behind a dom-if initially and not rendered.
+ assert.isNotOk(Polymer.dom(element.root)
+ .querySelector('gr-edit-file-controls'));
+
element.editMode = true;
+ flushAsynchronousOperations();
+
+ // Commit message should not have edit controls.
const editControls =
Polymer.dom(element.root).querySelectorAll('.row:not(.header)')
.map(row => row.querySelector('gr-edit-file-controls'));
-
- // Commit message should not have edit controls.
assert.isTrue(editControls[0].classList.contains('invisible'));
});
@@ -1596,7 +1601,7 @@
commentStub.withArgs('ecf0b9fa_fe1a5f62').returns(
commentStubRes2);
// Expand the commit message diff
- MockInteractions.pressAndReleaseKeyOn(element, 73, 'shift', 'i');
+ MockInteractions.keyUpOn(element, 73, 'shift', 'i');
const diffs = renderAndGetNewDiffs(0);
flushAsynchronousOperations();
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
index a07c724f..cf79a31 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.html
@@ -23,13 +23,13 @@
<template>
<style include="shared-styles">
:host {
+ background-color: var(--dialog-background-color);
display: block;
max-height: 80vh;
overflow-y: auto;
padding: 4.5em 1em 1em 1em;
}
header {
- background: var(--view-background-color);
border-bottom: 1px solid var(--border-color);
left: 0;
padding: 1em;
@@ -58,8 +58,9 @@
margin-bottom: 1em;
}
ul li {
+ border: 1px solid var(--border-color);
border-radius: .2em;
- background: var(--header-background-color);
+ background: var(--chip-background-color);
display: inline-block;
margin: 0 .2em .4em .2em;
padding: .2em .4em;
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
index aae21c0..0ac5019 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
@@ -58,23 +58,23 @@
gr-button {
min-width: 40px;
--gr-button: {
- background-color: var(--button-background-color, #f5f5f5);
+ background-color: var(--button-background-color, var(--table-header-background-color));
color: var(--primary-text-color);
padding: .2em .85em;
@apply(--vote-chip-styles);
}
}
gr-button.iron-selected.max {
- --button-background-color: var(--vote-color-max);
+ --button-background-color: var(--vote-color-approved);
}
gr-button.iron-selected.positive {
- --button-background-color: var(--vote-color-positive);
+ --button-background-color: var(--vote-color-recommended);
}
gr-button.iron-selected.min {
- --button-background-color: var(--vote-color-min);
+ --button-background-color: var(--vote-color-rejected);
}
gr-button.iron-selected.negative {
- --button-background-color: var(--vote-color-negative);
+ --button-background-color: var(--vote-color-disliked);
}
gr-button.iron-selected.neutral {
--button-background-color: var(--vote-color-neutral);
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index a73faa4..a6e1b40 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -144,16 +144,16 @@
padding: 0 .1em;
}
.score.negative {
- background-color: var(--vote-color-negative);
+ background-color: var(--vote-color-disliked);
}
.score.negative.min {
- background-color: var(--vote-color-min);
+ background-color: var(--vote-color-rejected);
}
.score.positive {
- background-color: var(--vote-color-positive);
+ background-color: var(--vote-color-recommended);
}
.score.positive.max {
- background-color: var(--vote-color-max);
+ background-color: var(--vote-color-approved);
}
gr-account-label {
--gr-account-label-text-style: {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index 1e37725..df39362 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -38,6 +38,7 @@
<template>
<style include="shared-styles">
:host {
+ background-color: var(--dialog-background-color);
display: block;
max-height: 100%;
}
@@ -59,7 +60,7 @@
width: 100%;
}
.actions {
- background-color: var(--view-background-color);
+ background-color: var(--dialog-background-color);
bottom: 0;
display: flex;
justify-content: space-between;
@@ -75,6 +76,7 @@
flex-shrink: 0;
}
.peopleContainer {
+ border-top: none;
display: table;
}
.peopleList {
@@ -120,7 +122,7 @@
display: block;
}
.previewContainer gr-formatted-text {
- background: var(--header-background-color);
+ background: var(--table-header-background-color);
padding: 1em;
}
.draftsContainer h3 {
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
index 1ca678932..2201a9a 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.html
@@ -54,10 +54,6 @@
display: flex;
margin-right: 1rem;
}
- .draftsOnly gr-diff-comment-thread,
- .unresolvedOnly gr-diff-comment-thread {
- display: none
- }
.draftsOnly:not(.unresolvedOnly) gr-diff-comment-thread[has-draft],
.unresolvedOnly:not(.draftsOnly) gr-diff-comment-thread[unresolved],
.draftsOnly.unresolvedOnly gr-diff-comment-thread[has-draft][unresolved] {
@@ -68,12 +64,12 @@
<div class="toggleItem">
<paper-toggle-button
id="unresolvedToggle"
- on-change="_toggleUnresolved"></paper-toggle-button>
+ checked="{{_unresolvedOnly}}"></paper-toggle-button>
Only unresolved threads</div>
<div class$="toggleItem draftToggle [[_computeShowDraftToggle(loggedIn)]]">
<paper-toggle-button
id="draftToggle"
- on-change="_toggleDrafts"></paper-toggle-button>
+ checked="{{_draftsOnly}}"></paper-toggle-button>
Only threads with drafts</div>
</div>
<div id="threads">
@@ -82,7 +78,7 @@
</template>
<template
is="dom-repeat"
- items="[[_sortedThreads]]"
+ items="[[_filteredThreads]]"
as="thread"
initial-count="5"
target-framerate="60">
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
index 934f0f2..69d77f6 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
@@ -34,10 +34,24 @@
loggedIn: Boolean,
_sortedThreads: {
type: Array,
- computed: '_computeSortedThreads(threads.*)',
+ },
+ _filteredThreads: {
+ type: Array,
+ computed: '_computeFilteredThreads(_sortedThreads, _unresolvedOnly, ' +
+ '_draftsOnly)',
+ },
+ _unresolvedOnly: {
+ type: Boolean,
+ value: false,
+ },
+ _draftsOnly: {
+ type: Boolean,
+ value: false,
},
},
+ observers: ['_computeSortedThreads(threads.*)'],
+
_computeShowDraftToggle(loggedIn) {
return loggedIn ? 'show' : '';
},
@@ -49,36 +63,60 @@
* - Resolved threads with drafts (reverse chronological)
* - Resolved threads without drafts (reverse chronological)
* @param {!Object} changeRecord
- * @return {!Array}
*/
_computeSortedThreads(changeRecord) {
const threads = changeRecord.base;
if (!threads) { return []; }
- return threads.map(this._getThreadWithSortInfo).sort((c1, c2) => {
- const c1Date = c1.__date || util.parseDate(c1.updated);
- const c2Date = c2.__date || util.parseDate(c2.updated);
- const dateCompare = c2Date - c1Date;
- if (c2.unresolved || c1.unresolved) {
- if (!c1.unresolved) { return 1; }
- if (!c2.unresolved) { return -1; }
- }
- if (c2.hasDraft || c1.hasDraft) {
- if (!c1.hasDraft) { return 1; }
- if (!c2.hasDraft) { return -1; }
- }
+ this._updateSortedThreads(threads);
+ },
- if (dateCompare === 0 && (!c1.id || !c1.id.localeCompare)) {
- return 0;
+ _updateSortedThreads(threads) {
+ this._sortedThreads =
+ threads.map(this._getThreadWithSortInfo).sort((c1, c2) => {
+ const c1Date = c1.__date || util.parseDate(c1.updated);
+ const c2Date = c2.__date || util.parseDate(c2.updated);
+ const dateCompare = c2Date - c1Date;
+ if (c2.unresolved || c1.unresolved) {
+ if (!c1.unresolved) { return 1; }
+ if (!c2.unresolved) { return -1; }
+ }
+ if (c2.hasDraft || c1.hasDraft) {
+ if (!c1.hasDraft) { return 1; }
+ if (!c2.hasDraft) { return -1; }
+ }
+
+ if (dateCompare === 0 && (!c1.id || !c1.id.localeCompare)) {
+ return 0;
+ }
+ return dateCompare ? dateCompare : c1.id.localeCompare(c2.id);
+ });
+ },
+
+ _computeFilteredThreads(sortedThreads, unresolvedOnly, draftsOnly) {
+ return sortedThreads.filter(c => {
+ if (draftsOnly) {
+ return c.hasDraft;
+ } else if (unresolvedOnly) {
+ return c.unresolved;
+ } else {
+ return c;
}
- return dateCompare ? dateCompare : c1.id.localeCompare(c2.id);
}).map(threadInfo => threadInfo.thread);
},
_getThreadWithSortInfo(thread) {
const lastComment = thread.comments[thread.comments.length - 1] || {};
+
+ const lastNonDraftComment =
+ (lastComment.__draft && thread.comments.length > 1) ?
+ thread.comments[thread.comments.length - 2] :
+ lastComment;
+
return {
thread,
- unresolved: !!lastComment.unresolved,
+ // Use the unresolved bit for the last non draft comment. This is what
+ // anybody other than the current user would see.
+ unresolved: !!lastNonDraftComment.unresolved,
hasDraft: !!lastComment.__draft,
updated: lastComment.updated,
};
@@ -100,6 +138,10 @@
},
_handleCommentsChanged(e) {
+ // Reset threads so thread computations occur on deep array changes to
+ // threads comments that are not observed naturally.
+ this._updateSortedThreads(this.threads);
+
this.dispatchEvent(new CustomEvent('thread-list-modified',
{detail: {rootId: e.detail.rootId, path: e.detail.path}}));
},
@@ -107,13 +149,5 @@
_isOnParent(side) {
return !!side;
},
-
- _toggleUnresolved() {
- this.$.threads.classList.toggle('unresolvedOnly');
- },
-
- _toggleDrafts() {
- this.$.threads.classList.toggle('draftsOnly');
- },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
index 53557c6..804446a 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
@@ -37,15 +37,6 @@
let element;
let sandbox;
let threadElements;
- const computeVisibleNumber = threads => {
- let count = 0;
- for (const thread of threads) {
- if (getComputedStyle(thread).display !== 'none') {
- count++;
- }
- }
- return count;
- };
setup(() => {
sandbox = sinon.sandbox.create();
@@ -74,7 +65,7 @@
in_reply_to: 'ecf0b9fa_fe1a5f62',
updated: '2018-02-13 22:48:48.018000000',
message: 'draft',
- unresolved: true,
+ unresolved: false,
__draft: true,
__draftID: '0.m683trwff68',
__editing: false,
@@ -196,54 +187,67 @@
});
test('there are five threads by default', () => {
- assert.equal(computeVisibleNumber(threadElements), 5);
+ assert.equal(Polymer.dom(element.root)
+ .querySelectorAll('gr-diff-comment-thread').length, 5);
});
test('_computeSortedThreads', () => {
assert.equal(element._sortedThreads.length, 5);
// Draft and unresolved
- assert.equal(element._sortedThreads[0].rootId, 'ecf0b9fa_fe1a5f62');
+ assert.equal(element._sortedThreads[0].thread.rootId,
+ 'ecf0b9fa_fe1a5f62');
// unresolved
- assert.equal(element._sortedThreads[1].rootId, 'scaddf38_44770ec1');
+ assert.equal(element._sortedThreads[1].thread.rootId,
+ 'scaddf38_44770ec1');
// unresolved
- assert.equal(element._sortedThreads[2].rootId, '8caddf38_44770ec1');
+ assert.equal(element._sortedThreads[2].thread.rootId,
+ '8caddf38_44770ec1');
// resolved and draft
- assert.equal(element._sortedThreads[3].rootId, 'zcf0b9fa_fe1a5f62');
+ assert.equal(element._sortedThreads[3].thread.rootId,
+ 'zcf0b9fa_fe1a5f62');
// resolved
- assert.equal(element._sortedThreads[4].rootId, '09a9fb0a_1484e6cf');
+ assert.equal(element._sortedThreads[4].thread.rootId,
+ '09a9fb0a_1484e6cf');
});
test('thread removal', () => {
threadElements[1].fire('thread-discard', {rootId: 'scaddf38_44770ec1'});
flushAsynchronousOperations();
assert.equal(element._sortedThreads.length, 4);
- assert.equal(element._sortedThreads[0].rootId, 'ecf0b9fa_fe1a5f62');
+ assert.equal(element._sortedThreads[0].thread.rootId,
+ 'ecf0b9fa_fe1a5f62');
// unresolved
- assert.equal(element._sortedThreads[1].rootId, '8caddf38_44770ec1');
+ assert.equal(element._sortedThreads[1].thread.rootId,
+ '8caddf38_44770ec1');
// resolved and draft
- assert.equal(element._sortedThreads[2].rootId, 'zcf0b9fa_fe1a5f62');
+ assert.equal(element._sortedThreads[2].thread.rootId,
+ 'zcf0b9fa_fe1a5f62');
// resolved
- assert.equal(element._sortedThreads[3].rootId, '09a9fb0a_1484e6cf');
+ assert.equal(element._sortedThreads[3].thread.rootId,
+ '09a9fb0a_1484e6cf');
});
test('toggle unresolved only shows unressolved comments', () => {
MockInteractions.tap(element.$.unresolvedToggle);
flushAsynchronousOperations();
- assert.equal(computeVisibleNumber(threadElements), 3);
+ assert.equal(Polymer.dom(element.root)
+ .querySelectorAll('gr-diff-comment-thread').length, 3);
});
test('toggle drafts only shows threads with draft comments', () => {
MockInteractions.tap(element.$.draftToggle);
flushAsynchronousOperations();
- assert.equal(computeVisibleNumber(threadElements), 2);
+ assert.equal(Polymer.dom(element.root)
+ .querySelectorAll('gr-diff-comment-thread').length, 2);
});
test('toggle drafts and unresolved only shows threads with drafts and ' +
- 'unresolved', () => {
+ 'publicly unresolved ', () => {
MockInteractions.tap(element.$.draftToggle);
MockInteractions.tap(element.$.unresolvedToggle);
flushAsynchronousOperations();
- assert.equal(computeVisibleNumber(threadElements), 1);
+ assert.equal(Polymer.dom(element.root)
+ .querySelectorAll('gr-diff-comment-thread').length, 2);
});
test('modification events are consumed and displatched', () => {
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
index 0b78df5..e4cb243 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
@@ -55,10 +55,11 @@
padding-top: 1em;
}
.key {
+ background-color: var(--chip-background-color);
+ border: 1px solid var(--border-color);
+ border-radius: 3px;
display: inline-block;
font-family: var(--font-family-bold);
- border-radius: 3px;
- background-color: #f1f2f3;
padding: .1em .5em;
text-align: center;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
index de12ea4..0da1f00 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.html
@@ -34,17 +34,18 @@
padding: .5em .7em;
}
#container {
- background-color: #fcfad6;
- border: 1px solid #bbb;
+ background-color: var(--comment-background-color);
+ border: 1px solid var(--border-color);
+ color: var(--comment-text-color);
display: block;
margin-bottom: 1px;
white-space: normal;
}
#container.unresolved {
- background-color: #fcfaa6;
+ background-color: var(--unresolved-comment-background-color);
}
#commentInfoContainer {
- border-top: 1px dotted #bbb;
+ border-top: 1px dotted var(--border-color);
display: flex;
}
#unresolvedLabel {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index 75a67bf..29b780a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -25,6 +25,7 @@
<link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-formatted-text/gr-formatted-text.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-storage/gr-storage.html">
@@ -76,7 +77,7 @@
}
.draftLabel,
.draftTooltip {
- color: #999;
+ color: var(--deemphasized-text-color);
display: none;
}
.date {
@@ -151,10 +152,16 @@
margin-left: .4em;
}
.robotId {
- color: #808080;
+ color: var(--deemphasized-text-color);
margin-bottom: .8em;
margin-top: -.4em;
}
+ .robotIcon {
+ margin-right: .2em;
+ /* because of the antenna of the robot, it looks off center even when it
+ is centered. artificially adjust margin to account for this. */
+ margin-top: -.3em;
+ }
.runIdInformation {
margin: .7em 0;
}
@@ -168,7 +175,7 @@
display: none;
}
label.show-hide {
- color: var(--primary-text-color);
+ color: var(--comment-text-color);
cursor: pointer;
display: block;
font-size: .8rem;
@@ -201,7 +208,7 @@
margin: 0;
}
.resolve label {
- color: #333;
+ color: var(--comment-text-color);
font-size: var(--font-size-small);
}
gr-confirm-dialog .main {
@@ -270,6 +277,7 @@
<div class="body">
<template is="dom-if" if="[[comment.robot_id]]">
<div class="robotId" hidden$="[[collapsed]]">
+ <iron-icon class="robotIcon" icon="gr-icons:robot"></iron-icon>
[[comment.robot_id]]
</div>
</template>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
index 68c4d39..375e598 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
@@ -44,6 +44,11 @@
.actions {
padding: 1em 1.5em;
}
+ .header,
+ .mainContainer,
+ .actions {
+ background-color: var(--dialog-background-color);
+ }
.header {
border-bottom: 1px solid var(--border-color);
font-family: var(--font-family-bold);
@@ -58,7 +63,7 @@
width: 20em;
}
.pref:hover {
- background-color: #ebf5fb;
+ background-color: var(--hover-background-color);
}
.pref label {
cursor: pointer;
@@ -69,10 +74,6 @@
display: flex;
justify-content: flex-end;
}
- .beta {
- font-family: var(--font-family-bold);
- color: #888;
- }
gr-button {
margin-left: 1em;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 3972751..81c6d99 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -32,16 +32,6 @@
<dom-module id="gr-diff">
<template>
<style include="shared-styles">
- :host {
- --light-remove-highlight-color: #fee;
- --dark-remove-highlight-color: rgba(255, 0, 0, 0.15);
- --light-add-highlight-color: #efe;
- --dark-add-highlight-color: rgba(0, 255, 0, 0.15);
- --light-rebased-remove-highlight-color: #fff6ea;
- --dark-rebased-remove-highlight-color: rgba(255, 139, 6, 0.15);
- --light-rebased-add-highlight-color: #edfffa;
- --dark-rebased-add-highlight-color: rgba(11, 255, 155, 0.15);
- }
:host(.no-left) .sideBySide ::content .left,
:host(.no-left) .sideBySide ::content .left + td,
:host(.no-left) .sideBySide ::content .right:not([data-value]),
@@ -54,7 +44,7 @@
@apply --diff-container-styles;
}
.diffContainer.hiddenscroll {
- padding-bottom: .8em;
+ margin-bottom: .8em;
}
table {
border-collapse: collapse;
@@ -62,14 +52,14 @@
table-layout: fixed;
}
.lineNum {
- background-color: var(--header-background-color);
+ background-color: var(--table-header-background-color);
}
.image-diff .gr-diff {
text-align: center;
}
.image-diff img {
max-width: 50em;
- outline: 1px solid #ccc;
+ outline: 1px solid var(--border-color);
}
.image-diff label,
.binary-diff label {
@@ -82,7 +72,7 @@
.diff-row.target-row.target-side-left .lineNum.left,
.diff-row.target-row.target-side-right .lineNum.right,
.diff-row.target-row.unified .lineNum {
- background-color: #BBDEFB;
+ background-color: var(--diff-selection-background-color);
color: var(--primary-text-color);
}
.blank,
@@ -151,20 +141,21 @@
background-color: var(--dark-rebased-remove-highlight-color);
}
.dueToRebase .content.remove {
- background-color: var(--light-rebased-remove-highlight-color);
+ background-color: var(--light-remove-add-highlight-color);
}
.content .contentText:empty:after {
/* Newline, to ensure empty lines are one line-height tall. */
content: '\A';
}
.contextControl {
- background-color: #fff7d4;
- border: 1px solid #f6e6a5;
+ background-color: var(--diff-context-control-color);
+ border: 1px solid var(--diff-context-control-border-color);
}
.contextControl gr-button {
display: inline-block;
text-decoration: none;
--gr-button: {
+ color: var(--deemphasized-text-color);
padding: .2em;
}
}
@@ -172,7 +163,7 @@
text-align: center;
}
.displayLine .diff-row.target-row td {
- box-shadow: inset 0 -1px #bbb;
+ box-shadow: inset 0 -1px var(--border-color);
}
.br:after {
/* Line feed */
@@ -182,17 +173,18 @@
display: inline-block;
}
.tab-indicator:before {
- color: #C62828;
+ color: var(--diff-tab-indicator-color);
/* >> character */
content: '\00BB';
}
.trailing-whitespace {
border-radius: .4em;
- background-color: #FF9AD2;
+ background-color: var(--diff-trailing-whitespace-indicator);
}
#diffHeader {
- background-color: #F9F9F9;
- color: #2A00FF;
+ background-color: var(--table-header-background-color);
+ border-bottom: 1px solid var(--border-color);
+ color: var(--link-color);
font-family: var(--monospace-font-family);
font-size: var(--font-size, var(--font-size-small));
padding: 0.5em 0 0.5em 4em;
@@ -210,7 +202,7 @@
display: block;
}
.target-row td.blame {
- background: var(--header-background-color);
+ background: var(--diff-selection-background-color);
}
col.blame {
display: none;
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
index 3209d63..633530f 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.html
@@ -29,9 +29,6 @@
position: absolute;
white-space: nowrap;
}
- #tooltip {
- --tooltip-background-color: rgba(22, 22, 22, .9);
- }
</style>
<gr-tooltip
id="tooltip"
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 92c2eb9..f8db343 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -25,6 +25,7 @@
'text/css': 'css',
'text/html': 'html',
'text/javascript': 'js',
+ 'text/jsx': 'jsx',
'text/x-c': 'cpp',
'text/x-c++src': 'cpp',
'text/x-clojure': 'clojure',
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
index 6597f4a..c2d6cdf 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
@@ -38,7 +38,7 @@
background-color: var(--view-background-color);
}
gr-fixed-panel {
- background-color: #ebf5fb;
+ background-color: var(--edit-mode-background-color);
border-bottom: 1px var(--border-color) solid;
z-index: 1;
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index d31825f..daa86fa 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -33,7 +33,7 @@
</test-fixture>
<script>
- suite('gr-diff tests', () => {
+ suite('gr-plugin-host tests', () => {
let element;
let sandbox;
let url;
@@ -54,8 +54,8 @@
sandbox.stub(Gerrit, '_setPluginsCount');
element.config = {
plugin: {
- html_resource_paths: ['foo/bar', 'baz'],
- js_resource_paths: ['42'],
+ html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
+ js_resource_paths: ['plugins/42'],
},
};
assert.isTrue(Gerrit._setPluginsCount.calledWith(3));
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
index 1164375..2fe07ca 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
@@ -79,6 +79,7 @@
If you lose it, you will need to generate a new one.
</section>
<gr-button
+ link
class="closeButton"
on-tap="_closeOverlay">Close</gr-button>
</div>
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html
index b1b91cc..ab12403 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.html
@@ -74,12 +74,14 @@
<td>[[_getStatusLabel(key.valid)]]</td>
<td>
<gr-button
+ link
on-tap="_showKey"
data-index$="[[index]]"
link>Click to View</gr-button>
</td>
<td>
<gr-button
+ link
data-index$="[[index]]"
on-tap="_handleDeleteKey">Delete</gr-button>
</td>
@@ -123,6 +125,7 @@
</section>
<gr-button
id="addButton"
+ link
disabled$="[[_computeAddButtonDisabled(_newKey)]]"
on-tap="_handleAddKey">Add new SSH key</gr-button>
</fieldset>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
index f04caaa..89ab8fe 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.html
@@ -31,7 +31,7 @@
}
.container {
align-items: center;
- background: var(--header-background-color);
+ background: var(--chip-background-color);
border-radius: .75em;
display: inline-flex;
padding: 0 .5em;
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
index b47d5a4..5817fb7 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
@@ -29,7 +29,7 @@
* HOW THEY ARE USED IN THE CODE.
*/
:host([toast]) {
- background-color: #333;
+ background-color: var(--tooltip-background-color);
bottom: 1.25rem;
border-radius: 3px;
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
@@ -45,6 +45,7 @@
transform: translateY(0);
}
.text {
+ color: var(--tooltip-text-color);
display: inline-block;
max-height: 10rem;
max-width: 80vw;
@@ -52,7 +53,7 @@
word-break: break-all;
}
.action {
- color: #a1c2fa;
+ color: var(--link-color);
font-family: var(--font-family-bold);
margin-left: 1em;
text-decoration: none;
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 1eaad4e..f39398f 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -312,6 +312,12 @@
// For any normal keypress, return focus to the input to allow for
// unbroken user input.
this.$.input.inputElement.focus();
+
+ // Since this has been a normal keypress, the suggestions will have
+ // been based on a previous input. Clear them. This prevents an
+ // outdated suggestion from being used if the input keystroke is
+ // immediately followed by a commit keystroke. @see Issue 8655
+ this._suggestions = [];
}
this.fire('input-keydown', {keyCode: e.keyCode, input: this.$.input});
},
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 872f79f..d257cdd 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -379,6 +379,21 @@
assert.isTrue(commitStub.calledOnce);
});
+ test('issue 8655', () => {
+ function makeSuggestion(s) { return {name: s, text: s, value: s}; }
+ const keydownSpy = sandbox.spy(element, '_handleKeydown');
+ element.setText('file:');
+ element._suggestions =
+ [makeSuggestion('file:'), makeSuggestion('-file:')];
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 88, null, 'x');
+ // Must set the value, because the MockInteraction does not.
+ element.$.input.value = 'file:x';
+ assert.isTrue(keydownSpy.calledOnce);
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null, 'enter');
+ assert.isTrue(keydownSpy.calledTwice);
+ assert.equal(element.text, 'file:x');
+ });
+
suite('focus', () => {
let commitSpy;
let focusSpy;
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
index 1656c8e..92a7d5d 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
@@ -23,6 +23,7 @@
<template>
<style include="shared-styles">
:host {
+ color: var(--primary-text-color);
display: block;
max-height: 90vh;
}
@@ -60,7 +61,7 @@
<main><slot name="main"></slot></main>
<footer>
<gr-button link on-tap="_handleCancelTap">[[cancelLabel]]</gr-button>
- <gr-button link primary on-tap="_handleConfirm" disabled="[[disabled]]">
+ <gr-button id="confirm" link primary on-tap="_handleConfirm" disabled="[[disabled]]">
[[confirmLabel]]
</gr-button>
</footer>
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.js b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.js
index 8ddb85d..b8d137b 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.js
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.js
@@ -70,5 +70,9 @@
_handleKeydown(e) {
if (this.confirmOnEnter && e.keyCode === 13) { this._handleConfirm(e); }
},
+
+ resetFocus() {
+ this.$.confirm.focus();
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
index 0aa9ba6..3abe28b 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
@@ -77,7 +77,7 @@
border-bottom: 1px solid var(--border-color);
}
.bottomContent {
- color: rgba(0,0,0,.54);
+ color: var(--deemphasized-text-color);
font-size: var(--font-size-small);
/*
* Should be 16px when the base font size is 13px (browser default of
@@ -103,7 +103,7 @@
}
}
gr-date-formatter {
- color: rgba(0,0,0,.54);
+ color: var(--deemphasized-text-color);
margin-left: 2em;
white-space: nowrap;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
index 96066de..8581396 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
@@ -76,6 +76,7 @@
--paper-input-container-input: {
font-size: var(--font-size-normal);
}
+ --paper-input-container-focus-color: var(--link-color);
}
</style>
<label
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 a3e1c02..cbae987 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -40,6 +40,8 @@
<g id="chevron-left"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></g>
<!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.html -->
<g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></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 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 -->
@@ -48,6 +50,9 @@
<g id="content-copy"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></g>
<!-- This is a custom PolyGerrit SVG -->
<g id="check"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></g>
+ <!-- This is a custom PolyGerrit SVG -->
+ <g id="robot"><path d="M4.137453,5.61015591 L4.54835569,1.5340419 C4.5717665,1.30180904 4.76724872,1.12504213 5.00065859,1.12504213 C5.23327176,1.12504213 5.42730868,1.30282046 5.44761309,1.53454578 L5.76084628,5.10933916 C6.16304484,5.03749412 6.57714381,5 7,5 L17,5 C20.8659932,5 24,8.13400675 24,12 L24,15.1250421 C24,18.9910354 20.8659932,22.1250421 17,22.1250421 L7,22.1250421 C3.13400675,22.1250421 2.19029351e-15,18.9910354 0,15.1250421 L0,12 C-3.48556243e-16,9.15382228 1.69864167,6.70438358 4.137453,5.61015591 Z M5.77553049,6.12504213 C3.04904264,6.69038358 1,9.10590202 1,12 L1,15.1250421 C1,18.4387506 3.6862915,21.1250421 7,21.1250421 L17,21.1250421 C20.3137085,21.1250421 23,18.4387506 23,15.1250421 L23,12 C23,8.6862915 20.3137085,6 17,6 L7,6 C6.60617231,6 6.2212068,6.03794347 5.84855971,6.11037415 L5.84984496,6.12504213 L5.77553049,6.12504213 Z M6.93003717,6.95027711 L17.1232083,6.95027711 C19.8638332,6.95027711 22.0855486,9.17199258 22.0855486,11.9126175 C22.0855486,14.6532424 19.8638332,16.8749579 17.1232083,16.8749579 L6.93003717,16.8749579 C4.18941226,16.8749579 1.9676968,14.6532424 1.9676968,11.9126175 C1.9676968,9.17199258 4.18941226,6.95027711 6.93003717,6.95027711 Z M7.60124392,14.0779303 C9.03787127,14.0779303 10.2024878,12.9691885 10.2024878,11.6014862 C10.2024878,10.2337839 9.03787127,9.12504213 7.60124392,9.12504213 C6.16461657,9.12504213 5,10.2337839 5,11.6014862 C5,12.9691885 6.16461657,14.0779303 7.60124392,14.0779303 Z M16.617997,14.1098288 C18.0638768,14.1098288 19.2359939,12.9939463 19.2359939,11.6174355 C19.2359939,10.2409246 18.0638768,9.12504213 16.617997,9.12504213 C15.1721172,9.12504213 14,10.2409246 14,11.6174355 C14,12.9939463 15.1721172,14.1098288 16.617997,14.1098288 Z M9.79751216,18.1250421 L15,18.1250421 L15,19.1250421 C15,19.6773269 14.5522847,20.1250421 14,20.1250421 L10.7975122,20.1250421 C10.2452274,20.1250421 9.79751216,19.6773269 9.79751216,19.1250421 L9.79751216,18.1250421 Z"></path>
+ </g>
</defs>
</svg>
</iron-iconset-svg>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index 42bfc6a..7d91f41 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -351,6 +351,25 @@
return Gerrit.awaitPluginsLoaded();
});
+ test('multiple ui plugins per java plugin', () => {
+ const file1 = 'http://test.com/plugins/qaz/static/foo.nocache.js';
+ const file2 = 'http://test.com/plugins/qaz/static/bar.js';
+ Gerrit._setPluginsPending([file1, file2]);
+ Gerrit.install(() => {}, '0.1', file1);
+ Gerrit.install(() => {}, '0.1', file2);
+ return Gerrit.awaitPluginsLoaded();
+ });
+
+ test('plugin install errors shows toasts', () => {
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+ Gerrit._setPluginsCount(1);
+ Gerrit.install(() => {}, '0.0pre-alpha');
+ return Gerrit.awaitPluginsLoaded().then(() => {
+ assert.isTrue(alertStub.calledOnce);
+ });
+ });
+
test('installGwt calls _pluginInstalled', () => {
sandbox.stub(Gerrit, '_pluginInstalled');
Gerrit.installGwt('http://test.com/plugins/testplugin/static/test.js');
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 60e07e0..ae368bc 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -34,7 +34,7 @@
CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item',
};
- const PLUGIN_LOADING_TIMEOUT_MS = 10000;
+ const PLUGIN_LOADING_TIMEOUT_MS = 60000;
let _restAPI;
const getRestAPI = () => {
@@ -552,11 +552,6 @@
};
Gerrit._pluginLoadingTimeout = function() {
- document.dispatchEvent(new CustomEvent('show-alert', {
- detail: {
- message: 'Plugins loading timeout. Check the console for errors.',
- },
- }));
console.error(`Failed to load plugins: ${Object.keys(_pluginsPending)}`);
Gerrit._setPluginsPending([]);
};
@@ -566,7 +561,7 @@
o[getPluginNameFromUrl(url)] = url;
return o;
}, {});
- Gerrit._setPluginsCount(plugins.length);
+ Gerrit._setPluginsCount(Object.keys(_pluginsPending).length);
};
Gerrit._setPluginsCount = function(count) {
@@ -580,7 +575,12 @@
};
Gerrit._pluginInstallError = function(message) {
- console.log(`Plugin install error: ${message}`);
+ document.dispatchEvent(new CustomEvent('show-alert', {
+ detail: {
+ message: `Plugin install error: ${message}`,
+ },
+ }));
+ console.info(`Plugin install error: ${message}`);
Gerrit._setPluginsCount(_pluginsPendingCount - 1);
};
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
index 5d7a8a8..fab562a 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
@@ -31,7 +31,7 @@
}
.container {
align-items: center;
- background: var(--header-background-color);
+ background: var(--chip-background-color);
border-radius: .75em;
display: inline-flex;
padding: 0 .5em;
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
index 71999acaf..7df77ca 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.html
@@ -28,10 +28,15 @@
font-size: var(--font-size-normal);
max-width: 25em;
}
+ #filter:focus {
+ outline: none;
+ }
#topContainer {
+ align-items: center;
display: flex;
+ height: 3rem;
justify-content: space-between;
- margin: 1em;
+ margin: 0 1em;
}
#createNewContainer:not(.show) {
display: none;
@@ -44,18 +49,24 @@
text-decoration: underline;
}
nav {
- padding: .5em 0;
- text-align: center;
+ align-items: center;
+ display: flex;
+ height: 3rem;
+ justify-content: flex-end;
+ margin-right: 20px;
}
- nav a {
- display: inline-block;
+ nav,
+ iron-icon {
+ color: var(--deemphasized-text-color);
}
- nav a:first-of-type {
- margin-right: .5em;
+ iron-icon {
+ height: 1.85rem;
+ margin-left: 16px;
+ width: 1.85rem;
}
</style>
<div id="topContainer">
- <div>
+ <div class="filterContainer">
<label>Filter:</label>
<input is="iron-input"
type="text"
@@ -64,7 +75,7 @@
</div>
<div id="createNewContainer"
class$="[[_computeCreateClass(createNew)]]">
- <gr-button id="createNew" on-tap="_createNewItem">
+ <gr-button primary link id="createNew" on-tap="_createNewItem">
Create New
</gr-button>
</div>
@@ -73,11 +84,14 @@
<nav>
<a id="prevArrow"
href$="[[_computeNavLink(offset, -1, itemsPerPage, filter, path)]]"
- hidden$="[[_hidePrevArrow(offset)]]" hidden>← Prev</a>
+ hidden$="[[_hidePrevArrow(loading, offset)]]" hidden>
+ <iron-icon icon="gr-icons:chevron-left"></iron-icon>
+ </a>
<a id="nextArrow"
href$="[[_computeNavLink(offset, 1, itemsPerPage, filter, path)]]"
hidden$="[[_hideNextArrow(loading, items)]]" hidden>
- Next →</a>
+ <iron-icon icon="gr-icons:chevron-right"></iron-icon>
+ </a>
</nav>
</template>
<script src="gr-list-view.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
index 53691fc..8b83eb3 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.js
@@ -84,8 +84,8 @@
return createNew ? 'show' : '';
},
- _hidePrevArrow(offset) {
- return offset === 0;
+ _hidePrevArrow(loading, offset) {
+ return loading || offset === 0;
},
_hideNextArrow(loading, items) {
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
index d1cf80a..09e68dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
@@ -114,11 +114,12 @@
});
test('prev button', () => {
+ assert.isTrue(element._hidePrevArrow(true, 0));
flush(() => {
let offset = 0;
- assert.isTrue(element._hidePrevArrow(offset));
+ assert.isTrue(element._hidePrevArrow(false, offset));
offset = 5;
- assert.isFalse(element._hidePrevArrow(offset));
+ assert.isFalse(element._hidePrevArrow(false, offset));
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
index ea94086..e94b655 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
@@ -23,7 +23,7 @@
<template>
<style include="shared-styles">
:host {
- background: var(--view-background-color);
+ background: var(--dialog-background-color);
box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
index 909cb9a..9ccff600 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
@@ -35,9 +35,7 @@
_handleBodyScroll() {
if (this._headerHeight === undefined) {
let top = this._getOffsetTop(this);
- // Don't want to include the element that wraps around the nav, start
- // with its parent.
- for (let offsetParent = this._getOffsetParent(this.offsetParent);
+ for (let offsetParent = this.offsetParent;
offsetParent;
offsetParent = this._getOffsetParent(offsetParent)) {
top += this._getOffsetTop(offsetParent);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 86b0996..9dc51ba 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1228,7 +1228,7 @@
repoInfo);
},
- setProjectAccessRightsForReview(projectName, projectInfo) {
+ setRepoAccessRightsForReview(projectName, projectInfo) {
return this.send(
'PUT', `/projects/${encodeURIComponent(projectName)}/access:review`,
projectInfo).then(response => this.getResponseObject(response));
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
index cf39b5a..10c9111 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.html
@@ -41,6 +41,7 @@
display: inline-block
}
#textarea {
+ background-color: var(--view-background-color);
width: 100%;
}
#hiddenText #emojiSuggestions {
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
index 12a8f1c..9947d61 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip.html
@@ -25,9 +25,9 @@
--gr-tooltip-arrow-size: .5em;
--gr-tooltip-arrow-center-offset: 0;
- background-color: var(--tooltip-background-color, #333);
+ background-color: var(--tooltip-background-color);
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
- color: #fff;
+ color: var(--tooltip-text-color);
font-size: var(--font-size-small);
position: absolute;
z-index: 1000;
@@ -53,11 +53,11 @@
width: 0;
}
.arrowPositionAbove {
- border-top: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color, #333);
+ border-top: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color);
bottom: -var(--gr-tooltip-arrow-size);
}
.arrowPositionBelow {
- border-bottom: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color, #333);
+ border-bottom: var(--gr-tooltip-arrow-size) solid var(--tooltip-background-color);
top: -var(--gr-tooltip-arrow-size);
}
</style>
diff --git a/polygerrit-ui/app/run_template_test.sh b/polygerrit-ui/app/run_template_test.sh
new file mode 100755
index 0000000..4cd6e7f
--- /dev/null
+++ b/polygerrit-ui/app/run_template_test.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+if [[ -z "${TEMPLATE_NO_DEFAULT}" ]]; then
+bazel test \
+ --test_env="HOME=$HOME" \
+ //polygerrit-ui/app:all
+ --test_tag_filters=template \
+ "$@" \
+ --test_output errors \
+ --nocache_test_results
+else
+bazel test \
+ --test_env="HOME=$HOME" \
+ "$@" \
+ --test_output errors \
+ --nocache_test_results
+fi
diff --git a/polygerrit-ui/app/styles/app-theme.html b/polygerrit-ui/app/styles/app-theme.html
index 9642019..6112035 100644
--- a/polygerrit-ui/app/styles/app-theme.html
+++ b/polygerrit-ui/app/styles/app-theme.html
@@ -27,8 +27,8 @@
--border-color: #ddd;
/* Following are not part of plugin API. */
- --selection-background-color: #f1f5fb;
- --hover-background-color: #e8effa;
+ --selection-background-color: rgba(161, 194, 250, 0.1);
+ --hover-background-color: rgba(161, 194, 250, 0.2);
--expanded-background-color: #eee;
--view-background-color: #fff;
--default-horizontal-margin: 1rem;
@@ -42,6 +42,8 @@
--table-header-background-color: #fafafa;
--table-subheader-background-color: #eaeaea;
+ --chip-background-color: var(--header-background-color);
+
--dropdown-background-color: #fff;
/* Font sizes */
@@ -56,6 +58,7 @@
--secondary-button-text-color: #212121;
--default-button-background-color: #fff;
--default-button-text-color: var(--link-color);
+ --dialog-background-color: #fff;
/* Used for both the old patchset header and for indicating that a particular
change message was selected. */
@@ -63,9 +66,38 @@
--error-text-color: red;
+ --vote-color-approved: #9fcc6b;
+ --vote-color-recommended: #c9dfaf;
+ --vote-color-rejected: #f7a1ad;
+ --vote-color-disliked: #f7c4cb;
+ --vote-color-neutral: #ebf5fb;
+
+ /* Diff colors */
+ --diff-selection-background-color: #c7dbf9;
+ --light-remove-highlight-color: #fee;
+ --light-add-highlight-color: #efe;
+ --light-remove-add-highlight-color: #fff6ea;
+ --light-rebased-add-highlight-color: #edfffa;
+ --dark-remove-highlight-color: rgba(255, 0, 0, 0.15);
+ --dark-add-highlight-color: rgba(0, 255, 0, 0.15);
+ --dark-rebased-remove-highlight-color: rgba(255, 139, 6, 0.15);
+ --dark-rebased-add-highlight-color: rgba(11, 255, 155, 0.15);
+ --diff-context-control-color: #fff7d4;
+ --diff-context-control-border-color: #f6e6a5;
+ --diff-tab-indicator-color: var(--deemphasized-text-color);
+ --diff-trailing-whitespace-indicator: #ff9ad2;
--diff-highlight-range-color: rgba(255, 213, 0, 0.5);
--diff-highlight-range-hover-color: rgba(255, 255, 0, 0.5);
+ --comment-text-color: #000;
+ --comment-background-color: #fcfad6;
+ --unresolved-comment-background-color: #fcfaa6;
+
+ --edit-mode-background-color: #ebf5fb;
+
+ --tooltip-background-color: #333;
+ --tooltip-text-color: #fff;
+
--syntax-default-color: var(--primary-text-color);
--syntax-meta-color: #FF1717;
--syntax-keyword-color: #9E0069;
diff --git a/polygerrit-ui/app/styles/gr-form-styles.html b/polygerrit-ui/app/styles/gr-form-styles.html
index 88c75c8..f4b367b 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.html
+++ b/polygerrit-ui/app/styles/gr-form-styles.html
@@ -59,9 +59,6 @@
.gr-form-styles .emptyHeader {
text-align: right;
}
- .gr-form-styles tbody tr:nth-child(even):not(.loading) {
- background-color: #f4f4f4;
- }
.gr-form-styles table {
width: 50em;
}
diff --git a/polygerrit-ui/app/styles/gr-page-nav-styles.html b/polygerrit-ui/app/styles/gr-page-nav-styles.html
index 6d62f23..6eee5a8 100644
--- a/polygerrit-ui/app/styles/gr-page-nav-styles.html
+++ b/polygerrit-ui/app/styles/gr-page-nav-styles.html
@@ -26,7 +26,7 @@
display: block;
padding: 0 2em;
}
- .navStyles li a {
+ .navStyles li a {
display: block;
overflow: hidden;
text-overflow: ellipsis;
@@ -50,8 +50,8 @@
}
.navStyles .selected {
background-color: var(--view-background-color);
- border-bottom: 1px dotted #808080;
- border-top: 1px dotted #808080;
+ border-bottom: 1px solid var(--border-color);
+ border-top: 1px solid var(--border-color);
font-family: var(--font-family-bold);
}
.navStyles a {
diff --git a/polygerrit-ui/app/styles/gr-table-styles.html b/polygerrit-ui/app/styles/gr-table-styles.html
index 2bcd743..5e40735 100644
--- a/polygerrit-ui/app/styles/gr-table-styles.html
+++ b/polygerrit-ui/app/styles/gr-table-styles.html
@@ -18,29 +18,64 @@
<dom-module id="gr-table-styles">
<template>
<style>
- .genericList .loading {
- display: none;
- }
.genericList {
border-collapse: collapse;
width: 100%;
}
- .genericList tr.table {
+ .genericList td {
+ height: 2.25rem;
+ padding: .3rem 0;
+ vertical-align: middle;
+ }
+ .genericList tr {
border-bottom: 1px solid var(--border-color);
}
+ .genericList th {
+ white-space: nowrap;
+ }
+ .genericList th,
+ .genericList td {
+ padding-right: 1rem;
+ }
+ .genericList tr th:first-of-type,
+ .genericList tr td:first-of-type {
+ padding-left: 1rem;
+ }
+ .genericList tr:first-of-type {
+ border-top: 1px solid var(--border-color);
+ }
+ .genericList tr th:last-of-type,
+ .genericList tr td:last-of-type {
+ border-left: 1px solid var(--border-color);
+ text-align: center;
+ padding: 0 1em;
+ }
+ .genericList tr th.delete,
+ .genericList tr td.delete,
+ .genericList tr.loadingMsg td {
+ border-left: none;
+ }
+ .genericList .loading {
+ border: none;
+ display: none;
+ }
.genericList td {
flex-shrink: 0;
- padding: .3em .5em;
}
- .genericList th {
- background-color: var(--border-color);
- border-bottom: 1px solid var(--border-color);
+ .genericList .topHeader,
+ .genericList .groupHeader {
+ color: var(--primary-text-color);
font-family: var(--font-family-bold);
- padding: .3em .5em;
text-align: left;
+ vertical-align: middle
+ }
+ .genericList .topHeader {
+ background-color: var(--table-header-background-color);
+ font-size: var(--font-size-large);
+ height: 3rem;
}
.genericList .groupHeader {
- background-color: #eee;
+ background-color: var(--table-subheader-background-color);
}
.genericList a {
color: var(--primary-text-color);
@@ -50,12 +85,12 @@
text-decoration: underline;
}
.genericList .description {
- width: 70%;
+ width: 99%;
}
.genericList .loadingMsg {
color: var(--deemphasized-text-color);
display: block;
- padding: 1em var(--default-horizontal-margin);
+ padding: .3em var(--default-horizontal-margin);
}
.genericList .loadingMsg:not(.loading) {
display: none;
diff --git a/polygerrit-ui/app/styles/gr-voting-styles.html b/polygerrit-ui/app/styles/gr-voting-styles.html
index 9413851..96c8026 100644
--- a/polygerrit-ui/app/styles/gr-voting-styles.html
+++ b/polygerrit-ui/app/styles/gr-voting-styles.html
@@ -19,12 +19,6 @@
<template>
<style>
:host {
- --vote-color-max: #9fcc6b;
- --vote-color-positive: #c9dfaf;
- --vote-color-min: #f7a1ad;
- --vote-color-negative: #f7c4cb;
- --vote-color-neutral: #ebf5fb;
-
--vote-chip-styles: {
border: 1px solid rgba(0,0,0,.12);
border-radius: 12px;
diff --git a/polygerrit-ui/app/styles/shared-styles.html b/polygerrit-ui/app/styles/shared-styles.html
index f97408d..99c4423 100644
--- a/polygerrit-ui/app/styles/shared-styles.html
+++ b/polygerrit-ui/app/styles/shared-styles.html
@@ -36,7 +36,9 @@
input,
iron-autogrow-textarea {
background-color: inherit;
+ border: 1px solid var(--border-color);
box-sizing: border-box;
+ color: var(--primary-text-color);
margin: 0;
padding: 0;
}
diff --git a/polygerrit-ui/app/template_test.sh b/polygerrit-ui/app/template_test.sh
index a9710cd..fcadc1b 100755
--- a/polygerrit-ui/app/template_test.sh
+++ b/polygerrit-ui/app/template_test.sh
@@ -2,20 +2,19 @@
set -ex
-npm_bin=$(which npm)
-if [ -z "$npm_bin" ]; then
- echo "NPM must be on the path."
- exit 1
-fi
-
node_bin=$(which node)
if [ -z "$node_bin" ]; then
echo "node must be on the path."
exit 1
fi
+npm_bin=$(which npm)
+if [[ -z "$npm_bin" ]]; then
+ echo "NPM must be on the path. (https://www.npmjs.com/)"
+ exit 1
+fi
+
fried_twinkie_config=$(npm list -g | grep -c fried-twinkie)
-typescript_config=$(npm list -g | grep -c typescript)
if [ -z "$npm_bin" ] || [ "$fried_twinkie_config" -eq "0" ]; then
echo "You must install fried twinkie and its dependencies from NPM."
echo "> npm install -g fried-twinkie"
diff --git a/proto/BUILD b/proto/BUILD
new file mode 100644
index 0000000..4528dcb
--- /dev/null
+++ b/proto/BUILD
@@ -0,0 +1,10 @@
+proto_library(
+ name = "cache_proto",
+ srcs = ["cache.proto"],
+)
+
+java_proto_library(
+ name = "cache_java_proto",
+ visibility = ["//visibility:public"],
+ deps = [":cache_proto"],
+)
diff --git a/proto/cache.proto b/proto/cache.proto
new file mode 100644
index 0000000..4a84ab1
--- /dev/null
+++ b/proto/cache.proto
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 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.
+
+syntax = "proto3";
+
+package gerrit.cache;
+
+option java_package = "com.google.gerrit.server.cache.proto";
+
+// Serialized form of com.google.gerrit.server.change.CHangeKindCacheImpl.Key.
+// Next ID: 4
+message ChangeKindKeyProto {
+ bytes prior = 1;
+ bytes next = 2;
+ string strategy_name = 3;
+}
diff --git a/resources/com/google/gerrit/server/mail/ChangeSubject.soy b/resources/com/google/gerrit/server/mail/ChangeSubject.soy
index b0224de..48ec9a2 100644
--- a/resources/com/google/gerrit/server/mail/ChangeSubject.soy
+++ b/resources/com/google/gerrit/server/mail/ChangeSubject.soy
@@ -22,13 +22,13 @@
* @param branch
* @param change
* @param shortProjectName
- * @param instanceName
+ * @param instanceAndProjectName
* @param addInstanceNameInSubject boolean
*/
{template .ChangeSubject kind="text"}
{if not $addInstanceNameInSubject}
Change in {$shortProjectName}[{$branch.shortName}]: {$change.shortSubject}
{else}
- [{$instanceName}] Change in {$shortProjectName}[{$branch.shortName}]: {$change.shortSubject}
+ Change in {$instanceAndProjectName}[{$branch.shortName}]: {$change.shortSubject}
{/if}
{/template}
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index f054fad..a6b0964 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -145,6 +145,7 @@
doc = make_classpath()
src = set()
lib = set()
+ proto = set()
gwt_src = set()
gwt_lib = set()
plugins = set()
@@ -170,6 +171,9 @@
# JGit dependency from external repository
if 'gerrit-' not in p and 'jgit' in p:
lib.add(p)
+ # Assume any jars in /proto/ are from java_proto_library rules
+ if '/bin/proto/' in p:
+ proto.add(p)
else:
# Don't mess up with Bazel internal test runner dependencies.
# When we use Eclipse we rely on it for running the tests
@@ -239,6 +243,11 @@
continue
classpathentry('lib', j, s)
+ for p in sorted(proto):
+ s = p.replace('-fastbuild/bin/proto/lib', '-fastbuild/genfiles/proto/')
+ s = s.replace('.jar', '-src.jar')
+ classpathentry('lib', p, s)
+
for s in sorted(gwt_src):
p = path.join(ROOT, s, 'src', 'main', 'java')
if path.exists(p):