Merge "Add plugin API documentation for deprecated APIs"
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 87e2233..ba346e7 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -261,6 +261,33 @@
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
+=== Work In Progress State Changed
+
+Sent when the the link:intro-user.html#wip[WIP] state of the change has changed.
+
+type:: wip-state-changed
+
+change:: link:json.html#change[change attribute]
+
+changer:: link:json.html#account[account attribute]
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
+=== Private State Changed
+
+Sent when the the link:intro-user.html#private-changes[private] state of the
+change has changed.
+
+type:: private-state-changed
+
+change:: link:json.html#change[change attribute]
+
+changer:: link:json.html#account[account attribute]
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
=== Vote Deleted
Sent when a vote was removed from a change.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 45bc045..91a837d 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1133,6 +1133,18 @@
+
Default is true.
+[[change.api.allowedIdentifier]]change.api.allowedIdentifier::
++
+Change identifier(s) that are allowed on the API. See
+link:rest-api-changes.html#change-id[Change Id] for more information.
++
+Possible values are `ALL`, `TRIPLET`, `NUMERIC_ID`, `I_HASH`, and
+`COMMIT_HASH` or any combination of those as a string list.
+`PROJECT_NUMERIC_ID` is always allowed and doesn't need to be listed
+explicitly.
++
+Default is `ALL`.
+
[[change.cacheAutomerge]]change.cacheAutomerge::
+
When reviewing diff commits, the left-hand side shows the output of the
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 227651a0..84e4062 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -166,9 +166,7 @@
[[label_layout]]
=== Layout
-Labels are laid out in the order they are specified in project.config,
-with inherited labels appearing first, providing some layout control to
-the administrator.
+Labels are laid out in alphabetical order.
[[label_name]]
=== `label.Label-Name`
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 9991e2d..ff32a27 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -35,7 +35,7 @@
=== Gerrit Release WAR File
To build the Gerrit web application that includes the GWT UI, the
-PolyGerrit UI and documentation:
+PolyGerrit UI, core plugins and documentation:
----
bazel build release
diff --git a/Documentation/dev-build-plugins.txt b/Documentation/dev-build-plugins.txt
index c777327..5dacd71 100644
--- a/Documentation/dev-build-plugins.txt
+++ b/Documentation/dev-build-plugins.txt
@@ -95,6 +95,38 @@
)
----
+=== Bundle custom plugin in release.war ===
+
+To bundle custom plugin(s) in the link:dev-bazel.html#release[release.war] artifact,
+add them to the CUSTOM_PLUGINS list in `tools/bzl/plugins.bzl`.
+
+Example of `tools/bzl/plugins.bzl` with custom plugin `my-plugin`:
+
+----
+CORE_PLUGINS = [
+ "commit-message-length-validator",
+ "download-commands",
+ "hooks",
+ "replication",
+ "reviewnotes",
+ "singleusergroup",
+]
+
+CUSTOM_PLUGINS = [
+ "my-plugin",
+]
+
+CUSTOM_PLUGINS_TEST_DEPS = [
+ # Add custom core plugins with tests deps here
+]
+----
+
+[NOTE]
+Since `tools/bzl/plugins.bzl` is part of Gerrit's source code and the version
+of the war is based on the state of the git repository that is built; you should
+commit this change before building, otherwise the version will be marked as
+'dirty'.
+
== Bazel standalone driven
Only few plugins support that mode for now:
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 9743779..6e39502 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -42,6 +42,13 @@
Filters on a folder, they will be overwritten the next time you run
`tools/eclipse/project.py`.
+=== Eclipse project with custom plugins ===
+
+To add custom plugins to the eclipse project add them to `tools/bzl/plugins.bzl`
+the same way you would when
+link:dev-build-plugins.html#_bundle_custom_plugin_in_release_war[bundling in release.war]
+and run `tools/eclipse/project.py`.
+
[[Formatting]]
== Code Formatter Settings
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 78b1bd7..f1dbe73 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -45,6 +45,12 @@
ABANDONED;; Change was abandoned by its owner or administrator.
+private:: Boolean indicating if the change is
+link:intro-user.html#private-changes[private].
+
+wip:: Boolean indicating if the change is
+link:intro-user.html#wip[work in progress].
+
comments:: All inline/file comments for this change in <<message,message attributes>>.
trackingIds:: Issue tracking system links in
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 1cfd3e1..1606b8a 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5384,11 +5384,12 @@
[[change-id]]
=== \{change-id\}
-Identifier that uniquely identifies one change.
+Identifier that uniquely identifies one change. It contains the URL-encoded
+project name as well as the change number: "'$$<project>~<numericId>$$'"
-This can be:
+Depending on the server's configuration, Gerrit can still support the following
+deprecated identifiers. These will be removed in a future release:
-* an ID of the change in the format "'$$<project>~<numericId>$$'"
* an ID of the change in the format "'$$<project>~<branch>~<Change-Id>$$'",
where for the branch the `refs/heads/` prefix can be omitted
("$$myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940$$")
@@ -5396,6 +5397,10 @@
("I8473b95934b5732ac55d26311a706c9c2bde9940")
* a numeric change ID ("4247")
+If you need more time to migrate off of old change IDs, please see
+link:config-gerrit.html#change.api.allowedIdentifier[change.api.allowedIdentifier]
+for more information on how to enable the use of deprecated identifiers.
+
[[comment-id]]
=== \{comment-id\}
UUID of a published comment.
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index bb0f343..6e02786 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1386,9 +1386,6 @@
|`large_change` ||
link:config-gerrit.html#change.largeChange[Number of changed lines from
which on a change is considered as a large change].
-|`private_by_default` |not set if `false`|
-Returns true if changes are by default created as private.
-See link:config-gerrit.html#change.privateByDefault[privateByDefault]
|`reply_label` ||
link:config-gerrit.html#change.replyTooltip[Label name for the reply
button].
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index c89163b..fec430f 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1165,12 +1165,14 @@
{
"remove": [
- "refs/*": {
- "permissions": {
- "read": {
- "rules": {
- "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
- "action": "ALLOW"
+ {
+ "refs/*": {
+ "permissions": {
+ "read": {
+ "rules": {
+ "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
+ "action": "ALLOW"
+ }
}
}
}
diff --git a/WORKSPACE b/WORKSPACE
index 1186db8..3ef1265 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -414,8 +414,8 @@
maven_jar(
name = "auto_value",
- artifact = "com.google.auto.value:auto-value:1.4.1",
- sha1 = "8172ebbd7970188aff304c8a420b9f17168f6f48",
+ artifact = "com.google.auto.value:auto-value:1.5.3",
+ sha1 = "514df6a7c7938de35c7f68dc8b8f22df86037f38",
)
maven_jar(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 039948d..b57545b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -26,6 +26,7 @@
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.changes.CommentInfo;
+import com.google.gerrit.client.changes.QueryScreen;
import com.google.gerrit.client.changes.RevisionInfoCache;
import com.google.gerrit.client.changes.StarredChanges;
import com.google.gerrit.client.changes.Util;
@@ -282,11 +283,46 @@
@Override
protected void onLoad() {
super.onLoad();
+ loadChangeScreen();
+ }
+
+ private void loadChangeScreen() {
+ if (project == null) {
+ // Load the project if it is not already present. This is the case when the user used a URL
+ // that doesn't include the project. Setting it here will rewrite the URL token to include the
+ // project (visible to the user) and all future API calls made from the change screen will use
+ // project/+/changeId to identify the change.
+ String query = "change:" + changeId.get();
+ ChangeList.query(
+ query,
+ Collections.emptySet(),
+ new AsyncCallback<ChangeList>() {
+ @Override
+ public void onSuccess(ChangeList result) {
+ if (result.length() == 0) {
+ Gerrit.display(getToken(), new NotFoundScreen());
+ } else if (result.length() > 1) {
+ Gerrit.display(PageLinks.toChangeQuery(query), QueryScreen.forQuery(query));
+ } else {
+ // Initialize current screen with newly obtained project
+ project = result.get(0).projectNameKey();
+ loadChangeScreen();
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ GerritCallback.showFailure(caught);
+ }
+ });
+
+ return;
+ }
CallbackGroup group = new CallbackGroup();
if (Gerrit.isSignedIn()) {
ChangeList.query(
"change:" + changeId.get() + " has:draft",
- Collections.<ListChangesOption>emptySet(),
+ Collections.emptySet(),
group.add(
new AsyncCallback<ChangeList>() {
@Override
@@ -318,15 +354,6 @@
@Override
public void onSuccess(ChangeInfo info) {
info.init();
- if (project == null) {
- // Update Project when the first API call succeeded if it wasn't already present.
- // This is the case when the user used a URL that doesn't include the project.
- // Setting it here will rewrite the URL token to include the project (visible to
- // the user) and all future API calls made from the change screen will use
- // project/+/changeId to identify the change.
- project = info.projectNameKey();
- }
-
initCurrentRevision(info);
final RevisionInfo rev = info.revision(revision);
CallbackGroup group = new CallbackGroup();
diff --git a/java/com/google/gerrit/acceptance/AccountCreator.java b/java/com/google/gerrit/acceptance/AccountCreator.java
index 693f296..ef42ad3 100644
--- a/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -119,12 +119,7 @@
accountsUpdate
.create()
- .insert(
- id,
- a -> {
- a.setFullName(fullName);
- a.setPreferredEmail(email);
- });
+ .insert("Create Test Account", id, u -> u.setFullName(fullName).setPreferredEmail(email));
if (groupNames != null) {
for (String n : groupNames) {
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index 7bca1e8..b710121 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -19,7 +19,6 @@
public Boolean showAssigneeInChangesTable;
public Boolean allowDrafts;
public int largeChange;
- public Boolean privateByDefault;
public String replyLabel;
public String replyTooltip;
public int updateDelay;
diff --git a/java/com/google/gerrit/extensions/events/PrivateStateChangedListener.java b/java/com/google/gerrit/extensions/events/PrivateStateChangedListener.java
new file mode 100644
index 0000000..e46ceb8
--- /dev/null
+++ b/java/com/google/gerrit/extensions/events/PrivateStateChangedListener.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.events;
+
+public interface PrivateStateChangedListener {
+ interface Event extends ChangeEvent {}
+
+ void onPrivateStateChanged(Event event);
+}
diff --git a/java/com/google/gerrit/extensions/events/WorkInProgressStateChangedListener.java b/java/com/google/gerrit/extensions/events/WorkInProgressStateChangedListener.java
new file mode 100644
index 0000000..e957421
--- /dev/null
+++ b/java/com/google/gerrit/extensions/events/WorkInProgressStateChangedListener.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.events;
+
+public interface WorkInProgressStateChangedListener {
+ interface Event extends ChangeEvent {}
+
+ void onWorkInProgressStateChanged(Event event);
+}
diff --git a/java/com/google/gerrit/extensions/restapi/DeprecatedIdentifierException.java b/java/com/google/gerrit/extensions/restapi/DeprecatedIdentifierException.java
new file mode 100644
index 0000000..aa28cfc
--- /dev/null
+++ b/java/com/google/gerrit/extensions/restapi/DeprecatedIdentifierException.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.restapi;
+
+/** Named resource was accessed using a deprecated identifier. */
+public class DeprecatedIdentifierException extends BadRequestException {
+ private static final long serialVersionUID = 1L;
+
+ /** Requested resource using a deprecated identifier. */
+ public DeprecatedIdentifierException(String msg) {
+ super(msg);
+ }
+}
diff --git a/java/com/google/gerrit/httpd/BUILD b/java/com/google/gerrit/httpd/BUILD
index 65943fc..d7cbdb8 100644
--- a/java/com/google/gerrit/httpd/BUILD
+++ b/java/com/google/gerrit/httpd/BUILD
@@ -15,6 +15,7 @@
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/git/receive",
"//java/com/google/gerrit/server/ioutil",
+ "//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/util/cli",
"//java/com/google/gerrit/util/http",
"//java/com/google/gwtexpui/linker:server",
diff --git a/java/com/google/gerrit/httpd/init/BUILD b/java/com/google/gerrit/httpd/init/BUILD
index 936044d..9624241 100644
--- a/java/com/google/gerrit/httpd/init/BUILD
+++ b/java/com/google/gerrit/httpd/init/BUILD
@@ -20,6 +20,7 @@
"//java/com/google/gerrit/server/api",
"//java/com/google/gerrit/server/cache/h2",
"//java/com/google/gerrit/server/git/receive",
+ "//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/sshd",
"//lib:guava",
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index b5995a8..5b9cf3b 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -55,7 +55,6 @@
import com.google.gerrit.server.config.GerritOptions;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.GerritServerConfigModule;
-import com.google.gerrit.server.config.RestCacheAdminModule;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.events.EventBroker;
import com.google.gerrit.server.events.StreamEventsApiListener;
@@ -79,6 +78,7 @@
import com.google.gerrit.server.plugins.PluginRestApiModule;
import com.google.gerrit.server.project.DefaultPermissionBackendModule;
import com.google.gerrit.server.project.DefaultProjectNameLockManager;
+import com.google.gerrit.server.restapi.config.RestCacheAdminModule;
import com.google.gerrit.server.schema.DataSourceModule;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DataSourceType;
diff --git a/java/com/google/gerrit/httpd/restapi/ConfigRestApiServlet.java b/java/com/google/gerrit/httpd/restapi/ConfigRestApiServlet.java
index 87df4cf..4d56036 100644
--- a/java/com/google/gerrit/httpd/restapi/ConfigRestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/ConfigRestApiServlet.java
@@ -14,7 +14,7 @@
package com.google.gerrit.httpd.restapi;
-import com.google.gerrit.server.config.ConfigCollection;
+import com.google.gerrit.server.restapi.config.ConfigCollection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index ce8741c..5b24284 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -15,6 +15,7 @@
// WARNING: NoteDbUpdateManager cares about the package name RestApiServlet lives in.
package com.google.gerrit.httpd.restapi;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS;
@@ -1210,6 +1211,7 @@
RequestUtil.setErrorTraceAttribute(req, err);
}
configureCaching(req, res, null, null, c);
+ checkArgument(statusCode >= 400, "non-error status: %s", statusCode);
res.setStatus(statusCode);
return replyText(req, res, msg);
}
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 3dce217..a255020 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -38,6 +38,7 @@
"//java/com/google/gerrit/server/api",
"//java/com/google/gerrit/server/cache/h2",
"//java/com/google/gerrit/server/git/receive",
+ "//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/sshd",
"//java/com/google/gwtexpui/linker:server",
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 16d81b7..4bc06d0 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -64,7 +64,6 @@
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.GerritOptions;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.RestCacheAdminModule;
import com.google.gerrit.server.events.EventBroker;
import com.google.gerrit.server.events.StreamEventsApiListener;
import com.google.gerrit.server.git.GarbageCollectionModule;
@@ -88,6 +87,7 @@
import com.google.gerrit.server.plugins.PluginRestApiModule;
import com.google.gerrit.server.project.DefaultPermissionBackendModule;
import com.google.gerrit.server.project.DefaultProjectNameLockManager;
+import com.google.gerrit.server.restapi.config.RestCacheAdminModule;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.InMemoryAccountPatchReviewStore;
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
diff --git a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
index 385f198..d00b945 100644
--- a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
+++ b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
@@ -17,20 +17,28 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.server.account.externalids.DisabledExternalIdCache;
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.account.externalids.ExternalIdsBatchUpdate;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
import com.google.gerrit.server.schema.SchemaVersionCheck;
-import com.google.inject.AbstractModule;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.inject.Inject;
import com.google.inject.Injector;
+import com.google.inject.Provider;
+import java.io.IOException;
import java.util.Collection;
import java.util.Locale;
import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
/** Converts the local username for all accounts to lower case */
@@ -38,10 +46,12 @@
private final LifecycleManager manager = new LifecycleManager();
private final TextProgressMonitor monitor = new TextProgressMonitor();
+ @Inject private GitRepositoryManager repoManager;
+ @Inject private AllUsersName allUsersName;
+ @Inject private Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
+ @Inject private ExternalIdNotes.FactoryNoReindex externalIdNotesFactory;
@Inject private ExternalIds externalIds;
- @Inject private ExternalIdsBatchUpdate externalIdsBatchUpdate;
-
@Override
public int run() throws Exception {
Injector dbInjector = createDbInjector(MULTI_USER);
@@ -49,9 +59,12 @@
manager.start();
dbInjector
.createChildInjector(
- new AbstractModule() {
+ new FactoryModule() {
@Override
protected void configure() {
+ bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
+ factory(MetaDataUpdate.InternalFactory.class);
+
// The LocalUsernamesToLowerCase program needs to access all external IDs only
// once to update them. After the update they are not accessed again. Hence the
// LocalUsernamesToLowerCase program doesn't benefit from caching external IDs and
@@ -64,12 +77,18 @@
Collection<ExternalId> todo = externalIds.all();
monitor.beginTask("Converting local usernames", todo.size());
- for (ExternalId extId : todo) {
- convertLocalUserToLowerCase(extId);
- monitor.update(1);
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ ExternalIdNotes extIdNotes = externalIdNotesFactory.load(repo);
+ for (ExternalId extId : todo) {
+ convertLocalUserToLowerCase(extIdNotes, extId);
+ monitor.update(1);
+ }
+ try (MetaDataUpdate metaDataUpdate = metaDataUpdateServerFactory.get().create(allUsersName)) {
+ metaDataUpdate.setMessage("Convert local usernames to lower case");
+ extIdNotes.commit(metaDataUpdate);
+ }
}
- externalIdsBatchUpdate.commit("Convert local usernames to lower case");
monitor.endTask();
int exitCode = reindexAccounts();
@@ -77,7 +96,8 @@
return exitCode;
}
- private void convertLocalUserToLowerCase(ExternalId extId) {
+ private void convertLocalUserToLowerCase(ExternalIdNotes extIdNotes, ExternalId extId)
+ throws OrmDuplicateKeyException, IOException {
if (extId.isScheme(SCHEME_GERRIT)) {
String localUser = extId.key().id();
String localUserLowerCase = localUser.toLowerCase(Locale.US);
@@ -89,7 +109,7 @@
extId.accountId(),
extId.email(),
extId.password());
- externalIdsBatchUpdate.replace(extId, extIdLowerCase);
+ extIdNotes.replace(extId, extIdLowerCase);
}
}
}
diff --git a/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/java/com/google/gerrit/pgm/MigrateToNoteDb.java
index 59511a2..8cd148f 100644
--- a/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ b/java/com/google/gerrit/pgm/MigrateToNoteDb.java
@@ -31,6 +31,7 @@
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.index.DummyIndexModule;
+import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -145,7 +146,12 @@
// their server is offline.
List<String> reindexArgs =
ImmutableList.of(
- "--site-path", getSitePath().toString(), "--threads", Integer.toString(threads));
+ "--site-path",
+ getSitePath().toString(),
+ "--threads",
+ Integer.toString(threads),
+ "--index",
+ ChangeSchemaDefinitions.NAME);
System.out.println("Migration complete, reindexing changes with:");
System.out.println(" reindex " + reindexArgs.stream().collect(joining(" ")));
Reindex reindexPgm = new Reindex();
diff --git a/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index 35beaeb..260f695 100644
--- a/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -148,7 +148,7 @@
@Override
public void destroy() {}
- private final class Listener implements ContinuationListener {
+ private static final class Listener implements ContinuationListener {
final Future<?> future;
Listener(Future<?> future) {
diff --git a/java/com/google/gerrit/pgm/init/AccountsOnInit.java b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
index 2beb50a..fbe9b62 100644
--- a/java/com/google/gerrit/pgm/init/AccountsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
@@ -24,6 +24,7 @@
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.account.AccountConfig;
import com.google.gerrit.server.account.Accounts;
+import com.google.gerrit.server.account.InternalAccountUpdate;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import java.io.File;
@@ -69,7 +70,14 @@
new GerritPersonIdentProvider(flags.cfg).get(), account.getRegisteredOn());
Config accountConfig = new Config();
- AccountConfig.writeToConfig(account, accountConfig);
+ AccountConfig.writeToConfig(
+ InternalAccountUpdate.builder()
+ .setActive(account.isActive())
+ .setFullName(account.getFullName())
+ .setPreferredEmail(account.getPreferredEmail())
+ .setStatus(account.getStatus())
+ .build(),
+ accountConfig);
DirCache newTree = DirCache.newInCore();
DirCacheEditor editor = newTree.editor();
diff --git a/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java b/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
index ab491f7c..d2a9b04 100644
--- a/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
@@ -19,10 +19,10 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdReader;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.File;
@@ -31,13 +31,9 @@
import java.util.Collection;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.FS;
public class ExternalIdsOnInit {
@@ -54,32 +50,20 @@
public synchronized void insert(String commitMessage, Collection<ExternalId> extIds)
throws OrmException, IOException, ConfigInvalidException {
-
File path = getPath();
if (path != null) {
- try (Repository repo = new FileRepository(path);
- RevWalk rw = new RevWalk(repo);
- ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId rev = ExternalIdReader.readRevision(repo);
-
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
- for (ExternalId extId : extIds) {
- ExternalIdsUpdate.insert(rw, ins, noteMap, extId);
+ try (Repository allUsersRepo = new FileRepository(path)) {
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(allUsersRepo);
+ extIdNotes.insert(extIds);
+ try (MetaDataUpdate metaDataUpdate =
+ new MetaDataUpdate(
+ GitReferenceUpdated.DISABLED, new Project.NameKey(allUsers), allUsersRepo)) {
+ PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get();
+ metaDataUpdate.getCommitBuilder().setAuthor(serverIdent);
+ metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
+ metaDataUpdate.getCommitBuilder().setMessage(commitMessage);
+ extIdNotes.commit(metaDataUpdate);
}
-
- PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get();
- ExternalIdsUpdate.commit(
- new Project.NameKey(allUsers),
- repo,
- rw,
- ins,
- rev,
- noteMap,
- commitMessage,
- serverIdent,
- serverIdent,
- null,
- GitReferenceUpdated.DISABLED);
}
}
}
diff --git a/java/com/google/gerrit/reviewdb/client/Comment.java b/java/com/google/gerrit/reviewdb/client/Comment.java
index 4b3c652..3d19da4 100644
--- a/java/com/google/gerrit/reviewdb/client/Comment.java
+++ b/java/com/google/gerrit/reviewdb/client/Comment.java
@@ -214,6 +214,12 @@
public String serverId;
public boolean unresolved;
+ /**
+ * Whether the comment was parsed from a JSON representation (false) or the legacy custom notes
+ * format (true).
+ */
+ public transient boolean legacyFormat;
+
public Comment(Comment c) {
this(
new Key(c.key),
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 6052a63..fb1fc28 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -100,6 +100,7 @@
":server",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/server/git/receive",
+ "//java/com/google/gerrit/server/restapi",
"//lib:blame-cache",
"//lib:guava",
"//lib:soy",
diff --git a/java/com/google/gerrit/server/ChangeFinder.java b/java/com/google/gerrit/server/ChangeFinder.java
index 4b7cbac..cb82778 100644
--- a/java/com/google/gerrit/server/ChangeFinder.java
+++ b/java/com/google/gerrit/server/ChangeFinder.java
@@ -17,8 +17,10 @@
import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
+import com.google.gerrit.extensions.restapi.DeprecatedIdentifierException;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
@@ -30,6 +32,8 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.change.ChangeTriplet;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -46,6 +50,7 @@
import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
@Singleton
public class ChangeFinder {
@@ -60,10 +65,11 @@
};
}
- private enum ChangeIdType {
+ public enum ChangeIdType {
+ ALL,
TRIPLET,
NUMERIC_ID,
- CHANGE_ID,
+ I_HASH,
PROJECT_NUMERIC_ID,
COMMIT_HASH
}
@@ -74,6 +80,7 @@
private final Provider<ReviewDb> reviewDb;
private final ChangeNotes.Factory changeNotesFactory;
private final Counter1<ChangeIdType> changeIdCounter;
+ private final ImmutableSet<ChangeIdType> allowedIdTypes;
@Inject
ChangeFinder(
@@ -82,7 +89,8 @@
Provider<InternalChangeQuery> queryProvider,
Provider<ReviewDb> reviewDb,
ChangeNotes.Factory changeNotesFactory,
- MetricMaker metricMaker) {
+ MetricMaker metricMaker,
+ @GerritServerConfig Config config) {
this.indexConfig = indexConfig;
this.changeIdProjectCache = changeIdProjectCache;
this.queryProvider = queryProvider;
@@ -95,16 +103,41 @@
.setRate()
.setUnit("requests"),
Field.ofEnum(ChangeIdType.class, "change_id_type"));
+ List<ChangeIdType> configuredChangeIdTypes =
+ ConfigUtil.getEnumList(config, "change", "api", "allowedIdentifier", ChangeIdType.ALL);
+ // Ensure that PROJECT_NUMERIC_ID can't be removed
+ configuredChangeIdTypes.add(ChangeIdType.PROJECT_NUMERIC_ID);
+ this.allowedIdTypes = ImmutableSet.copyOf(configuredChangeIdTypes);
}
/**
* Find changes matching the given identifier.
*
- * @param id change identifier, either a numeric ID, a Change-Id, or project~branch~id triplet.
+ * @param id change identifier.
* @return possibly-empty list of notes for all matching changes; may or may not be visible.
* @throws OrmException if an error occurred querying the database.
*/
public List<ChangeNotes> find(String id) throws OrmException {
+ try {
+ return find(id, false);
+ } catch (DeprecatedIdentifierException e) {
+ // This can't happen because we don't enforce deprecation
+ throw new OrmException(e);
+ }
+ }
+
+ /**
+ * Find changes matching the given identifier.
+ *
+ * @param id change identifier.
+ * @param enforceDeprecation boolean to see if we should throw {@link
+ * DeprecatedIdentifierException} in case the identifier is deprecated
+ * @return possibly-empty list of notes for all matching changes; may or may not be visible.
+ * @throws OrmException if an error occurred querying the database
+ * @throws DeprecatedIdentifierException if the identifier is deprecated.
+ */
+ public List<ChangeNotes> find(String id, boolean enforceDeprecation)
+ throws OrmException, DeprecatedIdentifierException {
if (id.isEmpty()) {
return Collections.emptyList();
}
@@ -115,7 +148,7 @@
// Try project~numericChangeId
Integer n = Ints.tryParse(id.substring(z + 1));
if (n != null) {
- changeIdCounter.increment(ChangeIdType.PROJECT_NUMERIC_ID);
+ checkIdType(ChangeIdType.PROJECT_NUMERIC_ID, enforceDeprecation, n.toString());
return fromProjectNumber(id.substring(0, z), n.intValue());
}
}
@@ -124,7 +157,7 @@
// Try numeric changeId
Integer n = Ints.tryParse(id);
if (n != null) {
- changeIdCounter.increment(ChangeIdType.NUMERIC_ID);
+ checkIdType(ChangeIdType.NUMERIC_ID, enforceDeprecation, n.toString());
return find(new Change.Id(n));
}
}
@@ -135,7 +168,7 @@
// Try commit hash
if (id.matches("^([0-9a-fA-F]{" + RevId.ABBREV_LEN + "," + RevId.LEN + "})$")) {
- changeIdCounter.increment(ChangeIdType.COMMIT_HASH);
+ checkIdType(ChangeIdType.COMMIT_HASH, enforceDeprecation, id);
return asChangeNotes(query.byCommit(id));
}
@@ -144,7 +177,7 @@
Optional<ChangeTriplet> triplet = ChangeTriplet.parse(id, y, z);
if (triplet.isPresent()) {
ChangeTriplet t = triplet.get();
- changeIdCounter.increment(ChangeIdType.TRIPLET);
+ checkIdType(ChangeIdType.TRIPLET, enforceDeprecation, triplet.get().toString());
return asChangeNotes(query.byBranchKey(t.branch(), t.id()));
}
}
@@ -152,7 +185,7 @@
// Try isolated Ihash... format ("Change-Id: Ihash").
List<ChangeNotes> notes = asChangeNotes(query.byKeyPrefix(id));
if (!notes.isEmpty()) {
- changeIdCounter.increment(ChangeIdType.CHANGE_ID);
+ checkIdType(ChangeIdType.I_HASH, enforceDeprecation, id);
}
return notes;
}
@@ -222,4 +255,18 @@
}
return notes;
}
+
+ private void checkIdType(ChangeIdType type, boolean enforceDeprecation, String val)
+ throws DeprecatedIdentifierException {
+ if (enforceDeprecation
+ && !allowedIdTypes.contains(ChangeIdType.ALL)
+ && !allowedIdTypes.contains(type)) {
+ throw new DeprecatedIdentifierException(
+ String.format(
+ "The provided change identifier %s is deprecated. "
+ + "Use 'project~changeNumber' instead.",
+ val));
+ }
+ changeIdCounter.increment(type);
+ }
}
diff --git a/java/com/google/gerrit/server/ReviewersUtil.java b/java/com/google/gerrit/server/ReviewersUtil.java
index c566e59..a8410d8 100644
--- a/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/java/com/google/gerrit/server/ReviewersUtil.java
@@ -18,36 +18,41 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.extensions.common.GroupBaseInfo;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.FieldBundle;
+import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.index.query.QueryResult;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.account.AccountLoader;
-import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.change.PostReviewers;
import com.google.gerrit.server.change.SuggestReviewers;
+import com.google.gerrit.server.index.account.AccountField;
+import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.account.AccountPredicates;
import com.google.gerrit.server.query.account.AccountQueryBuilder;
-import com.google.gerrit.server.query.account.AccountQueryProcessor;
import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
@@ -108,30 +113,36 @@
private final AccountLoader accountLoader;
private final AccountQueryBuilder accountQueryBuilder;
- private final Provider<AccountQueryProcessor> queryProvider;
private final GroupBackend groupBackend;
private final GroupMembers groupMembers;
private final ReviewerRecommender reviewerRecommender;
private final Metrics metrics;
+ private final AccountIndexCollection accountIndexes;
+ private final IndexConfig indexConfig;
+ private final AccountControl.Factory accountControlFactory;
@Inject
ReviewersUtil(
AccountLoader.Factory accountLoaderFactory,
AccountQueryBuilder accountQueryBuilder,
- Provider<AccountQueryProcessor> queryProvider,
GroupBackend groupBackend,
GroupMembers groupMembers,
ReviewerRecommender reviewerRecommender,
- Metrics metrics) {
+ Metrics metrics,
+ AccountIndexCollection accountIndexes,
+ IndexConfig indexConfig,
+ AccountControl.Factory accountControlFactory) {
Set<FillOptions> fillOptions = EnumSet.of(FillOptions.SECONDARY_EMAILS);
fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
this.accountLoader = accountLoaderFactory.create(fillOptions);
this.accountQueryBuilder = accountQueryBuilder;
- this.queryProvider = queryProvider;
this.groupBackend = groupBackend;
this.groupMembers = groupMembers;
this.reviewerRecommender = reviewerRecommender;
this.metrics = metrics;
+ this.accountIndexes = accountIndexes;
+ this.indexConfig = indexConfig;
+ this.accountControlFactory = accountControlFactory;
}
public interface VisibilityControl {
@@ -167,7 +178,9 @@
if (filteredRecommendations.size() >= limit) {
break;
}
- if (visibilityControl.isVisibleTo(reviewer)) {
+ // Check if change is visible to reviewer and if the current user can see reviewer
+ if (visibilityControl.isVisibleTo(reviewer)
+ && accountControlFactory.get().canSee(reviewer)) {
filteredRecommendations.add(reviewer);
}
}
@@ -191,14 +204,27 @@
private List<Account.Id> suggestAccounts(SuggestReviewers suggestReviewers) throws OrmException {
try (Timer0.Context ctx = metrics.queryAccountsLatency.start()) {
try {
- QueryResult<AccountState> result =
- queryProvider
- .get()
- .setUserProvidedLimit(suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER)
- .query(
- AccountPredicates.andActive(
- accountQueryBuilder.defaultQuery(suggestReviewers.getQuery())));
- return result.entities().stream().map(a -> a.getAccount().getId()).collect(toList());
+ // For performance reasons we don't use AccountQueryProvider as it would always load the
+ // complete account from the cache (or worse, from NoteDb) even though we only need the ID
+ // which we can directly get from the returned results.
+ ResultSet<FieldBundle> result =
+ accountIndexes
+ .getSearchIndex()
+ .getSource(
+ Predicate.and(
+ AccountPredicates.isActive(),
+ accountQueryBuilder.defaultQuery(suggestReviewers.getQuery())),
+ QueryOptions.create(
+ indexConfig,
+ 0,
+ suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER,
+ ImmutableSet.of(AccountField.ID.getName())))
+ .readRaw();
+ return result
+ .toList()
+ .stream()
+ .map(f -> new Account.Id(f.getValue(AccountField.ID).intValue()))
+ .collect(toList());
} catch (QueryParseException e) {
return ImmutableList.of();
}
diff --git a/java/com/google/gerrit/server/account/AccountConfig.java b/java/com/google/gerrit/server/account/AccountConfig.java
index 6bc6a21..1283270 100644
--- a/java/com/google/gerrit/server/account/AccountConfig.java
+++ b/java/com/google/gerrit/server/account/AccountConfig.java
@@ -80,6 +80,7 @@
private final String ref;
private Optional<Account> loadedAccount;
+ private Optional<InternalAccountUpdate> accountUpdate = Optional.empty();
private Timestamp registeredOn;
private List<ValidationError> validationErrors;
@@ -117,6 +118,14 @@
public void setAccount(Account account) {
checkLoaded();
this.loadedAccount = Optional.of(account);
+ this.accountUpdate =
+ Optional.of(
+ InternalAccountUpdate.builder()
+ .setActive(account.isActive())
+ .setFullName(account.getFullName())
+ .setPreferredEmail(account.getPreferredEmail())
+ .setStatus(account.getStatus())
+ .build());
this.registeredOn = account.getRegisteredOn();
}
@@ -127,15 +136,29 @@
* @throws OrmDuplicateKeyException if the user branch already exists
*/
public Account getNewAccount() throws OrmDuplicateKeyException {
+ return getNewAccount(TimeUtil.nowTs());
+ }
+
+ /**
+ * Creates a new account.
+ *
+ * @return the new account
+ * @throws OrmDuplicateKeyException if the user branch already exists
+ */
+ Account getNewAccount(Timestamp registeredOn) throws OrmDuplicateKeyException {
checkLoaded();
if (revision != null) {
throw new OrmDuplicateKeyException(String.format("account %s already exists", accountId));
}
- this.registeredOn = TimeUtil.nowTs();
+ this.registeredOn = registeredOn;
this.loadedAccount = Optional.of(new Account(accountId, registeredOn));
return loadedAccount.get();
}
+ public void setAccountUpdate(InternalAccountUpdate accountUpdate) {
+ this.accountUpdate = Optional.of(accountUpdate);
+ }
+
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
if (revision != null) {
@@ -186,24 +209,39 @@
}
if (revision != null) {
- commit.setMessage("Update account\n");
+ if (Strings.isNullOrEmpty(commit.getMessage())) {
+ commit.setMessage("Update account\n");
+ }
} else {
- commit.setMessage("Create account\n");
+ if (Strings.isNullOrEmpty(commit.getMessage())) {
+ commit.setMessage("Create account\n");
+ }
+
commit.setAuthor(new PersonIdent(commit.getAuthor(), registeredOn));
commit.setCommitter(new PersonIdent(commit.getCommitter(), registeredOn));
}
Config cfg = readConfig(ACCOUNT_CONFIG);
- writeToConfig(loadedAccount.get(), cfg);
+ if (accountUpdate.isPresent()) {
+ writeToConfig(accountUpdate.get(), cfg);
+ }
saveConfig(ACCOUNT_CONFIG, cfg);
+
+ // metaId is set in the commit(MetaDataUpdate) method after the commit is created
+ loadedAccount = Optional.of(parse(cfg, null));
+
+ accountUpdate = Optional.empty();
+
return true;
}
- public static void writeToConfig(Account account, Config cfg) {
- setActive(cfg, account.isActive());
- set(cfg, KEY_FULL_NAME, account.getFullName());
- set(cfg, KEY_PREFERRED_EMAIL, account.getPreferredEmail());
- set(cfg, KEY_STATUS, account.getStatus());
+ public static void writeToConfig(InternalAccountUpdate accountUpdate, Config cfg) {
+ accountUpdate.getActive().ifPresent(active -> setActive(cfg, active));
+ accountUpdate.getFullName().ifPresent(fullName -> set(cfg, KEY_FULL_NAME, fullName));
+ accountUpdate
+ .getPreferredEmail()
+ .ifPresent(preferredEmail -> set(cfg, KEY_PREFERRED_EMAIL, preferredEmail));
+ accountUpdate.getStatus().ifPresent(status -> set(cfg, KEY_STATUS, status));
}
/**
diff --git a/java/com/google/gerrit/server/account/AccountManager.java b/java/com/google/gerrit/server/account/AccountManager.java
index b3e210d..3f87e5f 100644
--- a/java/com/google/gerrit/server/account/AccountManager.java
+++ b/java/com/google/gerrit/server/account/AccountManager.java
@@ -29,6 +29,8 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.account.AccountsUpdate.AccountUpdater;
+import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
@@ -203,7 +205,7 @@
private void update(AuthRequest who, ExternalId extId)
throws OrmException, IOException, ConfigInvalidException {
IdentifiedUser user = userFactory.create(extId.accountId());
- List<Consumer<Account>> accountUpdates = new ArrayList<>();
+ List<Consumer<InternalAccountUpdate.Builder>> accountUpdates = new ArrayList<>();
// If the email address was modified by the authentication provider,
// update our records to match the changed email.
@@ -212,19 +214,20 @@
String oldEmail = extId.email();
if (newEmail != null && !newEmail.equals(oldEmail)) {
if (oldEmail != null && oldEmail.equals(user.getAccount().getPreferredEmail())) {
- accountUpdates.add(a -> a.setPreferredEmail(newEmail));
+ accountUpdates.add(u -> u.setPreferredEmail(newEmail));
}
- externalIdsUpdateFactory
- .create()
- .replace(
- extId, ExternalId.create(extId.key(), extId.accountId(), newEmail, extId.password()));
+ accountUpdates.add(
+ u ->
+ u.replaceExternalId(
+ extId,
+ ExternalId.create(extId.key(), extId.accountId(), newEmail, extId.password())));
}
if (!realm.allowsEdit(AccountFieldName.FULL_NAME)
&& !Strings.isNullOrEmpty(who.getDisplayName())
&& !eq(user.getAccount().getFullName(), who.getDisplayName())) {
- accountUpdates.add(a -> a.setFullName(who.getDisplayName()));
+ accountUpdates.add(u -> u.setFullName(who.getDisplayName()));
}
if (!realm.allowsEdit(AccountFieldName.USER_NAME)
@@ -236,7 +239,13 @@
}
if (!accountUpdates.isEmpty()) {
- Account account = accountsUpdateFactory.create().update(user.getAccountId(), accountUpdates);
+ Account account =
+ accountsUpdateFactory
+ .create()
+ .update(
+ "Update Account on Login",
+ user.getAccountId(),
+ AccountUpdater.joinConsumers(accountUpdates));
if (account == null) {
throw new OrmException("Account " + user.getAccountId() + " has been deleted");
}
@@ -258,27 +267,23 @@
Account account;
try {
- AccountsUpdate accountsUpdate = accountsUpdateFactory.create();
account =
- accountsUpdate.insert(
- newId,
- a -> {
- a.setFullName(who.getDisplayName());
- a.setPreferredEmail(extId.email());
- });
-
- ExternalId existingExtId = externalIds.get(extId.key());
- if (existingExtId != null && !existingExtId.accountId().equals(extId.accountId())) {
- // external ID is assigned to another account, do not overwrite
- accountsUpdate.delete(account);
- throw new AccountException(
- "Cannot assign external ID \""
- + extId.key().get()
- + "\" to account "
- + newId
- + "; external ID already in use.");
- }
- externalIdsUpdateFactory.create().upsert(extId);
+ accountsUpdateFactory
+ .create()
+ .insert(
+ "Create Account on First Login",
+ newId,
+ u ->
+ u.setFullName(who.getDisplayName())
+ .setPreferredEmail(extId.email())
+ .addExternalId(extId));
+ } catch (DuplicateExternalIdKeyException e) {
+ throw new AccountException(
+ "Cannot assign external ID \""
+ + e.getDuplicateKey().get()
+ + "\" to account "
+ + newId
+ + "; external ID already in use.");
} finally {
// If adding the account failed, it may be that it actually was the
// first account. So we reset the 'check for first account'-guard, as
@@ -308,7 +313,7 @@
// Only set if the name hasn't been used yet, but was given to us.
//
try {
- changeUserNameFactory.create(user, who.getUserName()).call();
+ changeUserNameFactory.create("Set Username on Login", user, who.getUserName()).call();
} catch (NameAlreadyUsedException e) {
String message =
"Cannot assign user name \""
@@ -407,23 +412,19 @@
}
update(who, extId);
} else {
- externalIdsUpdateFactory
+ accountsUpdateFactory
.create()
- .insert(ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress()));
-
- if (who.getEmailAddress() != null) {
- accountsUpdateFactory
- .create()
- .update(
- to,
- a -> {
- if (a.getPreferredEmail() == null) {
- a.setPreferredEmail(who.getEmailAddress());
- }
- });
- }
+ .update(
+ "Link External ID",
+ to,
+ (a, u) -> {
+ u.addExternalId(
+ ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress()));
+ if (who.getEmailAddress() != null && a.getPreferredEmail() == null) {
+ u.setPreferredEmail(who.getEmailAddress());
+ }
+ });
}
-
return new AuthResult(to, who.getExternalIdKey(), false);
}
@@ -503,12 +504,16 @@
accountsUpdateFactory
.create()
.update(
+ "Clear Preferred Email on Unlinking External ID\n"
+ + "\n"
+ + "The preferred email is cleared because the corresponding external ID\n"
+ + "was removed.",
from,
- a -> {
+ (a, u) -> {
if (a.getPreferredEmail() != null) {
for (ExternalId extId : extIds) {
if (a.getPreferredEmail().equals(extId.email())) {
- a.setPreferredEmail(null);
+ u.setPreferredEmail(null);
break;
}
}
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index e0f17dd..f88f7e2 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -15,29 +15,40 @@
package com.google.gerrit.server.account;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
-import com.google.common.collect.ImmutableList;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Runnables;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
+import com.google.gerrit.server.update.RefUpdateUtil;
+import com.google.gerrit.server.update.RetryHelper;
import com.google.gwtorm.server.OrmDuplicateKeyException;
+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;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
@@ -64,6 +75,38 @@
@Singleton
public class AccountsUpdate {
/**
+ * Updater for an account.
+ *
+ * <p>Allows to read the current state of an account and to prepare updates to it.
+ */
+ @FunctionalInterface
+ public static interface AccountUpdater {
+ /**
+ * Prepare updates to an account.
+ *
+ * <p>Use the provided account only to read the current state of the account. Don't do updates
+ * to the account. For updates use the provided account update builder.
+ *
+ * @param account the account that is being updated
+ * @param update account update builder
+ */
+ void update(Account account, InternalAccountUpdate.Builder update);
+
+ public static AccountUpdater join(List<AccountUpdater> updaters) {
+ return (a, u) -> updaters.stream().forEach(updater -> updater.update(a, u));
+ }
+
+ public static AccountUpdater joinConsumers(
+ List<Consumer<InternalAccountUpdate.Builder>> consumers) {
+ return join(Lists.transform(consumers, AccountUpdater::fromConsumer));
+ }
+
+ static AccountUpdater fromConsumer(Consumer<InternalAccountUpdate.Builder> consumer) {
+ return (a, u) -> consumer.accept(u);
+ }
+ }
+
+ /**
* Factory to create an AccountsUpdate instance for updating accounts by the Gerrit server.
*
* <p>The Gerrit server identity will be used as author and committer for all commits that update
@@ -75,8 +118,10 @@
private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator;
- private final Provider<PersonIdent> serverIdent;
- private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
+ private final Provider<PersonIdent> serverIdentProvider;
+ private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
+ private final RetryHelper retryHelper;
+ private final ExternalIdNotes.Factory extIdNotesFactory;
@Inject
public Server(
@@ -84,26 +129,33 @@
GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName,
OutgoingEmailValidator emailValidator,
- @GerritPersonIdent Provider<PersonIdent> serverIdent,
- Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory) {
+ @GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
+ Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
+ RetryHelper retryHelper,
+ ExternalIdNotes.Factory extIdNotesFactory) {
this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName;
this.emailValidator = emailValidator;
- this.serverIdent = serverIdent;
- this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
+ this.serverIdentProvider = serverIdentProvider;
+ this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
+ this.retryHelper = retryHelper;
+ this.extIdNotesFactory = extIdNotesFactory;
}
public AccountsUpdate create() {
- PersonIdent i = serverIdent.get();
+ PersonIdent serverIdent = serverIdentProvider.get();
return new AccountsUpdate(
repoManager,
gitRefUpdated,
null,
allUsersName,
emailValidator,
- i,
- () -> metaDataUpdateServerFactory.get().create(allUsersName));
+ metaDataUpdateInternalFactory,
+ retryHelper,
+ extIdNotesFactory,
+ serverIdent,
+ serverIdent);
}
}
@@ -119,9 +171,11 @@
private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator;
- private final Provider<PersonIdent> serverIdent;
+ private final Provider<PersonIdent> serverIdentProvider;
private final Provider<IdentifiedUser> identifiedUser;
- private final Provider<MetaDataUpdate.User> metaDataUpdateUserFactory;
+ private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
+ private final RetryHelper retryHelper;
+ private final ExternalIdNotes.Factory extIdNotesFactory;
@Inject
public User(
@@ -129,29 +183,37 @@
GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName,
OutgoingEmailValidator emailValidator,
- @GerritPersonIdent Provider<PersonIdent> serverIdent,
+ @GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
Provider<IdentifiedUser> identifiedUser,
- Provider<MetaDataUpdate.User> metaDataUpdateUserFactory) {
+ Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
+ RetryHelper retryHelper,
+ ExternalIdNotes.Factory extIdNotesFactory) {
this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName;
- this.serverIdent = serverIdent;
+ this.serverIdentProvider = serverIdentProvider;
this.emailValidator = emailValidator;
this.identifiedUser = identifiedUser;
- this.metaDataUpdateUserFactory = metaDataUpdateUserFactory;
+ this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
+ this.retryHelper = retryHelper;
+ this.extIdNotesFactory = extIdNotesFactory;
}
public AccountsUpdate create() {
IdentifiedUser user = identifiedUser.get();
- PersonIdent i = serverIdent.get();
+ PersonIdent serverIdent = serverIdentProvider.get();
+ PersonIdent userIdent = createPersonIdent(serverIdent, user);
return new AccountsUpdate(
repoManager,
gitRefUpdated,
user,
allUsersName,
emailValidator,
- createPersonIdent(i, user),
- () -> metaDataUpdateUserFactory.get().create(allUsersName));
+ metaDataUpdateInternalFactory,
+ retryHelper,
+ extIdNotesFactory,
+ serverIdent,
+ userIdent);
}
private PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
@@ -164,8 +226,12 @@
@Nullable private final IdentifiedUser currentUser;
private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator;
+ private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
+ private final RetryHelper retryHelper;
+ private final ExternalIdNotes.Factory extIdNotesFactory;
private final PersonIdent committerIdent;
- private final MetaDataUpdateFactory metaDataUpdateFactory;
+ private final PersonIdent authorIdent;
+ private final Runnable afterReadRevision;
private AccountsUpdate(
GitRepositoryManager repoManager,
@@ -173,36 +239,99 @@
@Nullable IdentifiedUser currentUser,
AllUsersName allUsersName,
OutgoingEmailValidator emailValidator,
+ Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
+ RetryHelper retryHelper,
+ ExternalIdNotes.Factory extIdNotesFactory,
PersonIdent committerIdent,
- MetaDataUpdateFactory metaDataUpdateFactory) {
+ PersonIdent authorIdent) {
+ this(
+ repoManager,
+ gitRefUpdated,
+ currentUser,
+ allUsersName,
+ emailValidator,
+ metaDataUpdateInternalFactory,
+ retryHelper,
+ extIdNotesFactory,
+ committerIdent,
+ authorIdent,
+ Runnables.doNothing());
+ }
+
+ @VisibleForTesting
+ public AccountsUpdate(
+ GitRepositoryManager repoManager,
+ GitReferenceUpdated gitRefUpdated,
+ @Nullable IdentifiedUser currentUser,
+ AllUsersName allUsersName,
+ OutgoingEmailValidator emailValidator,
+ Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
+ RetryHelper retryHelper,
+ ExternalIdNotes.Factory extIdNotesFactory,
+ PersonIdent committerIdent,
+ PersonIdent authorIdent,
+ Runnable afterReadRevision) {
this.repoManager = checkNotNull(repoManager, "repoManager");
this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
this.currentUser = currentUser;
this.allUsersName = checkNotNull(allUsersName, "allUsersName");
this.emailValidator = checkNotNull(emailValidator, "emailValidator");
+ this.metaDataUpdateInternalFactory =
+ checkNotNull(metaDataUpdateInternalFactory, "metaDataUpdateInternalFactory");
+ this.retryHelper = checkNotNull(retryHelper, "retryHelper");
+ this.extIdNotesFactory = checkNotNull(extIdNotesFactory, "extIdNotesFactory");
this.committerIdent = checkNotNull(committerIdent, "committerIdent");
- this.metaDataUpdateFactory = checkNotNull(metaDataUpdateFactory, "metaDataUpdateFactory");
+ this.authorIdent = checkNotNull(authorIdent, "authorIdent");
+ this.afterReadRevision = afterReadRevision;
}
/**
* Inserts a new account.
*
+ * @param message commit message for the account creation, must not be {@code null or empty}
* @param accountId ID of the new account
* @param init consumer to populate the new account
* @return the newly created account
* @throws OrmDuplicateKeyException if the account already exists
- * @throws IOException if updating the user branch fails
+ * @throws IOException if creating the user branch fails due to an IO error
+ * @throws OrmException if creating the user branch fails
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/
- public Account insert(Account.Id accountId, Consumer<Account> init)
- throws OrmDuplicateKeyException, IOException, ConfigInvalidException {
- AccountConfig accountConfig = read(accountId);
- Account account = accountConfig.getNewAccount();
- init.accept(account);
+ public Account insert(
+ String message, Account.Id accountId, Consumer<InternalAccountUpdate.Builder> init)
+ throws OrmException, IOException, ConfigInvalidException {
+ return insert(message, accountId, AccountUpdater.fromConsumer(init));
+ }
- // Create in NoteDb
- commitNew(accountConfig);
- return account;
+ /**
+ * Inserts a new account.
+ *
+ * @param message commit message for the account creation, must not be {@code null or empty}
+ * @param accountId ID of the new account
+ * @param updater updater to populate the new account
+ * @return the newly created account
+ * @throws OrmDuplicateKeyException if the account already exists
+ * @throws IOException if creating the user branch fails due to an IO error
+ * @throws OrmException if creating the user branch fails
+ * @throws ConfigInvalidException if any of the account fields has an invalid value
+ */
+ public Account insert(String message, Account.Id accountId, AccountUpdater updater)
+ throws OrmException, IOException, ConfigInvalidException {
+ return updateAccount(
+ r -> {
+ AccountConfig accountConfig = read(r, accountId);
+ Account account =
+ accountConfig.getNewAccount(new Timestamp(committerIdent.getWhen().getTime()));
+ InternalAccountUpdate.Builder updateBuilder = InternalAccountUpdate.builder();
+ updater.update(account, updateBuilder);
+
+ InternalAccountUpdate update = updateBuilder.build();
+ accountConfig.setAccountUpdate(update);
+ ExternalIdNotes extIdNotes = createExternalIdNotes(r, accountId, update);
+ UpdatedAccount updatedAccounts = new UpdatedAccount(message, accountConfig, extIdNotes);
+ updatedAccounts.setCreated(true);
+ return updatedAccounts;
+ });
}
/**
@@ -210,15 +339,18 @@
*
* <p>Changing the registration date of an account is not supported.
*
+ * @param message commit message for the account update, must not be {@code null or empty}
* @param accountId ID of the account
- * @param consumer consumer to update the account, only invoked if the account exists
+ * @param update consumer to update the account, only invoked if the account exists
* @return the updated account, {@code null} if the account doesn't exist
- * @throws IOException if updating the user branch fails
+ * @throws IOException if updating the user branch fails due to an IO error
+ * @throws OrmException if updating the user branch fails
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/
- public Account update(Account.Id accountId, Consumer<Account> consumer)
- throws IOException, ConfigInvalidException {
- return update(accountId, ImmutableList.of(consumer));
+ public Account update(
+ String message, Account.Id accountId, Consumer<InternalAccountUpdate.Builder> update)
+ throws OrmException, IOException, ConfigInvalidException {
+ return update(message, accountId, AccountUpdater.fromConsumer(update));
}
/**
@@ -226,33 +358,45 @@
*
* <p>Changing the registration date of an account is not supported.
*
+ * @param message commit message for the account update, must not be {@code null or empty}
* @param accountId ID of the account
- * @param consumers consumers to update the account, only invoked if the account exists
+ * @param updater updater to update the account, only invoked if the account exists
* @return the updated account, {@code null} if the account doesn't exist
- * @throws IOException if updating the user branch fails
+ * @throws IOException if updating the user branch fails due to an IO error
+ * @throws OrmException if updating the user branch fails
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/
@Nullable
- public Account update(Account.Id accountId, List<Consumer<Account>> consumers)
- throws IOException, ConfigInvalidException {
- AccountConfig accountConfig = read(accountId);
- Optional<Account> account = accountConfig.getLoadedAccount();
- if (!account.isPresent()) {
- return null;
- }
+ public Account update(String message, Account.Id accountId, AccountUpdater updater)
+ throws OrmException, IOException, ConfigInvalidException {
+ return updateAccount(
+ r -> {
+ AccountConfig accountConfig = read(r, accountId);
+ Optional<Account> account = accountConfig.getLoadedAccount();
+ if (!account.isPresent()) {
+ return null;
+ }
- consumers.stream().forEach(c -> c.accept(account.get()));
- commit(accountConfig);
- return account.get();
+ InternalAccountUpdate.Builder updateBuilder = InternalAccountUpdate.builder();
+ updater.update(account.get(), updateBuilder);
+
+ InternalAccountUpdate update = updateBuilder.build();
+ accountConfig.setAccountUpdate(update);
+ ExternalIdNotes extIdNotes = createExternalIdNotes(r, accountId, update);
+ UpdatedAccount updatedAccounts = new UpdatedAccount(message, accountConfig, extIdNotes);
+ return updatedAccounts;
+ });
}
/**
* Deletes the account.
*
* @param account the account that should be deleted
- * @throws IOException if updating the user branch fails
+ * @throws IOException if deleting the user branch fails due to an IO error
+ * @throws OrmException if deleting the user branch fails
+ * @throws ConfigInvalidException
*/
- public void delete(Account account) throws IOException {
+ public void delete(Account account) throws IOException, OrmException, ConfigInvalidException {
deleteByKey(account.getId());
}
@@ -260,15 +404,27 @@
* Deletes the account.
*
* @param accountId the ID of the account that should be deleted
- * @throws IOException if updating the user branch fails
+ * @throws IOException if deleting the user branch fails due to an IO error
+ * @throws OrmException if deleting the user branch fails
+ * @throws ConfigInvalidException
*/
- public void deleteByKey(Account.Id accountId) throws IOException {
- deleteUserBranch(accountId);
+ public void deleteByKey(Account.Id accountId)
+ throws IOException, OrmException, ConfigInvalidException {
+ deleteAccount(accountId);
+ }
+
+ private Account deleteAccount(Account.Id accountId)
+ throws IOException, OrmException, ConfigInvalidException {
+ return retryHelper.execute(
+ () -> {
+ deleteUserBranch(accountId);
+ return null;
+ });
}
private void deleteUserBranch(Account.Id accountId) throws IOException {
try (Repository repo = repoManager.openRepository(allUsersName)) {
- deleteUserBranch(repo, allUsersName, gitRefUpdated, currentUser, committerIdent, accountId);
+ deleteUserBranch(repo, allUsersName, gitRefUpdated, currentUser, authorIdent, accountId);
}
}
@@ -299,34 +455,177 @@
gitRefUpdated.fire(project, ru, user != null ? user.getAccount() : null);
}
- private AccountConfig read(Account.Id accountId) throws IOException, ConfigInvalidException {
- try (Repository repo = repoManager.openRepository(allUsersName)) {
- AccountConfig accountConfig = new AccountConfig(emailValidator, accountId);
- accountConfig.load(repo);
- return accountConfig;
- }
+ private AccountConfig read(Repository allUsersRepo, Account.Id accountId)
+ throws IOException, ConfigInvalidException {
+ AccountConfig accountConfig = new AccountConfig(emailValidator, accountId);
+ accountConfig.load(allUsersRepo);
+
+ afterReadRevision.run();
+
+ return accountConfig;
}
- private void commitNew(AccountConfig accountConfig) throws IOException {
+ private Account updateAccount(AccountUpdate accountUpdate)
+ throws IOException, ConfigInvalidException, OrmException {
+ return retryHelper.execute(
+ () -> {
+ try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
+ UpdatedAccount updatedAccount = accountUpdate.update(allUsersRepo);
+ if (updatedAccount == null) {
+ return null;
+ }
+
+ commit(allUsersRepo, updatedAccount);
+ return updatedAccount.getAccount();
+ }
+ });
+ }
+
+ private ExternalIdNotes createExternalIdNotes(
+ Repository allUsersRepo, Account.Id accountId, InternalAccountUpdate update)
+ throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
+ ExternalIdNotes.checkSameAccount(
+ Iterables.concat(
+ update.getCreatedExternalIds(),
+ update.getUpdatedExternalIds(),
+ update.getDeletedExternalIds()),
+ accountId);
+
+ ExternalIdNotes extIdNotes = extIdNotesFactory.load(allUsersRepo);
+ extIdNotes.replace(update.getDeletedExternalIds(), update.getCreatedExternalIds());
+ extIdNotes.upsert(update.getUpdatedExternalIds());
+ return extIdNotes;
+ }
+
+ private void commit(Repository allUsersRepo, UpdatedAccount updatedAccount) throws IOException {
+ BatchRefUpdate batchRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate();
+ if (updatedAccount.isCreated()) {
+ commitNewAccountConfig(
+ updatedAccount.getMessage(),
+ allUsersRepo,
+ batchRefUpdate,
+ updatedAccount.getAccountConfig());
+ } else {
+ commitAccountConfig(
+ updatedAccount.getMessage(),
+ allUsersRepo,
+ batchRefUpdate,
+ updatedAccount.getAccountConfig());
+ }
+
+ commitExternalIdUpdates(
+ updatedAccount.getMessage(),
+ allUsersRepo,
+ batchRefUpdate,
+ updatedAccount.getExternalIdNotes());
+ RefUpdateUtil.executeChecked(batchRefUpdate, allUsersRepo);
+ updatedAccount.getExternalIdNotes().updateCaches();
+ gitRefUpdated.fire(
+ allUsersName, batchRefUpdate, currentUser != null ? currentUser.getAccount() : null);
+ }
+
+ private void commitNewAccountConfig(
+ String message,
+ Repository allUsersRepo,
+ BatchRefUpdate batchRefUpdate,
+ AccountConfig accountConfig)
+ throws IOException {
// When creating a new account we must allow empty commits so that the user branch gets created
// with an empty commit when no account properties are set and hence no 'account.config' file
// will be created.
- commit(accountConfig, true);
+ commitAccountConfig(message, allUsersRepo, batchRefUpdate, accountConfig, true);
}
- private void commit(AccountConfig accountConfig) throws IOException {
- commit(accountConfig, false);
+ private void commitAccountConfig(
+ String message,
+ Repository allUsersRepo,
+ BatchRefUpdate batchRefUpdate,
+ AccountConfig accountConfig)
+ throws IOException {
+ commitAccountConfig(message, allUsersRepo, batchRefUpdate, accountConfig, false);
}
- private void commit(AccountConfig accountConfig, boolean allowEmptyCommit) throws IOException {
- try (MetaDataUpdate md = metaDataUpdateFactory.create()) {
+ private void commitAccountConfig(
+ String message,
+ Repository allUsersRepo,
+ BatchRefUpdate batchRefUpdate,
+ AccountConfig accountConfig,
+ boolean allowEmptyCommit)
+ throws IOException {
+ try (MetaDataUpdate md = createMetaDataUpdate(message, allUsersRepo, batchRefUpdate)) {
md.setAllowEmpty(allowEmptyCommit);
accountConfig.commit(md);
}
}
+ private void commitExternalIdUpdates(
+ String message,
+ Repository allUsersRepo,
+ BatchRefUpdate batchRefUpdate,
+ ExternalIdNotes extIdNotes)
+ throws IOException {
+ try (MetaDataUpdate md = createMetaDataUpdate(message, allUsersRepo, batchRefUpdate)) {
+ extIdNotes.commit(md);
+ }
+ }
+
+ private MetaDataUpdate createMetaDataUpdate(
+ String message, Repository allUsersRepo, BatchRefUpdate batchRefUpdate) {
+ MetaDataUpdate metaDataUpdate =
+ metaDataUpdateInternalFactory.get().create(allUsersName, allUsersRepo, batchRefUpdate);
+ if (!message.endsWith("\n")) {
+ message = message + "\n";
+ }
+
+ metaDataUpdate.getCommitBuilder().setMessage(message);
+ metaDataUpdate.getCommitBuilder().setCommitter(committerIdent);
+ metaDataUpdate.getCommitBuilder().setAuthor(authorIdent);
+ return metaDataUpdate;
+ }
+
@FunctionalInterface
- private static interface MetaDataUpdateFactory {
- MetaDataUpdate create() throws IOException;
+ private static interface AccountUpdate {
+ UpdatedAccount update(Repository allUsersRepo)
+ throws IOException, ConfigInvalidException, OrmException;
+ }
+
+ private static class UpdatedAccount {
+ private final String message;
+ private final AccountConfig accountConfig;
+ private final ExternalIdNotes extIdNotes;
+
+ private boolean created;
+
+ private UpdatedAccount(
+ String message, AccountConfig accountConfig, ExternalIdNotes extIdNotes) {
+ checkState(!Strings.isNullOrEmpty(message), "message for account update must be set");
+ this.message = checkNotNull(message);
+ this.accountConfig = checkNotNull(accountConfig);
+ this.extIdNotes = checkNotNull(extIdNotes);
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public AccountConfig getAccountConfig() {
+ return accountConfig;
+ }
+
+ public Account getAccount() {
+ return accountConfig.getLoadedAccount().get();
+ }
+
+ public ExternalIdNotes getExternalIdNotes() {
+ return extIdNotes;
+ }
+
+ public void setCreated(boolean created) {
+ this.created = created;
+ }
+
+ public boolean isCreated() {
+ return created;
+ }
}
}
diff --git a/java/com/google/gerrit/server/account/ChangeUserName.java b/java/com/google/gerrit/server/account/ChangeUserName.java
index 3d1a5f6..b332b75 100644
--- a/java/com/google/gerrit/server/account/ChangeUserName.java
+++ b/java/com/google/gerrit/server/account/ChangeUserName.java
@@ -22,7 +22,6 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtorm.server.OrmDuplicateKeyException;
@@ -30,7 +29,6 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
-import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -43,13 +41,17 @@
/** Generic factory to change any user's username. */
public interface Factory {
- ChangeUserName create(IdentifiedUser user, String newUsername);
+ ChangeUserName create(
+ @Assisted("message") String message,
+ IdentifiedUser user,
+ @Assisted("newUsername") String newUsername);
}
private final SshKeyCache sshKeyCache;
private final ExternalIds externalIds;
- private final ExternalIdsUpdate.Server externalIdsUpdateFactory;
+ private final AccountsUpdate.Server accountsUpdate;
+ private final String message;
private final IdentifiedUser user;
private final String newUsername;
@@ -57,12 +59,14 @@
ChangeUserName(
SshKeyCache sshKeyCache,
ExternalIds externalIds,
- ExternalIdsUpdate.Server externalIdsUpdateFactory,
+ AccountsUpdate.Server accountsUpdate,
+ @Assisted("message") String message,
@Assisted IdentifiedUser user,
- @Nullable @Assisted String newUsername) {
+ @Nullable @Assisted("newUsername") String newUsername) {
this.sshKeyCache = sshKeyCache;
this.externalIds = externalIds;
- this.externalIdsUpdateFactory = externalIdsUpdateFactory;
+ this.accountsUpdate = accountsUpdate;
+ this.message = message;
this.user = user;
this.newUsername = newUsername;
}
@@ -71,12 +75,10 @@
public VoidResult call()
throws OrmException, NameAlreadyUsedException, InvalidUserNameException, IOException,
ConfigInvalidException {
- Collection<ExternalId> old = externalIds.byAccount(user.getAccountId(), SCHEME_USERNAME);
- if (!old.isEmpty()) {
+ if (!externalIds.byAccount(user.getAccountId(), SCHEME_USERNAME).isEmpty()) {
throw new IllegalStateException(USERNAME_CANNOT_BE_CHANGED);
}
- ExternalIdsUpdate externalIdsUpdate = externalIdsUpdateFactory.create();
if (newUsername != null && !newUsername.isEmpty()) {
if (!USER_NAME_PATTERN.matcher(newUsername).matches()) {
throw new InvalidUserNameException();
@@ -84,13 +86,12 @@
ExternalId.Key key = ExternalId.Key.create(SCHEME_USERNAME, newUsername);
try {
- String password = null;
- for (ExternalId i : old) {
- if (i.password() != null) {
- password = i.password();
- }
- }
- externalIdsUpdate.insert(ExternalId.create(key, user.getAccountId(), null, password));
+ accountsUpdate
+ .create()
+ .update(
+ message,
+ user.getAccountId(),
+ u -> u.addExternalId(ExternalId.create(key, user.getAccountId(), null, null)));
} catch (OrmDuplicateKeyException dupeErr) {
// If we are using this identity, don't report the exception.
//
@@ -105,13 +106,6 @@
}
}
- // If we have any older user names, remove them.
- //
- externalIdsUpdate.delete(old);
- for (ExternalId extId : old) {
- sshKeyCache.evict(extId.key().id());
- }
-
sshKeyCache.evict(newUsername);
return VoidResult.INSTANCE;
}
diff --git a/java/com/google/gerrit/server/account/CreateAccount.java b/java/com/google/gerrit/server/account/CreateAccount.java
index ed92a7e..2ce13ea 100644
--- a/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/account/CreateAccount.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.account;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -37,16 +38,14 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.group.GroupsCollection;
import com.google.gerrit.server.group.UserInitiated;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.group.db.InternalGroupUpdate;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gerrit.server.ssh.SshKeyCache;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -72,8 +71,6 @@
private final AccountsUpdate.User accountsUpdate;
private final AccountLoader.Factory infoLoader;
private final DynamicSet<AccountExternalIdCreator> externalIdCreators;
- private final ExternalIds externalIds;
- private final ExternalIdsUpdate.User externalIdsUpdateFactory;
private final Provider<GroupsUpdate> groupsUpdate;
private final OutgoingEmailValidator validator;
private final String username;
@@ -88,8 +85,6 @@
AccountsUpdate.User accountsUpdate,
AccountLoader.Factory infoLoader,
DynamicSet<AccountExternalIdCreator> externalIdCreators,
- ExternalIds externalIds,
- ExternalIdsUpdate.User externalIdsUpdateFactory,
@UserInitiated Provider<GroupsUpdate> groupsUpdate,
OutgoingEmailValidator validator,
@Assisted String username) {
@@ -101,8 +96,6 @@
this.accountsUpdate = accountsUpdate;
this.infoLoader = infoLoader;
this.externalIdCreators = externalIdCreators;
- this.externalIds = externalIds;
- this.externalIdsUpdateFactory = externalIdsUpdateFactory;
this.groupsUpdate = groupsUpdate;
this.validator = validator;
this.username = username;
@@ -130,55 +123,40 @@
Set<AccountGroup.UUID> groups = parseGroups(input.groups);
Account.Id id = new Account.Id(seq.nextAccountId());
+ List<ExternalId> extIds = new ArrayList<>();
- ExternalId extUser = ExternalId.createUsername(username, id, input.httpPassword);
- if (externalIds.get(extUser.key()) != null) {
- throw new ResourceConflictException("username '" + username + "' already exists");
- }
if (input.email != null) {
- if (externalIds.get(ExternalId.Key.create(SCHEME_MAILTO, input.email)) != null) {
- throw new UnprocessableEntityException("email '" + input.email + "' already exists");
- }
if (!validator.isValid(input.email)) {
throw new BadRequestException("invalid email address");
}
+ extIds.add(ExternalId.createEmail(id, input.email));
}
- List<ExternalId> extIds = new ArrayList<>();
- extIds.add(extUser);
+ extIds.add(ExternalId.createUsername(username, id, input.httpPassword));
for (AccountExternalIdCreator c : externalIdCreators) {
extIds.addAll(c.create(id, username, input.email));
}
- ExternalIdsUpdate externalIdsUpdate = externalIdsUpdateFactory.create();
try {
- externalIdsUpdate.insert(extIds);
- } catch (OrmDuplicateKeyException duplicateKey) {
- throw new ResourceConflictException("username '" + username + "' already exists");
- }
-
- if (input.email != null) {
- try {
- externalIdsUpdate.insert(ExternalId.createEmail(id, input.email));
- } catch (OrmDuplicateKeyException duplicateKey) {
- try {
- externalIdsUpdate.delete(extUser);
- } catch (IOException | ConfigInvalidException cleanupError) {
- // Ignored
- }
- throw new UnprocessableEntityException("email '" + input.email + "' already exists");
+ accountsUpdate
+ .create()
+ .insert(
+ "Create Account via API",
+ id,
+ u -> u.setFullName(input.name).setPreferredEmail(input.email).addExternalIds(extIds));
+ } catch (DuplicateExternalIdKeyException e) {
+ if (e.getDuplicateKey().isScheme(SCHEME_USERNAME)) {
+ throw new ResourceConflictException(
+ "username '" + e.getDuplicateKey().id() + "' already exists");
+ } else if (e.getDuplicateKey().isScheme(SCHEME_MAILTO)) {
+ throw new UnprocessableEntityException(
+ "email '" + e.getDuplicateKey().id() + "' already exists");
+ } else {
+ // AccountExternalIdCreator returned an external ID that already exists
+ throw e;
}
}
- accountsUpdate
- .create()
- .insert(
- id,
- a -> {
- a.setFullName(input.name);
- a.setPreferredEmail(input.email);
- });
-
for (AccountGroup.UUID groupUuid : groups) {
try {
addGroupMember(groupUuid, id);
diff --git a/java/com/google/gerrit/server/account/InternalAccountUpdate.java b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
new file mode 100644
index 0000000..ea778ca
--- /dev/null
+++ b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
@@ -0,0 +1,390 @@
+// Copyright (C) 2017 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 Licens
+
+package com.google.gerrit.server.account;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Class to prepare updates to an account.
+ *
+ * <p>The getters in this class and the setters in the {@link Builder} correspond to fields in
+ * {@link Account}. The account ID and the registration date cannot be updated.
+ */
+@AutoValue
+public abstract class InternalAccountUpdate {
+ public static Builder builder() {
+ return new Builder.WrapperThatConvertsNullStringArgsToEmptyStrings(
+ new AutoValue_InternalAccountUpdate.Builder());
+ }
+
+ /**
+ * Returns the new value for the full name.
+ *
+ * @return the new value for the full name, {@code Optional#empty()} if the full name is not being
+ * updated, {@code Optional#of("")} if the full name is unset, the wrapped value is never
+ * {@code null}
+ */
+ public abstract Optional<String> getFullName();
+
+ /**
+ * Returns the new value for the preferred email.
+ *
+ * @return the new value for the preferred email, {@code Optional#empty()} if the preferred email
+ * is not being updated, {@code Optional#of("")} if the preferred email is unset, the wrapped
+ * value is never {@code null}
+ */
+ public abstract Optional<String> getPreferredEmail();
+
+ /**
+ * Returns the new value for the active flag.
+ *
+ * @return the new value for the active flag, {@code Optional#empty()} if the active flag is not
+ * being updated, the wrapped value is never {@code null}
+ */
+ public abstract Optional<Boolean> getActive();
+
+ /**
+ * Returns the new value for the status.
+ *
+ * @return the new value for the status, {@code Optional#empty()} if the status is not being
+ * updated, {@code Optional#of("")} if the status is unset, the wrapped value is never {@code
+ * null}
+ */
+ public abstract Optional<String> getStatus();
+
+ /**
+ * Returns external IDs that should be newly created for the account.
+ *
+ * @return external IDs that should be newly created for the account
+ */
+ public abstract ImmutableSet<ExternalId> getCreatedExternalIds();
+
+ /**
+ * Returns external IDs that should be updated for the account.
+ *
+ * @return external IDs that should be updated for the account
+ */
+ public abstract ImmutableSet<ExternalId> getUpdatedExternalIds();
+
+ /**
+ * Returns external IDs that should be deleted for the account.
+ *
+ * @return external IDs that should be deleted for the account
+ */
+ public abstract ImmutableSet<ExternalId> getDeletedExternalIds();
+
+ /**
+ * Class to build an account update.
+ *
+ * <p>Account data is only updated if the corresponding setter is invoked. If a setter is not
+ * invoked the corresponding data stays unchanged. To unset string values the setter can be
+ * invoked with either {@code null} or an empty string ({@code null} is converted to an empty
+ * string by using the {@link WrapperThatConvertsNullStringArgsToEmptyStrings} wrapper, see {@link
+ * InternalAccountUpdate#builder()}).
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets a new full name for the account.
+ *
+ * @param fullName the new full name, if {@code null} or empty string the full name is unset
+ * @return the builder
+ */
+ public abstract Builder setFullName(String fullName);
+
+ /**
+ * Sets a new preferred email for the account.
+ *
+ * @param preferredEmail the new preferred email, if {@code null} or empty string the preferred
+ * email is unset
+ * @return the builder
+ */
+ public abstract Builder setPreferredEmail(String preferredEmail);
+
+ /**
+ * Sets the active flag for the account.
+ *
+ * @param active {@code true} if the account should be set to active, {@code false} if the
+ * account should be set to inactive
+ * @return the builder
+ */
+ public abstract Builder setActive(boolean active);
+
+ /**
+ * Sets a new status for the account.
+ *
+ * @param status the new status, if {@code null} or empty string the status is unset
+ * @return the builder
+ */
+ public abstract Builder setStatus(String status);
+
+ /**
+ * Returns a builder for the set of created external IDs.
+ *
+ * @return builder for the set of created external IDs.
+ */
+ abstract ImmutableSet.Builder<ExternalId> createdExternalIdsBuilder();
+
+ /**
+ * Adds a new external ID for the account.
+ *
+ * <p>The account ID of the external ID must match the account ID of the account that is
+ * updated.
+ *
+ * <p>If an external ID with the same ID already exists the account update will fail with {@link
+ * DuplicateExternalIdKeyException}.
+ *
+ * @param extId external ID that should be added
+ * @return the builder
+ */
+ public Builder addExternalId(ExternalId extId) {
+ return addExternalIds(ImmutableSet.of(extId));
+ }
+
+ /**
+ * Adds new external IDs for the account.
+ *
+ * <p>The account IDs of the external IDs must match the account ID of the account that is
+ * updated.
+ *
+ * <p>If any of the external ID keys already exists, the insert fails with {@link
+ * DuplicateExternalIdKeyException}.
+ *
+ * @param extIds external IDs that should be added
+ * @return the builder
+ */
+ public Builder addExternalIds(Collection<ExternalId> extIds) {
+ createdExternalIdsBuilder().addAll(extIds);
+ return this;
+ }
+
+ /**
+ * Returns a builder for the set of updated external IDs.
+ *
+ * @return builder for the set of updated external IDs.
+ */
+ abstract ImmutableSet.Builder<ExternalId> updatedExternalIdsBuilder();
+
+ /**
+ * Updates an external ID for the account.
+ *
+ * <p>The account ID of the external ID must match the account ID of the account that is
+ * updated.
+ *
+ * <p>If no external ID with the ID exists the external ID is created.
+ *
+ * @param extId external ID that should be updated
+ * @return the builder
+ */
+ public Builder updateExternalId(ExternalId extId) {
+ return updateExternalIds(ImmutableSet.of(extId));
+ }
+
+ /**
+ * Updates external IDs for the account.
+ *
+ * <p>The account IDs of the external IDs must match the account ID of the account that is
+ * updated.
+ *
+ * <p>If any of the external IDs already exists, it is overwritten. New external IDs are
+ * inserted.
+ *
+ * @param extIds external IDs that should be updated
+ * @return the builder
+ */
+ public Builder updateExternalIds(Collection<ExternalId> extIds) {
+ updatedExternalIdsBuilder().addAll(extIds);
+ return this;
+ }
+
+ /**
+ * Returns a builder for the set of deleted external IDs.
+ *
+ * @return builder for the set of deleted external IDs.
+ */
+ abstract ImmutableSet.Builder<ExternalId> deletedExternalIdsBuilder();
+
+ /**
+ * Deletes an external ID for the account.
+ *
+ * <p>The account ID of the external ID must match the account ID of the account that is
+ * updated.
+ *
+ * <p>If no external ID with the ID exists this is a no-op.
+ *
+ * @param extId external ID that should be deleted
+ * @return the builder
+ */
+ public Builder deleteExternalId(ExternalId extId) {
+ return deleteExternalIds(ImmutableSet.of(extId));
+ }
+
+ /**
+ * Delete external IDs for the account.
+ *
+ * <p>The account IDs of the external IDs must match the account ID of the account that is
+ * updated.
+ *
+ * <p>For non-existing external IDs this is a no-op.
+ *
+ * @param extIds external IDs that should be deleted
+ * @return the builder
+ */
+ public Builder deleteExternalIds(Collection<ExternalId> extIds) {
+ deletedExternalIdsBuilder().addAll(extIds);
+ return this;
+ }
+
+ /**
+ * Replaces an external ID.
+ *
+ * @param extIdToDelete external ID that should be deleted
+ * @param extIdToAdd external ID that should be added
+ * @return the builder
+ */
+ public Builder replaceExternalId(ExternalId extIdToDelete, ExternalId extIdToAdd) {
+ return replaceExternalIds(ImmutableSet.of(extIdToDelete), ImmutableSet.of(extIdToAdd));
+ }
+
+ /**
+ * Replaces an external IDs.
+ *
+ * @param extIdsToDelete external IDs that should be deleted
+ * @param extIdsToAdd external IDs that should be added
+ * @return the builder
+ */
+ public Builder replaceExternalIds(
+ Collection<ExternalId> extIdsToDelete, Collection<ExternalId> extIdsToAdd) {
+ return deleteExternalIds(extIdsToDelete).addExternalIds(extIdsToAdd);
+ }
+
+ /**
+ * Builds the account update.
+ *
+ * @return the account update
+ */
+ public abstract InternalAccountUpdate build();
+
+ /**
+ * Wrapper for {@link Builder} that converts {@code null} string arguments to empty strings for
+ * all setter methods. This allows us to treat setter invocations with a {@code null} string
+ * argument as signal to unset the corresponding field. E.g. for a builder method {@code
+ * setX(String)} the following semantics apply:
+ *
+ * <ul>
+ * <li>Method is not invoked: X stays unchanged, X is stored as {@code Optional.empty()}.
+ * <li>Argument is a non-empty string Y: X is updated to the Y, X is stored as {@code
+ * Optional.of(Y)}.
+ * <li>Argument is an empty string: X is unset, X is stored as {@code Optional.of("")}
+ * <li>Argument is {@code null}: X is unset, X is stored as {@code Optional.of("")} (since the
+ * wrapper converts {@code null} to an empty string)
+ * </ul>
+ *
+ * Without the wrapper calling {@code setX(null)} would fail with a {@link
+ * NullPointerException}. Hence all callers would need to take care to call {@link
+ * Strings#nullToEmpty(String)} for all string arguments and likely it would be forgotten in
+ * some places.
+ *
+ * <p>This means the stored values are interpreted like this:
+ *
+ * <ul>
+ * <li>{@code Optional.empty()}: property stays unchanged
+ * <li>{@code Optional.of(<non-empty-string>)}: property is updated
+ * <li>{@code Optional.of("")}: property is unset
+ * </ul>
+ *
+ * This wrapper forwards all method invocations to the wrapped {@link Builder} instance that was
+ * created by AutoValue. For methods that return the AutoValue {@link Builder} instance the
+ * return value is replaced with the wrapper instance so that all chained calls go through the
+ * wrapper.
+ */
+ private static class WrapperThatConvertsNullStringArgsToEmptyStrings extends Builder {
+ private final Builder delegate;
+
+ private WrapperThatConvertsNullStringArgsToEmptyStrings(Builder delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Builder setFullName(String fullName) {
+ delegate.setFullName(Strings.nullToEmpty(fullName));
+ return this;
+ }
+
+ @Override
+ public Builder setPreferredEmail(String preferredEmail) {
+ delegate.setPreferredEmail(Strings.nullToEmpty(preferredEmail));
+ return this;
+ }
+
+ @Override
+ public Builder setActive(boolean active) {
+ delegate.setActive(active);
+ return this;
+ }
+
+ @Override
+ public Builder setStatus(String status) {
+ delegate.setStatus(Strings.nullToEmpty(status));
+ return this;
+ }
+
+ @Override
+ public InternalAccountUpdate build() {
+ return delegate.build();
+ }
+
+ @Override
+ ImmutableSet.Builder<ExternalId> createdExternalIdsBuilder() {
+ return delegate.createdExternalIdsBuilder();
+ }
+
+ @Override
+ public Builder addExternalIds(Collection<ExternalId> extIds) {
+ delegate.addExternalIds(extIds);
+ return this;
+ }
+
+ @Override
+ ImmutableSet.Builder<ExternalId> updatedExternalIdsBuilder() {
+ return delegate.updatedExternalIdsBuilder();
+ }
+
+ @Override
+ public Builder updateExternalIds(Collection<ExternalId> extIds) {
+ delegate.updateExternalIds(extIds);
+ return this;
+ }
+
+ @Override
+ ImmutableSet.Builder<ExternalId> deletedExternalIdsBuilder() {
+ return delegate.deletedExternalIdsBuilder();
+ }
+
+ @Override
+ public Builder deleteExternalIds(Collection<ExternalId> extIds) {
+ delegate.deleteExternalIds(extIds);
+ return this;
+ }
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/account/PutName.java b/java/com/google/gerrit/server/account/PutName.java
index 0ac9d1d..b63c548 100644
--- a/java/com/google/gerrit/server/account/PutName.java
+++ b/java/com/google/gerrit/server/account/PutName.java
@@ -66,7 +66,7 @@
public Response<String> apply(IdentifiedUser user, NameInput input)
throws MethodNotAllowedException, ResourceNotFoundException, IOException,
- ConfigInvalidException {
+ ConfigInvalidException, OrmException {
if (input == null) {
input = new NameInput();
}
@@ -77,7 +77,9 @@
String newName = input.name;
Account account =
- accountsUpdate.create().update(user.getAccountId(), a -> a.setFullName(newName));
+ accountsUpdate
+ .create()
+ .update("Set Full Name via API", user.getAccountId(), u -> u.setFullName(newName));
if (account == null) {
throw new ResourceNotFoundException("account not found");
}
diff --git a/java/com/google/gerrit/server/account/PutPreferred.java b/java/com/google/gerrit/server/account/PutPreferred.java
index 5f9ddee..40e2f7a 100644
--- a/java/com/google/gerrit/server/account/PutPreferred.java
+++ b/java/com/google/gerrit/server/account/PutPreferred.java
@@ -61,18 +61,19 @@
}
public Response<String> apply(IdentifiedUser user, String email)
- throws ResourceNotFoundException, IOException, ConfigInvalidException {
+ throws ResourceNotFoundException, IOException, ConfigInvalidException, OrmException {
AtomicBoolean alreadyPreferred = new AtomicBoolean(false);
Account account =
accountsUpdate
.create()
.update(
+ "Set Preferred Email via API",
user.getAccountId(),
- a -> {
+ (a, u) -> {
if (email.equals(a.getPreferredEmail())) {
alreadyPreferred.set(true);
} else {
- a.setPreferredEmail(email);
+ u.setPreferredEmail(email);
}
});
if (account == null) {
diff --git a/java/com/google/gerrit/server/account/PutStatus.java b/java/com/google/gerrit/server/account/PutStatus.java
index 35ece15..85db1da 100644
--- a/java/com/google/gerrit/server/account/PutStatus.java
+++ b/java/com/google/gerrit/server/account/PutStatus.java
@@ -60,7 +60,7 @@
}
public Response<String> apply(IdentifiedUser user, StatusInput input)
- throws ResourceNotFoundException, IOException, ConfigInvalidException {
+ throws ResourceNotFoundException, IOException, ConfigInvalidException, OrmException {
if (input == null) {
input = new StatusInput();
}
@@ -69,7 +69,7 @@
Account account =
accountsUpdate
.create()
- .update(user.getAccountId(), a -> a.setStatus(Strings.nullToEmpty(newStatus)));
+ .update("Set Status via API", user.getAccountId(), u -> u.setStatus(newStatus));
if (account == null) {
throw new ResourceNotFoundException("account not found");
}
diff --git a/java/com/google/gerrit/server/account/PutUsername.java b/java/com/google/gerrit/server/account/PutUsername.java
index 2368913..f4ff79d 100644
--- a/java/com/google/gerrit/server/account/PutUsername.java
+++ b/java/com/google/gerrit/server/account/PutUsername.java
@@ -70,7 +70,7 @@
}
try {
- changeUserNameFactory.create(rsrc.getUser(), input.username).call();
+ changeUserNameFactory.create("Set Username via API", rsrc.getUser(), input.username).call();
} catch (IllegalStateException e) {
if (ChangeUserName.USERNAME_CANNOT_BE_CHANGED.equals(e.getMessage())) {
throw new MethodNotAllowedException(e.getMessage());
diff --git a/java/com/google/gerrit/server/account/SetInactiveFlag.java b/java/com/google/gerrit/server/account/SetInactiveFlag.java
index 6e12c3e..f8cd650 100644
--- a/java/com/google/gerrit/server/account/SetInactiveFlag.java
+++ b/java/com/google/gerrit/server/account/SetInactiveFlag.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -36,18 +37,19 @@
}
public Response<?> deactivate(Account.Id accountId)
- throws RestApiException, IOException, ConfigInvalidException {
+ throws RestApiException, IOException, ConfigInvalidException, OrmException {
AtomicBoolean alreadyInactive = new AtomicBoolean(false);
Account account =
accountsUpdate
.create()
.update(
+ "Deactivate Account via API",
accountId,
- a -> {
+ (a, u) -> {
if (!a.isActive()) {
alreadyInactive.set(true);
} else {
- a.setActive(false);
+ u.setActive(false);
}
});
if (account == null) {
@@ -60,18 +62,19 @@
}
public Response<String> activate(Account.Id accountId)
- throws ResourceNotFoundException, IOException, ConfigInvalidException {
+ throws ResourceNotFoundException, IOException, ConfigInvalidException, OrmException {
AtomicBoolean alreadyActive = new AtomicBoolean(false);
Account account =
accountsUpdate
.create()
.update(
+ "Activate Account via API",
accountId,
- a -> {
+ (a, u) -> {
if (a.isActive()) {
alreadyActive.set(true);
} else {
- a.setActive(true);
+ u.setActive(true);
}
});
if (account == null) {
diff --git a/java/com/google/gerrit/server/account/StarredChanges.java b/java/com/google/gerrit/server/account/StarredChanges.java
index 38c95e6..6dfd132 100644
--- a/java/com/google/gerrit/server/account/StarredChanges.java
+++ b/java/com/google/gerrit/server/account/StarredChanges.java
@@ -72,7 +72,7 @@
@Override
public AccountResource.StarredChange parse(AccountResource parent, IdString id)
- throws ResourceNotFoundException, OrmException, PermissionBackendException {
+ throws RestApiException, OrmException, PermissionBackendException {
IdentifiedUser user = parent.getUser();
ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
if (starredChangesUtil
@@ -103,7 +103,7 @@
@Override
public RestModifyView<AccountResource, EmptyInput> create(AccountResource parent, IdString id)
- throws UnprocessableEntityException {
+ throws RestApiException {
try {
return createProvider.get().setChange(changes.parse(TopLevelResource.INSTANCE, id));
} catch (ResourceNotFoundException e) {
diff --git a/java/com/google/gerrit/server/account/Stars.java b/java/com/google/gerrit/server/account/Stars.java
index 9019ad7..5eb8d7b 100644
--- a/java/com/google/gerrit/server/account/Stars.java
+++ b/java/com/google/gerrit/server/account/Stars.java
@@ -21,7 +21,7 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
@@ -66,7 +66,7 @@
@Override
public Star parse(AccountResource parent, IdString id)
- throws ResourceNotFoundException, OrmException, PermissionBackendException {
+ throws RestApiException, OrmException, PermissionBackendException {
IdentifiedUser user = parent.getUser();
ChangeResource change = changes.parse(TopLevelResource.INSTANCE, id);
Set<String> labels = starredChangesUtil.getLabels(user.getAccountId(), change.getId());
diff --git a/java/com/google/gerrit/server/account/externalids/DuplicateExternalIdKeyException.java b/java/com/google/gerrit/server/account/externalids/DuplicateExternalIdKeyException.java
new file mode 100644
index 0000000..b4c82d0
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/DuplicateExternalIdKeyException.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 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.account.externalids;
+
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+
+/**
+ * Exception that is thrown if an external ID cannot be inserted because an external ID with the
+ * same key already exists.
+ */
+public class DuplicateExternalIdKeyException extends OrmDuplicateKeyException {
+ private static final long serialVersionUID = 1L;
+
+ private final ExternalId.Key duplicateKey;
+
+ public DuplicateExternalIdKeyException(ExternalId.Key duplicateKey) {
+ super("Duplicate external ID key: " + duplicateKey.get());
+ this.duplicateKey = duplicateKey;
+ }
+
+ public ExternalId.Key getDuplicateKey() {
+ return duplicateKey;
+ }
+}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index ad119ca..1ac8f3d 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -16,11 +16,13 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
@@ -28,6 +30,7 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.HashedPassword;
import java.io.Serializable;
+import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -123,6 +126,10 @@
public String toString() {
return get();
}
+
+ public static ImmutableSet<ExternalId.Key> from(Collection<ExternalId> extIds) {
+ return extIds.stream().map(ExternalId::key).collect(toImmutableSet());
+ }
}
public static ExternalId create(String scheme, String id, Account.Id accountId) {
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
index 311e70f..6f80713 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
@@ -127,7 +127,7 @@
Collection<ExternalId> toRemove,
Collection<ExternalId> toAdd)
throws IOException {
- ExternalIdsUpdate.checkSameAccount(Iterables.concat(toRemove, toAdd), accountId);
+ ExternalIdNotes.checkSameAccount(Iterables.concat(toRemove, toAdd), accountId);
updateCache(
oldNotesRev,
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
new file mode 100644
index 0000000..d8148c6
--- /dev/null
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -0,0 +1,754 @@
+// Copyright (C) 2017 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.account.externalids;
+
+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.stream.Collectors.toSet;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.VersionedMetaData;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.BlobBasedConfig;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link VersionedMetaData} subclass to update external IDs.
+ *
+ * <p>On load the note map from {@code refs/meta/external-ids} is read, but the external IDs are not
+ * parsed yet (see {@link #onLoad()}).
+ *
+ * <p>After loading the note map callers can access single or all external IDs. Only now the
+ * requested external IDs are parsed.
+ *
+ * <p>After loading the note map callers can stage various external ID updates (insert, upsert,
+ * delete, replace).
+ *
+ * <p>On save the staged external ID updates are performed (see {@link #onSave(CommitBuilder)}).
+ *
+ * <p>After committing the external IDs a cache update can be requested which also reindexes the
+ * accounts for which external IDs have been updated (see {@link #updateCaches()}).
+ */
+public class ExternalIdNotes extends VersionedMetaData {
+ private static final Logger log = LoggerFactory.getLogger(ExternalIdNotes.class);
+
+ private static final int MAX_NOTE_SZ = 1 << 19;
+
+ @Singleton
+ public static class Factory {
+ private final ExternalIdCache externalIdCache;
+ private final AccountCache accountCache;
+
+ @Inject
+ Factory(ExternalIdCache externalIdCache, AccountCache accountCache) {
+ this.externalIdCache = externalIdCache;
+ this.accountCache = accountCache;
+ }
+
+ public ExternalIdNotes load(Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return new ExternalIdNotes(externalIdCache, accountCache, allUsersRepo).load();
+ }
+ }
+
+ @Singleton
+ public static class FactoryNoReindex {
+ private final ExternalIdCache externalIdCache;
+
+ @Inject
+ FactoryNoReindex(ExternalIdCache externalIdCache) {
+ this.externalIdCache = externalIdCache;
+ }
+
+ public ExternalIdNotes load(Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return new ExternalIdNotes(externalIdCache, null, allUsersRepo).load();
+ }
+ }
+
+ public static ExternalIdNotes loadReadOnly(Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return new ExternalIdNotes(new DisabledExternalIdCache(), null, allUsersRepo)
+ .setReadOnly()
+ .load();
+ }
+
+ public static ExternalIdNotes loadReadOnly(Repository allUsersRepo, ObjectId rev)
+ throws IOException, ConfigInvalidException {
+ return new ExternalIdNotes(new DisabledExternalIdCache(), null, allUsersRepo)
+ .setReadOnly()
+ .load(rev);
+ }
+
+ public static ExternalIdNotes loadNoCacheUpdate(Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return new ExternalIdNotes(new DisabledExternalIdCache(), null, allUsersRepo).load();
+ }
+
+ private final ExternalIdCache externalIdCache;
+ @Nullable private final AccountCache accountCache;
+ private final Repository repo;
+
+ private NoteMap noteMap;
+ private ObjectId oldRev;
+
+ // Staged note map updates that should be executed on save.
+ private List<NoteMapUpdate> noteMapUpdates = new ArrayList<>();
+
+ // Staged cache updates that should be executed after external ID changes have been committed.
+ private List<CacheUpdate> cacheUpdates = new ArrayList<>();
+
+ private Runnable afterReadRevision;
+ private boolean readOnly = false;
+
+ ExternalIdNotes(
+ ExternalIdCache externalIdCache,
+ @Nullable AccountCache accountCache,
+ Repository allUsersRepo) {
+ this.externalIdCache = checkNotNull(externalIdCache, "externalIdCache");
+ this.accountCache = accountCache;
+ this.repo = checkNotNull(allUsersRepo, "allUsersRepo");
+ }
+
+ public ExternalIdNotes setAfterReadRevision(Runnable afterReadRevision) {
+ this.afterReadRevision = afterReadRevision;
+ return this;
+ }
+
+ private ExternalIdNotes setReadOnly() {
+ this.readOnly = true;
+ return this;
+ }
+
+ public Repository getRepository() {
+ return repo;
+ }
+
+ @Override
+ protected String getRefName() {
+ return RefNames.REFS_EXTERNAL_IDS;
+ }
+
+ ExternalIdNotes load() throws IOException, ConfigInvalidException {
+ load(repo);
+ return this;
+ }
+
+ ExternalIdNotes load(ObjectId rev) throws IOException, ConfigInvalidException {
+ load(repo, rev);
+ return this;
+ }
+
+ /**
+ * Parses and returns the specified external ID.
+ *
+ * @param key the key of the external ID
+ * @return the external ID, {@code Optional.empty()} if it doesn't exist
+ */
+ public Optional<ExternalId> get(ExternalId.Key key) throws IOException, ConfigInvalidException {
+ checkLoaded();
+ ObjectId noteId = key.sha1();
+ if (!noteMap.contains(noteId)) {
+ return Optional.empty();
+ }
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ ObjectId noteDataId = noteMap.get(noteId);
+ byte[] raw = readNoteData(rw, noteDataId);
+ return Optional.of(ExternalId.parse(noteId.name(), raw, noteDataId));
+ }
+ }
+
+ /**
+ * Parses and returns the specified external IDs.
+ *
+ * @param keys the keys of the external IDs
+ * @return the external IDs
+ */
+ public Set<ExternalId> get(Collection<ExternalId.Key> keys)
+ throws IOException, ConfigInvalidException {
+ checkLoaded();
+ HashSet<ExternalId> externalIds = Sets.newHashSetWithExpectedSize(keys.size());
+ for (ExternalId.Key key : keys) {
+ get(key).ifPresent(externalIds::add);
+ }
+ return externalIds;
+ }
+
+ /**
+ * Parses and returns all external IDs.
+ *
+ * <p>Invalid external IDs are ignored.
+ *
+ * @return all external IDs
+ */
+ public Set<ExternalId> all() throws IOException {
+ checkLoaded();
+ try (RevWalk rw = new RevWalk(repo)) {
+ Set<ExternalId> extIds = new HashSet<>();
+ for (Note note : noteMap) {
+ byte[] raw = readNoteData(rw, note.getData());
+ try {
+ extIds.add(ExternalId.parse(note.getName(), raw, note.getData()));
+ } catch (ConfigInvalidException | RuntimeException e) {
+ log.error(String.format("Ignoring invalid external ID note %s", note.getName()), e);
+ }
+ }
+ return extIds;
+ }
+ }
+
+ NoteMap getNoteMap() {
+ checkLoaded();
+ return noteMap;
+ }
+
+ static byte[] readNoteData(RevWalk rw, ObjectId noteDataId) throws IOException {
+ return rw.getObjectReader().open(noteDataId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
+ }
+
+ /**
+ * Inserts a new external ID.
+ *
+ * @throws IOException on IO error while checking if external ID already exists
+ * @throws DuplicateExternalIdKeyException if the external ID already exists
+ */
+ public void insert(ExternalId extId) throws IOException, DuplicateExternalIdKeyException {
+ insert(Collections.singleton(extId));
+ }
+
+ /**
+ * Inserts new external IDs.
+ *
+ * @throws IOException on IO error while checking if external IDs already exist
+ * @throws DuplicateExternalIdKeyException if any of the external ID already exists
+ */
+ public void insert(Collection<ExternalId> extIds)
+ throws IOException, DuplicateExternalIdKeyException {
+ checkLoaded();
+ checkExternalIdsDontExist(extIds);
+
+ Set<ExternalId> newExtIds = new HashSet<>();
+ noteMapUpdates.add(
+ (rw, n) -> {
+ for (ExternalId extId : extIds) {
+ ExternalId insertedExtId = upsert(rw, inserter, noteMap, extId);
+ newExtIds.add(insertedExtId);
+ }
+ });
+ cacheUpdates.add(cu -> cu.add(newExtIds));
+ }
+
+ /**
+ * Inserts or updates an external ID.
+ *
+ * <p>If the external ID already exists, it is overwritten, otherwise it is inserted.
+ */
+ public void upsert(ExternalId extId) throws IOException, ConfigInvalidException {
+ upsert(Collections.singleton(extId));
+ }
+
+ /**
+ * Inserts or updates external IDs.
+ *
+ * <p>If any of the external IDs already exists, it is overwritten. New external IDs are inserted.
+ */
+ public void upsert(Collection<ExternalId> extIds) throws IOException, ConfigInvalidException {
+ checkLoaded();
+ Set<ExternalId> removedExtIds = get(ExternalId.Key.from(extIds));
+ Set<ExternalId> updatedExtIds = new HashSet<>();
+ noteMapUpdates.add(
+ (rw, n) -> {
+ for (ExternalId extId : extIds) {
+ ExternalId updatedExtId = upsert(rw, inserter, noteMap, extId);
+ updatedExtIds.add(updatedExtId);
+ }
+ });
+ cacheUpdates.add(cu -> cu.remove(removedExtIds).add(updatedExtIds));
+ }
+
+ /**
+ * Deletes an external ID.
+ *
+ * @throws IllegalStateException is thrown if there is an existing external ID that has the same
+ * key, but otherwise doesn't match the specified external ID.
+ */
+ public void delete(ExternalId extId) {
+ delete(Collections.singleton(extId));
+ }
+
+ /**
+ * Deletes external IDs.
+ *
+ * @throws IllegalStateException is thrown if there is an existing external ID that has the same
+ * key as any of the external IDs that should be deleted, but otherwise doesn't match the that
+ * external ID.
+ */
+ public void delete(Collection<ExternalId> extIds) {
+ checkLoaded();
+ Set<ExternalId> removedExtIds = new HashSet<>();
+ noteMapUpdates.add(
+ (rw, n) -> {
+ for (ExternalId extId : extIds) {
+ remove(rw, noteMap, extId);
+ removedExtIds.add(extId);
+ }
+ });
+ cacheUpdates.add(cu -> cu.remove(removedExtIds));
+ }
+
+ /**
+ * Delete an external ID by key.
+ *
+ * @throws IllegalStateException is thrown if the external ID does not belong to the specified
+ * account.
+ */
+ public void delete(Account.Id accountId, ExternalId.Key extIdKey) {
+ delete(accountId, Collections.singleton(extIdKey));
+ }
+
+ /**
+ * Delete external IDs by external ID key.
+ *
+ * @throws IllegalStateException is thrown if any of the external IDs does not belong to the
+ * specified account.
+ */
+ public void delete(Account.Id accountId, Collection<ExternalId.Key> extIdKeys) {
+ checkLoaded();
+ Set<ExternalId> removedExtIds = new HashSet<>();
+ noteMapUpdates.add(
+ (rw, n) -> {
+ for (ExternalId.Key extIdKey : extIdKeys) {
+ ExternalId removedExtId = remove(rw, noteMap, extIdKey, accountId);
+ removedExtIds.add(removedExtId);
+ }
+ });
+ cacheUpdates.add(cu -> cu.remove(removedExtIds));
+ }
+
+ /**
+ * Delete external IDs by external ID key.
+ *
+ * <p>The external IDs are deleted regardless of which account they belong to.
+ */
+ public void deleteByKeys(Collection<ExternalId.Key> extIdKeys) {
+ checkLoaded();
+ Set<ExternalId> removedExtIds = new HashSet<>();
+ noteMapUpdates.add(
+ (rw, n) -> {
+ for (ExternalId.Key extIdKey : extIdKeys) {
+ ExternalId extId = remove(rw, noteMap, extIdKey, null);
+ removedExtIds.add(extId);
+ }
+ });
+ cacheUpdates.add(cu -> cu.remove(removedExtIds));
+ }
+
+ /**
+ * Replaces external IDs for an account by external ID keys.
+ *
+ * <p>Deletion of external IDs is done before adding the new external IDs. This means if an
+ * external ID key is specified for deletion and an external ID with the same key is specified to
+ * be added, the old external ID with that key is deleted first and then the new external ID is
+ * added (so the external ID for that key is replaced).
+ *
+ * @throws IllegalStateException is thrown if any of the specified external IDs does not belong to
+ * the specified account.
+ */
+ public void replace(
+ Account.Id accountId, Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
+ throws IOException, DuplicateExternalIdKeyException {
+ checkLoaded();
+ checkSameAccount(toAdd, accountId);
+ checkExternalIdKeysDontExist(ExternalId.Key.from(toAdd), toDelete);
+
+ Set<ExternalId> removedExtIds = new HashSet<>();
+ Set<ExternalId> updatedExtIds = new HashSet<>();
+ noteMapUpdates.add(
+ (rw, n) -> {
+ for (ExternalId.Key extIdKey : toDelete) {
+ ExternalId removedExtId = remove(rw, noteMap, extIdKey, accountId);
+ if (removedExtId != null) {
+ removedExtIds.add(removedExtId);
+ }
+ }
+
+ for (ExternalId extId : toAdd) {
+ ExternalId insertedExtId = upsert(rw, inserter, noteMap, extId);
+ updatedExtIds.add(insertedExtId);
+ }
+ });
+ cacheUpdates.add(cu -> cu.add(updatedExtIds).remove(removedExtIds));
+ }
+
+ /**
+ * Replaces external IDs for an account by external ID keys.
+ *
+ * <p>Deletion of external IDs is done before adding the new external IDs. This means if an
+ * external ID key is specified for deletion and an external ID with the same key is specified to
+ * be added, the old external ID with that key is deleted first and then the new external ID is
+ * added (so the external ID for that key is replaced).
+ *
+ * <p>The external IDs are replaced regardless of which account they belong to.
+ */
+ public void replaceByKeys(Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
+ throws IOException, DuplicateExternalIdKeyException {
+ checkLoaded();
+ checkExternalIdKeysDontExist(ExternalId.Key.from(toAdd), toDelete);
+
+ Set<ExternalId> removedExtIds = new HashSet<>();
+ Set<ExternalId> updatedExtIds = new HashSet<>();
+ noteMapUpdates.add(
+ (rw, n) -> {
+ for (ExternalId.Key extIdKey : toDelete) {
+ ExternalId removedExtId = remove(rw, noteMap, extIdKey, null);
+ removedExtIds.add(removedExtId);
+ }
+
+ for (ExternalId extId : toAdd) {
+ ExternalId insertedExtId = upsert(rw, inserter, noteMap, extId);
+ updatedExtIds.add(insertedExtId);
+ }
+ });
+ cacheUpdates.add(cu -> cu.add(updatedExtIds).remove(removedExtIds));
+ }
+
+ /**
+ * Replaces an external ID.
+ *
+ * @throws IllegalStateException is thrown if the specified external IDs belong to different
+ * accounts.
+ */
+ public void replace(ExternalId toDelete, ExternalId toAdd)
+ throws IOException, DuplicateExternalIdKeyException {
+ replace(Collections.singleton(toDelete), Collections.singleton(toAdd));
+ }
+
+ /**
+ * Replaces external IDs.
+ *
+ * <p>Deletion of external IDs is done before adding the new external IDs. This means if an
+ * external ID is specified for deletion and an external ID with the same key is specified to be
+ * added, the old external ID with that key is deleted first and then the new external ID is added
+ * (so the external ID for that key is replaced).
+ *
+ * @throws IllegalStateException is thrown if the specified external IDs belong to different
+ * accounts.
+ */
+ public void replace(Collection<ExternalId> toDelete, Collection<ExternalId> toAdd)
+ throws IOException, DuplicateExternalIdKeyException {
+ Account.Id accountId = checkSameAccount(Iterables.concat(toDelete, toAdd));
+ if (accountId == null) {
+ // toDelete and toAdd are empty -> nothing to do
+ return;
+ }
+
+ replace(accountId, toDelete.stream().map(e -> e.key()).collect(toSet()), toAdd);
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ noteMap = revision != null ? NoteMap.read(reader, revision) : NoteMap.newEmptyMap();
+
+ if (afterReadRevision != null) {
+ afterReadRevision.run();
+ }
+ }
+
+ @Override
+ public RevCommit commit(MetaDataUpdate update) throws IOException {
+ oldRev = revision != null ? revision.copy() : ObjectId.zeroId();
+ return super.commit(update);
+ }
+
+ /**
+ * Updates the caches (external ID cache, account cache) and reindexes the accounts for which
+ * external IDs were modified.
+ *
+ * <p>Must only be called after committing changes.
+ *
+ * <p>No-op if this instance was created by {@link #loadNoCacheUpdate(Repository)}.
+ *
+ * <p>No eviction from account cache if this instance was created by {@link FactoryNoReindex}.
+ */
+ public void updateCaches() throws IOException {
+ checkState(oldRev != null, "no changes committed yet");
+
+ ExternalIdCacheUpdates externalIdCacheUpdates = new ExternalIdCacheUpdates();
+ for (CacheUpdate cacheUpdate : cacheUpdates) {
+ cacheUpdate.execute(externalIdCacheUpdates);
+ }
+
+ externalIdCache.onReplace(
+ oldRev,
+ getRevision(),
+ externalIdCacheUpdates.getRemoved(),
+ externalIdCacheUpdates.getAdded());
+
+ if (accountCache != null) {
+ for (Account.Id id :
+ Streams.concat(
+ externalIdCacheUpdates.getAdded().stream(),
+ externalIdCacheUpdates.getRemoved().stream())
+ .map(ExternalId::accountId)
+ .collect(toSet())) {
+ accountCache.evict(id);
+ }
+ }
+
+ cacheUpdates.clear();
+ oldRev = null;
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
+ checkState(!readOnly, "Updating external IDs is disabled");
+
+ if (noteMapUpdates.isEmpty()) {
+ return false;
+ }
+
+ if (Strings.isNullOrEmpty(commit.getMessage())) {
+ commit.setMessage("Update external IDs\n");
+ }
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ for (NoteMapUpdate noteMapUpdate : noteMapUpdates) {
+ try {
+ noteMapUpdate.execute(rw, noteMap);
+ } catch (DuplicateExternalIdKeyException e) {
+ throw new IOException(e);
+ }
+ }
+ noteMapUpdates.clear();
+
+ RevTree oldTree = revision != null ? rw.parseTree(revision) : null;
+ ObjectId newTreeId = noteMap.writeTree(inserter);
+ if (newTreeId.equals(oldTree)) {
+ return false;
+ }
+
+ commit.setTreeId(newTreeId);
+ return true;
+ }
+ }
+
+ /**
+ * Checks that all specified external IDs belong to the same account.
+ *
+ * @return the ID of the account to which all specified external IDs belong.
+ */
+ private static Account.Id checkSameAccount(Iterable<ExternalId> extIds) {
+ return checkSameAccount(extIds, null);
+ }
+
+ /**
+ * Checks that all specified external IDs belong to specified account. If no account is specified
+ * it is checked that all specified external IDs belong to the same account.
+ *
+ * @return the ID of the account to which all specified external IDs belong.
+ */
+ public static Account.Id checkSameAccount(
+ Iterable<ExternalId> extIds, @Nullable Account.Id accountId) {
+ for (ExternalId extId : extIds) {
+ if (accountId == null) {
+ accountId = extId.accountId();
+ continue;
+ }
+ checkState(
+ accountId.equals(extId.accountId()),
+ "external id %s belongs to account %s, expected account %s",
+ extId.key().get(),
+ extId.accountId().get(),
+ accountId.get());
+ }
+ return accountId;
+ }
+
+ /**
+ * Insert or updates an new external ID and sets it in the note map.
+ *
+ * <p>If the external ID already exists it is overwritten.
+ */
+ private static ExternalId upsert(
+ RevWalk rw, ObjectInserter ins, NoteMap noteMap, ExternalId extId)
+ throws IOException, ConfigInvalidException {
+ ObjectId noteId = extId.key().sha1();
+ Config c = new Config();
+ if (noteMap.contains(extId.key().sha1())) {
+ byte[] raw = readNoteData(rw, noteMap.get(noteId));
+ try {
+ c = new BlobBasedConfig(null, raw);
+ } catch (ConfigInvalidException e) {
+ throw new ConfigInvalidException(
+ String.format("Invalid external id config for note %s: %s", noteId, e.getMessage()));
+ }
+ }
+ extId.writeToConfig(c);
+ byte[] raw = c.toText().getBytes(UTF_8);
+ ObjectId noteData = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, noteData);
+ return ExternalId.create(extId, noteData);
+ }
+
+ /**
+ * Removes an external ID from the note map.
+ *
+ * @throws IllegalStateException is thrown if there is an existing external ID that has the same
+ * key, but otherwise doesn't match the specified external ID.
+ */
+ private static ExternalId remove(RevWalk rw, NoteMap noteMap, ExternalId extId)
+ throws IOException, ConfigInvalidException {
+ ObjectId noteId = extId.key().sha1();
+ if (!noteMap.contains(noteId)) {
+ return null;
+ }
+
+ ObjectId noteDataId = noteMap.get(noteId);
+ byte[] raw = readNoteData(rw, noteDataId);
+ ExternalId actualExtId = ExternalId.parse(noteId.name(), raw, noteDataId);
+ checkState(
+ extId.equals(actualExtId),
+ "external id %s should be removed, but it's not matching the actual external id %s",
+ extId.toString(),
+ actualExtId.toString());
+ noteMap.remove(noteId);
+ return actualExtId;
+ }
+
+ /**
+ * Removes an external ID from the note map by external ID key.
+ *
+ * @throws IllegalStateException is thrown if an expected account ID is provided and an external
+ * ID with the specified key exists, but belongs to another account.
+ * @return the external ID that was removed, {@code null} if no external ID with the specified key
+ * exists
+ */
+ private static ExternalId remove(
+ RevWalk rw, NoteMap noteMap, ExternalId.Key extIdKey, Account.Id expectedAccountId)
+ throws IOException, ConfigInvalidException {
+ ObjectId noteId = extIdKey.sha1();
+ if (!noteMap.contains(noteId)) {
+ return null;
+ }
+
+ ObjectId noteDataId = noteMap.get(noteId);
+ byte[] raw = readNoteData(rw, noteDataId);
+ ExternalId extId = ExternalId.parse(noteId.name(), raw, noteDataId);
+ if (expectedAccountId != null) {
+ checkState(
+ expectedAccountId.equals(extId.accountId()),
+ "external id %s should be removed for account %s,"
+ + " but external id belongs to account %s",
+ extIdKey.get(),
+ expectedAccountId.get(),
+ extId.accountId().get());
+ }
+ noteMap.remove(noteId);
+ return extId;
+ }
+
+ private void checkExternalIdsDontExist(Collection<ExternalId> extIds)
+ throws DuplicateExternalIdKeyException, IOException {
+ checkExternalIdKeysDontExist(ExternalId.Key.from(extIds));
+ }
+
+ private void checkExternalIdKeysDontExist(
+ Collection<ExternalId.Key> extIdKeysToAdd, Collection<ExternalId.Key> extIdKeysToDelete)
+ throws DuplicateExternalIdKeyException, IOException {
+ HashSet<ExternalId.Key> newKeys = new HashSet<>(extIdKeysToAdd);
+ newKeys.removeAll(extIdKeysToDelete);
+ checkExternalIdKeysDontExist(newKeys);
+ }
+
+ private void checkExternalIdKeysDontExist(Collection<ExternalId.Key> extIdKeys)
+ throws IOException, DuplicateExternalIdKeyException {
+ for (ExternalId.Key extIdKey : extIdKeys) {
+ if (noteMap.contains(extIdKey.sha1())) {
+ throw new DuplicateExternalIdKeyException(extIdKey);
+ }
+ }
+ }
+
+ private void checkLoaded() {
+ checkState(noteMap != null, "External IDs not loaded yet");
+ }
+
+ @FunctionalInterface
+ private interface NoteMapUpdate {
+ void execute(RevWalk rw, NoteMap noteMap)
+ throws IOException, ConfigInvalidException, DuplicateExternalIdKeyException;
+ }
+
+ @FunctionalInterface
+ private interface CacheUpdate {
+ void execute(ExternalIdCacheUpdates cacheUpdates) throws IOException;
+ }
+
+ private static class ExternalIdCacheUpdates {
+ private final Set<ExternalId> added = new HashSet<>();
+ private final Set<ExternalId> removed = new HashSet<>();
+
+ ExternalIdCacheUpdates add(Collection<ExternalId> extIds) {
+ this.added.addAll(extIds);
+ return this;
+ }
+
+ public Set<ExternalId> getAdded() {
+ return ImmutableSet.copyOf(added);
+ }
+
+ ExternalIdCacheUpdates remove(Collection<ExternalId> extIds) {
+ this.removed.addAll(extIds);
+ return this;
+ }
+
+ public Set<ExternalId> getRemoved() {
+ return ImmutableSet.copyOf(removed);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
index bf78b13..f97757f 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
@@ -14,10 +14,7 @@
package com.google.gerrit.server.account.externalids;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units;
@@ -29,17 +26,13 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Class to read external IDs from NoteDb.
@@ -58,10 +51,6 @@
*/
@Singleton
public class ExternalIdReader {
- private static final Logger log = LoggerFactory.getLogger(ExternalIdReader.class);
-
- public static final int MAX_NOTE_SZ = 1 << 19;
-
public static ObjectId readRevision(Repository repo) throws IOException {
Ref ref = repo.exactRef(RefNames.REFS_EXTERNAL_IDS);
return ref != null ? ref.getObjectId() : ObjectId.zeroId();
@@ -104,11 +93,12 @@
}
/** Reads and returns all external IDs. */
- Set<ExternalId> all() throws IOException {
+ Set<ExternalId> all() throws IOException, ConfigInvalidException {
checkReadEnabled();
- try (Repository repo = repoManager.openRepository(allUsersName)) {
- return all(repo, readRevision(repo));
+ try (Timer0.Context ctx = readAllLatency.start();
+ Repository repo = repoManager.openRepository(allUsersName)) {
+ return ExternalIdNotes.loadReadOnly(repo).all();
}
}
@@ -116,34 +106,12 @@
* Reads and returns all external IDs from the specified revision of the refs/meta/external-ids
* branch.
*/
- Set<ExternalId> all(ObjectId rev) throws IOException {
+ Set<ExternalId> all(ObjectId rev) throws IOException, ConfigInvalidException {
checkReadEnabled();
- try (Repository repo = repoManager.openRepository(allUsersName)) {
- return all(repo, rev);
- }
- }
-
- /** Reads and returns all external IDs. */
- private Set<ExternalId> all(Repository repo, ObjectId rev) throws IOException {
- if (rev.equals(ObjectId.zeroId())) {
- return ImmutableSet.of();
- }
-
try (Timer0.Context ctx = readAllLatency.start();
- RevWalk rw = new RevWalk(repo)) {
- NoteMap noteMap = readNoteMap(rw, rev);
- Set<ExternalId> extIds = new HashSet<>();
- for (Note note : noteMap) {
- byte[] raw =
- rw.getObjectReader().open(note.getData(), OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
- try {
- extIds.add(ExternalId.parse(note.getName(), raw, note.getData()));
- } catch (Exception e) {
- log.error(String.format("Ignoring invalid external ID note %s", note.getName()), e);
- }
- }
- return extIds;
+ Repository repo = repoManager.openRepository(allUsersName)) {
+ return ExternalIdNotes.loadReadOnly(repo, rev).all();
}
}
@@ -152,14 +120,8 @@
ExternalId get(ExternalId.Key key) throws IOException, ConfigInvalidException {
checkReadEnabled();
- try (Repository repo = repoManager.openRepository(allUsersName);
- RevWalk rw = new RevWalk(repo)) {
- ObjectId rev = readRevision(repo);
- if (rev.equals(ObjectId.zeroId())) {
- return null;
- }
-
- return parse(key, rw, rev);
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ return ExternalIdNotes.loadReadOnly(repo).get(key).orElse(null);
}
}
@@ -168,27 +130,9 @@
ExternalId get(ExternalId.Key key, ObjectId rev) throws IOException, ConfigInvalidException {
checkReadEnabled();
- if (rev.equals(ObjectId.zeroId())) {
- return null;
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ return ExternalIdNotes.loadReadOnly(repo, rev).get(key).orElse(null);
}
-
- try (Repository repo = repoManager.openRepository(allUsersName);
- RevWalk rw = new RevWalk(repo)) {
- return parse(key, rw, rev);
- }
- }
-
- private static ExternalId parse(ExternalId.Key key, RevWalk rw, ObjectId rev)
- throws IOException, ConfigInvalidException {
- NoteMap noteMap = readNoteMap(rw, rev);
- ObjectId noteId = key.sha1();
- if (!noteMap.contains(noteId)) {
- return null;
- }
-
- ObjectId noteData = noteMap.get(noteId);
- byte[] raw = rw.getObjectReader().open(noteData, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
- return ExternalId.parse(noteId.name(), raw, noteData);
}
private void checkReadEnabled() throws IOException {
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIds.java b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
index 35eb6d4..5732bce 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
@@ -43,12 +43,12 @@
}
/** Returns all external IDs. */
- public Set<ExternalId> all() throws IOException {
+ public Set<ExternalId> all() throws IOException, ConfigInvalidException {
return externalIdReader.all();
}
/** Returns all external IDs from the specified revision of the refs/meta/external-ids branch. */
- public Set<ExternalId> all(ObjectId rev) throws IOException {
+ public Set<ExternalId> all(ObjectId rev) throws IOException, ConfigInvalidException {
return externalIdReader.all(rev);
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java b/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
deleted file mode 100644
index 8e5582c..0000000
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (C) 2016 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.account.externalids;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.RevWalk;
-
-/**
- * This class allows to do batch updates to external IDs.
- *
- * <p>For NoteDb all updates will result in a single commit to the refs/meta/external-ids branch.
- * This means callers can prepare many updates by invoking {@link #replace(ExternalId, ExternalId)}
- * multiple times and when {@link ExternalIdsBatchUpdate#commit(String)} is invoked a single NoteDb
- * commit is created that contains all the prepared updates.
- */
-public class ExternalIdsBatchUpdate {
- private final GitRepositoryManager repoManager;
- private final GitReferenceUpdated gitRefUpdated;
- private final AllUsersName allUsersName;
- private final PersonIdent serverIdent;
- private final ExternalIdCache externalIdCache;
- private final Set<ExternalId> toAdd = new HashSet<>();
- private final Set<ExternalId> toDelete = new HashSet<>();
-
- @Inject
- public ExternalIdsBatchUpdate(
- GitRepositoryManager repoManager,
- GitReferenceUpdated gitRefUpdated,
- AllUsersName allUsersName,
- @GerritPersonIdent PersonIdent serverIdent,
- ExternalIdCache externalIdCache) {
- this.repoManager = repoManager;
- this.gitRefUpdated = gitRefUpdated;
- this.allUsersName = allUsersName;
- this.serverIdent = serverIdent;
- this.externalIdCache = externalIdCache;
- }
-
- /**
- * Adds an external ID replacement to the batch.
- *
- * <p>The actual replacement is only done when {@link #commit(String)} is invoked.
- */
- public void replace(ExternalId extIdToDelete, ExternalId extIdToAdd) {
- ExternalIdsUpdate.checkSameAccount(ImmutableSet.of(extIdToDelete, extIdToAdd));
- toAdd.add(extIdToAdd);
- toDelete.add(extIdToDelete);
- }
-
- /**
- * Commits this batch.
- *
- * <p>This means external ID replacements which were prepared by invoking {@link
- * #replace(ExternalId, ExternalId)} are now executed. Deletion of external IDs is done before
- * adding the new external IDs. This means if an external ID is specified for deletion and an
- * external ID with the same key is specified to be added, the old external ID with that key is
- * deleted first and then the new external ID is added (so the external ID for that key is
- * replaced).
- *
- * <p>For NoteDb a single commit is created that contains all the external ID updates.
- */
- public void commit(String commitMessage)
- throws IOException, OrmException, ConfigInvalidException {
- if (toDelete.isEmpty() && toAdd.isEmpty()) {
- return;
- }
-
- try (Repository repo = repoManager.openRepository(allUsersName);
- RevWalk rw = new RevWalk(repo);
- ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId rev = ExternalIdReader.readRevision(repo);
-
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
-
- for (ExternalId extId : toDelete) {
- ExternalIdsUpdate.remove(rw, noteMap, extId);
- }
-
- for (ExternalId extId : toAdd) {
- ExternalIdsUpdate.insert(rw, ins, noteMap, extId);
- }
-
- ObjectId newRev =
- ExternalIdsUpdate.commit(
- allUsersName,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- commitMessage,
- serverIdent,
- serverIdent,
- null,
- gitRefUpdated);
- externalIdCache.onReplace(rev, newRev, toDelete, toAdd);
- }
-
- toAdd.clear();
- toDelete.clear();
- }
-}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
index 5dbde8e..91619a3 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
@@ -16,7 +16,6 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import static java.util.stream.Collectors.joining;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
@@ -58,31 +57,29 @@
this.validator = validator;
}
- public List<ConsistencyProblemInfo> check() throws IOException {
+ public List<ConsistencyProblemInfo> check() throws IOException, ConfigInvalidException {
try (Repository repo = repoManager.openRepository(allUsers)) {
- return check(repo, ExternalIdReader.readRevision(repo));
+ return check(ExternalIdNotes.loadReadOnly(repo));
}
}
- public List<ConsistencyProblemInfo> check(ObjectId rev) throws IOException {
+ public List<ConsistencyProblemInfo> check(ObjectId rev)
+ throws IOException, ConfigInvalidException {
try (Repository repo = repoManager.openRepository(allUsers)) {
- return check(repo, rev);
+ return check(ExternalIdNotes.loadReadOnly(repo, rev));
}
}
- private List<ConsistencyProblemInfo> check(Repository repo, ObjectId commit) throws IOException {
+ private List<ConsistencyProblemInfo> check(ExternalIdNotes extIdNotes) throws IOException {
List<ConsistencyProblemInfo> problems = new ArrayList<>();
ListMultimap<String, ExternalId.Key> emails =
MultimapBuilder.hashKeys().arrayListValues().build();
- try (RevWalk rw = new RevWalk(repo)) {
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, commit);
+ try (RevWalk rw = new RevWalk(extIdNotes.getRepository())) {
+ NoteMap noteMap = extIdNotes.getNoteMap();
for (Note note : noteMap) {
- byte[] raw =
- rw.getObjectReader()
- .open(note.getData(), OBJ_BLOB)
- .getCachedBytes(ExternalIdReader.MAX_NOTE_SZ);
+ byte[] raw = ExternalIdNotes.readNoteData(rw, note.getData());
try {
ExternalId extId = ExternalId.parse(note.getName(), raw, note.getData());
problems.addAll(validateExternalId(extId));
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java b/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
index 2e58699..8a05a6c 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
@@ -15,35 +15,18 @@
package com.google.gerrit.server.account.externalids;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.gerrit.server.account.externalids.ExternalIdReader.MAX_NOTE_SZ;
-import static com.google.gerrit.server.account.externalids.ExternalIdReader.readNoteMap;
-import static com.google.gerrit.server.account.externalids.ExternalIdReader.readRevision;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.stream.Collectors.toSet;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
-import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Streams;
import com.google.common.util.concurrent.Runnables;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LockFailureException;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
@@ -53,20 +36,8 @@
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Stream;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
/**
* Updates externalIds in ReviewDb and NoteDb.
@@ -89,8 +60,6 @@
* cache and thus triggers reindex for them.
*/
public class ExternalIdsUpdate {
- private static final String COMMIT_MSG = "Update external IDs";
-
/**
* Factory to create an ExternalIdsUpdate instance for updating external IDs by the Gerrit server.
*
@@ -100,50 +69,43 @@
@Singleton
public static class Server {
private final GitRepositoryManager repoManager;
+ private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
private final AccountCache accountCache;
private final AllUsersName allUsersName;
private final MetricMaker metricMaker;
private final ExternalIds externalIds;
private final ExternalIdCache externalIdCache;
- private final Provider<PersonIdent> serverIdent;
- private final GitReferenceUpdated gitRefUpdated;
private final RetryHelper retryHelper;
@Inject
public Server(
GitRepositoryManager repoManager,
+ Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory,
AccountCache accountCache,
AllUsersName allUsersName,
MetricMaker metricMaker,
ExternalIds externalIds,
ExternalIdCache externalIdCache,
- @GerritPersonIdent Provider<PersonIdent> serverIdent,
- GitReferenceUpdated gitRefUpdated,
RetryHelper retryHelper) {
this.repoManager = repoManager;
+ this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
this.accountCache = accountCache;
this.allUsersName = allUsersName;
this.metricMaker = metricMaker;
this.externalIds = externalIds;
this.externalIdCache = externalIdCache;
- this.serverIdent = serverIdent;
- this.gitRefUpdated = gitRefUpdated;
this.retryHelper = retryHelper;
}
public ExternalIdsUpdate create() {
- PersonIdent i = serverIdent.get();
return new ExternalIdsUpdate(
repoManager,
+ () -> metaDataUpdateServerFactory.get().create(allUsersName),
accountCache,
allUsersName,
metricMaker,
externalIds,
externalIdCache,
- i,
- i,
- null,
- gitRefUpdated,
retryHelper);
}
}
@@ -160,47 +122,40 @@
@Singleton
public static class ServerNoReindex {
private final GitRepositoryManager repoManager;
+ private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
private final AllUsersName allUsersName;
private final MetricMaker metricMaker;
private final ExternalIds externalIds;
private final ExternalIdCache externalIdCache;
- private final Provider<PersonIdent> serverIdent;
- private final GitReferenceUpdated gitRefUpdated;
private final RetryHelper retryHelper;
@Inject
public ServerNoReindex(
GitRepositoryManager repoManager,
+ Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory,
AllUsersName allUsersName,
MetricMaker metricMaker,
ExternalIds externalIds,
ExternalIdCache externalIdCache,
- @GerritPersonIdent Provider<PersonIdent> serverIdent,
- GitReferenceUpdated gitRefUpdated,
RetryHelper retryHelper) {
this.repoManager = repoManager;
+ this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
this.allUsersName = allUsersName;
this.metricMaker = metricMaker;
this.externalIds = externalIds;
this.externalIdCache = externalIdCache;
- this.serverIdent = serverIdent;
- this.gitRefUpdated = gitRefUpdated;
this.retryHelper = retryHelper;
}
public ExternalIdsUpdate create() {
- PersonIdent i = serverIdent.get();
return new ExternalIdsUpdate(
repoManager,
+ () -> metaDataUpdateServerFactory.get().create(allUsersName),
null,
allUsersName,
metricMaker,
externalIds,
externalIdCache,
- i,
- i,
- null,
- gitRefUpdated,
retryHelper);
}
}
@@ -214,98 +169,74 @@
@Singleton
public static class User {
private final GitRepositoryManager repoManager;
+ private final Provider<MetaDataUpdate.User> metaDataUpdateUserFactory;
private final AccountCache accountCache;
private final AllUsersName allUsersName;
private final MetricMaker metricMaker;
private final ExternalIds externalIds;
private final ExternalIdCache externalIdCache;
- private final Provider<PersonIdent> serverIdent;
- private final Provider<IdentifiedUser> identifiedUser;
- private final GitReferenceUpdated gitRefUpdated;
private final RetryHelper retryHelper;
@Inject
public User(
GitRepositoryManager repoManager,
+ Provider<MetaDataUpdate.User> metaDataUpdateUserFactory,
AccountCache accountCache,
AllUsersName allUsersName,
MetricMaker metricMaker,
ExternalIds externalIds,
ExternalIdCache externalIdCache,
- @GerritPersonIdent Provider<PersonIdent> serverIdent,
- Provider<IdentifiedUser> identifiedUser,
- GitReferenceUpdated gitRefUpdated,
RetryHelper retryHelper) {
this.repoManager = repoManager;
+ this.metaDataUpdateUserFactory = metaDataUpdateUserFactory;
this.accountCache = accountCache;
this.allUsersName = allUsersName;
this.metricMaker = metricMaker;
this.externalIds = externalIds;
this.externalIdCache = externalIdCache;
- this.serverIdent = serverIdent;
- this.identifiedUser = identifiedUser;
- this.gitRefUpdated = gitRefUpdated;
this.retryHelper = retryHelper;
}
public ExternalIdsUpdate create() {
- IdentifiedUser user = identifiedUser.get();
- PersonIdent i = serverIdent.get();
return new ExternalIdsUpdate(
repoManager,
+ () -> metaDataUpdateUserFactory.get().create(allUsersName),
accountCache,
allUsersName,
metricMaker,
externalIds,
externalIdCache,
- createPersonIdent(i, user),
- i,
- user,
- gitRefUpdated,
retryHelper);
}
-
- private PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
- return user.newCommitterIdent(ident.getWhen(), ident.getTimeZone());
- }
}
private final GitRepositoryManager repoManager;
+ private final MetaDataUpdateFactory metaDataUpdateFactory;
@Nullable private final AccountCache accountCache;
private final AllUsersName allUsersName;
private final ExternalIds externalIds;
private final ExternalIdCache externalIdCache;
- private final PersonIdent committerIdent;
- private final PersonIdent authorIdent;
- @Nullable private final IdentifiedUser currentUser;
- private final GitReferenceUpdated gitRefUpdated;
private final RetryHelper retryHelper;
private final Runnable afterReadRevision;
private final Counter0 updateCount;
private ExternalIdsUpdate(
GitRepositoryManager repoManager,
+ MetaDataUpdateFactory metaDataUpdateFactory,
@Nullable AccountCache accountCache,
AllUsersName allUsersName,
MetricMaker metricMaker,
ExternalIds externalIds,
ExternalIdCache externalIdCache,
- PersonIdent committerIdent,
- PersonIdent authorIdent,
- @Nullable IdentifiedUser currentUser,
- GitReferenceUpdated gitRefUpdated,
RetryHelper retryHelper) {
this(
repoManager,
+ metaDataUpdateFactory,
accountCache,
allUsersName,
metricMaker,
externalIds,
externalIdCache,
- committerIdent,
- authorIdent,
- currentUser,
- gitRefUpdated,
retryHelper,
Runnables.doNothing());
}
@@ -313,26 +244,20 @@
@VisibleForTesting
public ExternalIdsUpdate(
GitRepositoryManager repoManager,
+ MetaDataUpdateFactory metaDataUpdateFactory,
@Nullable AccountCache accountCache,
AllUsersName allUsersName,
MetricMaker metricMaker,
ExternalIds externalIds,
ExternalIdCache externalIdCache,
- PersonIdent committerIdent,
- PersonIdent authorIdent,
- @Nullable IdentifiedUser currentUser,
- GitReferenceUpdated gitRefUpdated,
RetryHelper retryHelper,
Runnable afterReadRevision) {
this.repoManager = checkNotNull(repoManager, "repoManager");
+ this.metaDataUpdateFactory = checkNotNull(metaDataUpdateFactory, "metaDataUpdateFactory");
this.accountCache = accountCache;
this.allUsersName = checkNotNull(allUsersName, "allUsersName");
- this.committerIdent = checkNotNull(committerIdent, "committerIdent");
this.externalIds = checkNotNull(externalIds, "externalIds");
this.externalIdCache = checkNotNull(externalIdCache, "externalIdCache");
- this.authorIdent = checkNotNull(authorIdent, "authorIdent");
- this.currentUser = currentUser;
- this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
this.retryHelper = checkNotNull(retryHelper, "retryHelper");
this.afterReadRevision = checkNotNull(afterReadRevision, "afterReadRevision");
this.updateCount =
@@ -358,18 +283,7 @@
*/
public void insert(Collection<ExternalId> extIds)
throws IOException, ConfigInvalidException, OrmException {
- RefsMetaExternalIdsUpdate u =
- updateNoteMap(
- o -> {
- UpdatedExternalIds updatedExtIds = new UpdatedExternalIds();
- for (ExternalId extId : extIds) {
- ExternalId insertedExtId = insert(o.rw(), o.ins(), o.noteMap(), extId);
- updatedExtIds.onUpdate(insertedExtId);
- }
- return updatedExtIds;
- });
- externalIdCache.onCreate(u.oldRev(), u.newRev(), u.updatedExtIds().getUpdated());
- evictAccounts(u);
+ updateNoteMap(n -> n.insert(extIds));
}
/**
@@ -388,18 +302,7 @@
*/
public void upsert(Collection<ExternalId> extIds)
throws IOException, ConfigInvalidException, OrmException {
- RefsMetaExternalIdsUpdate u =
- updateNoteMap(
- o -> {
- UpdatedExternalIds updatedExtIds = new UpdatedExternalIds();
- for (ExternalId extId : extIds) {
- ExternalId updatedExtId = upsert(o.rw(), o.ins(), o.noteMap(), extId);
- updatedExtIds.onUpdate(updatedExtId);
- }
- return updatedExtIds;
- });
- externalIdCache.onUpdate(u.oldRev(), u.newRev(), u.updatedExtIds().getUpdated());
- evictAccounts(u);
+ updateNoteMap(n -> n.upsert(extIds));
}
/**
@@ -421,18 +324,7 @@
*/
public void delete(Collection<ExternalId> extIds)
throws IOException, ConfigInvalidException, OrmException {
- RefsMetaExternalIdsUpdate u =
- updateNoteMap(
- o -> {
- UpdatedExternalIds updatedExtIds = new UpdatedExternalIds();
- for (ExternalId extId : extIds) {
- ExternalId removedExtId = remove(o.rw(), o.noteMap(), extId);
- updatedExtIds.onRemove(removedExtId);
- }
- return updatedExtIds;
- });
- externalIdCache.onRemove(u.oldRev(), u.newRev(), u.updatedExtIds().getRemoved());
- evictAccounts(u);
+ updateNoteMap(n -> n.delete(extIds));
}
/**
@@ -454,18 +346,7 @@
*/
public void delete(Account.Id accountId, Collection<ExternalId.Key> extIdKeys)
throws IOException, ConfigInvalidException, OrmException {
- RefsMetaExternalIdsUpdate u =
- updateNoteMap(
- o -> {
- UpdatedExternalIds updatedExtIds = new UpdatedExternalIds();
- for (ExternalId.Key extIdKey : extIdKeys) {
- ExternalId removedExtId = remove(o.rw(), o.noteMap(), extIdKey, accountId);
- updatedExtIds.onRemove(removedExtId);
- }
- return updatedExtIds;
- });
- externalIdCache.onRemove(u.oldRev(), u.newRev(), u.updatedExtIds().getRemoved());
- evictAccount(accountId);
+ updateNoteMap(n -> n.delete(accountId, extIdKeys));
}
/**
@@ -475,18 +356,7 @@
*/
public void deleteByKeys(Collection<ExternalId.Key> extIdKeys)
throws IOException, ConfigInvalidException, OrmException {
- RefsMetaExternalIdsUpdate u =
- updateNoteMap(
- o -> {
- UpdatedExternalIds updatedExtIds = new UpdatedExternalIds();
- for (ExternalId.Key extIdKey : extIdKeys) {
- ExternalId extId = remove(o.rw(), o.noteMap(), extIdKey, null);
- updatedExtIds.onRemove(extId);
- }
- return updatedExtIds;
- });
- externalIdCache.onRemove(u.oldRev(), u.newRev(), u.updatedExtIds().getRemoved());
- evictAccounts(u);
+ updateNoteMap(n -> n.deleteByKeys(extIdKeys));
}
/** Deletes all external IDs of the specified account. */
@@ -509,30 +379,7 @@
public void replace(
Account.Id accountId, Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
throws IOException, ConfigInvalidException, OrmException {
- checkSameAccount(toAdd, accountId);
-
- RefsMetaExternalIdsUpdate u =
- updateNoteMap(
- o -> {
- UpdatedExternalIds updatedExtIds = new UpdatedExternalIds();
- for (ExternalId.Key extIdKey : toDelete) {
- ExternalId removedExtId = remove(o.rw(), o.noteMap(), extIdKey, accountId);
- updatedExtIds.onRemove(removedExtId);
- }
-
- for (ExternalId extId : toAdd) {
- ExternalId insertedExtId = insert(o.rw(), o.ins(), o.noteMap(), extId);
- updatedExtIds.onUpdate(insertedExtId);
- }
- return updatedExtIds;
- });
- externalIdCache.onReplace(
- u.oldRev(),
- u.newRev(),
- accountId,
- u.updatedExtIds().getRemoved(),
- u.updatedExtIds().getUpdated());
- evictAccount(accountId);
+ updateNoteMap(n -> n.replace(accountId, toDelete, toAdd));
}
/**
@@ -547,24 +394,7 @@
*/
public void replaceByKeys(Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
throws IOException, ConfigInvalidException, OrmException {
- RefsMetaExternalIdsUpdate u =
- updateNoteMap(
- o -> {
- UpdatedExternalIds updatedExtIds = new UpdatedExternalIds();
- for (ExternalId.Key extIdKey : toDelete) {
- ExternalId removedExtId = remove(o.rw(), o.noteMap(), extIdKey, null);
- updatedExtIds.onRemove(removedExtId);
- }
-
- for (ExternalId extId : toAdd) {
- ExternalId insertedExtId = insert(o.rw(), o.ins(), o.noteMap(), extId);
- updatedExtIds.onUpdate(insertedExtId);
- }
- return updatedExtIds;
- });
- externalIdCache.onReplace(
- u.oldRev(), u.newRev(), u.updatedExtIds().getRemoved(), u.updatedExtIds().getUpdated());
- evictAccounts(u);
+ updateNoteMap(n -> n.replaceByKeys(toDelete, toAdd));
}
/**
@@ -591,334 +421,38 @@
*/
public void replace(Collection<ExternalId> toDelete, Collection<ExternalId> toAdd)
throws IOException, ConfigInvalidException, OrmException {
- Account.Id accountId = checkSameAccount(Iterables.concat(toDelete, toAdd));
- if (accountId == null) {
- // toDelete and toAdd are empty -> nothing to do
- return;
- }
-
- replace(accountId, toDelete.stream().map(e -> e.key()).collect(toSet()), toAdd);
+ updateNoteMap(n -> n.replace(toDelete, toAdd));
}
- /**
- * Checks that all specified external IDs belong to the same account.
- *
- * @return the ID of the account to which all specified external IDs belong.
- */
- public static Account.Id checkSameAccount(Iterable<ExternalId> extIds) {
- return checkSameAccount(extIds, null);
- }
-
- /**
- * Checks that all specified external IDs belong to specified account. If no account is specified
- * it is checked that all specified external IDs belong to the same account.
- *
- * @return the ID of the account to which all specified external IDs belong.
- */
- public static Account.Id checkSameAccount(
- Iterable<ExternalId> extIds, @Nullable Account.Id accountId) {
- for (ExternalId extId : extIds) {
- if (accountId == null) {
- accountId = extId.accountId();
- continue;
- }
- checkState(
- accountId.equals(extId.accountId()),
- "external id %s belongs to account %s, expected account %s",
- extId.key().get(),
- extId.accountId().get(),
- accountId.get());
- }
- return accountId;
- }
-
- /**
- * Inserts a new external ID and sets it in the note map.
- *
- * <p>If the external ID already exists, the insert fails with {@link OrmDuplicateKeyException}.
- */
- public static ExternalId insert(RevWalk rw, ObjectInserter ins, NoteMap noteMap, ExternalId extId)
- throws OrmDuplicateKeyException, ConfigInvalidException, IOException {
- if (noteMap.contains(extId.key().sha1())) {
- throw new OrmDuplicateKeyException(
- String.format("external id %s already exists", extId.key().get()));
- }
- return upsert(rw, ins, noteMap, extId);
- }
-
- /**
- * Insert or updates an new external ID and sets it in the note map.
- *
- * <p>If the external ID already exists it is overwritten.
- */
- public static ExternalId upsert(RevWalk rw, ObjectInserter ins, NoteMap noteMap, ExternalId extId)
- throws IOException, ConfigInvalidException {
- ObjectId noteId = extId.key().sha1();
- Config c = new Config();
- if (noteMap.contains(extId.key().sha1())) {
- byte[] raw =
- rw.getObjectReader().open(noteMap.get(noteId), OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
- try {
- c.fromText(new String(raw, UTF_8));
- } catch (ConfigInvalidException e) {
- throw new ConfigInvalidException(
- String.format("Invalid external id config for note %s: %s", noteId, e.getMessage()));
- }
- }
- extId.writeToConfig(c);
- byte[] raw = c.toText().getBytes(UTF_8);
- ObjectId noteData = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, noteData);
- return ExternalId.create(extId, noteData);
- }
-
- /**
- * Removes an external ID from the note map.
- *
- * @throws IllegalStateException is thrown if there is an existing external ID that has the same
- * key, but otherwise doesn't match the specified external ID.
- */
- public static ExternalId remove(RevWalk rw, NoteMap noteMap, ExternalId extId)
- throws IOException, ConfigInvalidException {
- ObjectId noteId = extId.key().sha1();
- if (!noteMap.contains(noteId)) {
- return null;
- }
-
- ObjectId noteData = noteMap.get(noteId);
- byte[] raw = rw.getObjectReader().open(noteData, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
- ExternalId actualExtId = ExternalId.parse(noteId.name(), raw, noteData);
- checkState(
- extId.equals(actualExtId),
- "external id %s should be removed, but it's not matching the actual external id %s",
- extId.toString(),
- actualExtId.toString());
- noteMap.remove(noteId);
- return actualExtId;
- }
-
- /**
- * Removes an external ID from the note map by external ID key.
- *
- * @throws IllegalStateException is thrown if an expected account ID is provided and an external
- * ID with the specified key exists, but belongs to another account.
- * @return the external ID that was removed, {@code null} if no external ID with the specified key
- * exists
- */
- private static ExternalId remove(
- RevWalk rw, NoteMap noteMap, ExternalId.Key extIdKey, Account.Id expectedAccountId)
- throws IOException, ConfigInvalidException {
- ObjectId noteId = extIdKey.sha1();
- if (!noteMap.contains(noteId)) {
- return null;
- }
-
- ObjectId noteData = noteMap.get(noteId);
- byte[] raw = rw.getObjectReader().open(noteData, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
- ExternalId extId = ExternalId.parse(noteId.name(), raw, noteData);
- if (expectedAccountId != null) {
- checkState(
- expectedAccountId.equals(extId.accountId()),
- "external id %s should be removed for account %s,"
- + " but external id belongs to account %s",
- extIdKey.get(),
- expectedAccountId.get(),
- extId.accountId().get());
- }
- noteMap.remove(noteId);
- return extId;
- }
-
- private RefsMetaExternalIdsUpdate updateNoteMap(ExternalIdUpdater updater)
+ private void updateNoteMap(ExternalIdUpdater updater)
throws IOException, ConfigInvalidException, OrmException {
- return retryHelper.execute(
+ retryHelper.execute(
() -> {
- try (Repository repo = repoManager.openRepository(allUsersName);
- ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId rev = readRevision(repo);
-
- afterReadRevision.run();
-
- try (RevWalk rw = new RevWalk(repo)) {
- NoteMap noteMap = readNoteMap(rw, rev);
- UpdatedExternalIds updatedExtIds =
- updater.update(OpenRepo.create(repo, rw, ins, noteMap));
-
- return commit(repo, rw, ins, rev, noteMap, updatedExtIds);
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ ExternalIdNotes extIdNotes =
+ new ExternalIdNotes(externalIdCache, accountCache, repo)
+ .setAfterReadRevision(afterReadRevision)
+ .load();
+ updater.update(extIdNotes);
+ try (MetaDataUpdate metaDataUpdate = metaDataUpdateFactory.create()) {
+ extIdNotes.commit(metaDataUpdate);
}
+ extIdNotes.updateCaches();
+ updateCount.increment();
+ return null;
}
});
}
- private RefsMetaExternalIdsUpdate commit(
- Repository repo,
- RevWalk rw,
- ObjectInserter ins,
- ObjectId rev,
- NoteMap noteMap,
- UpdatedExternalIds updatedExtIds)
- throws IOException {
- ObjectId newRev =
- commit(
- allUsersName,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- COMMIT_MSG,
- committerIdent,
- authorIdent,
- currentUser,
- gitRefUpdated);
- updateCount.increment();
- return RefsMetaExternalIdsUpdate.create(rev, newRev, updatedExtIds);
- }
-
- /** Commits updates to the external IDs. */
- public static ObjectId commit(
- Project.NameKey project,
- Repository repo,
- RevWalk rw,
- ObjectInserter ins,
- ObjectId rev,
- NoteMap noteMap,
- String commitMessage,
- PersonIdent committerIdent,
- PersonIdent authorIdent,
- @Nullable IdentifiedUser user,
- GitReferenceUpdated gitRefUpdated)
- throws IOException {
- CommitBuilder cb = new CommitBuilder();
- cb.setMessage(commitMessage);
- cb.setTreeId(noteMap.writeTree(ins));
- cb.setAuthor(authorIdent);
- cb.setCommitter(committerIdent);
- if (!rev.equals(ObjectId.zeroId())) {
- cb.setParentId(rev);
- } else {
- cb.setParentIds(); // Ref is currently nonexistent, commit has no parents.
- }
- if (cb.getTreeId() == null) {
- if (rev.equals(ObjectId.zeroId())) {
- cb.setTreeId(emptyTree(ins)); // No parent, assume empty tree.
- } else {
- RevCommit p = rw.parseCommit(rev);
- cb.setTreeId(p.getTree()); // Copy tree from parent.
- }
- }
- ObjectId commitId = ins.insert(cb);
- ins.flush();
-
- RefUpdate u = repo.updateRef(RefNames.REFS_EXTERNAL_IDS);
- u.setRefLogIdent(committerIdent);
- u.setRefLogMessage("Update external IDs", false);
- u.setExpectedOldObjectId(rev);
- u.setNewObjectId(commitId);
- RefUpdate.Result res = u.update();
- switch (res) {
- case NEW:
- case FAST_FORWARD:
- case NO_CHANGE:
- case RENAMED:
- case FORCED:
- break;
- case LOCK_FAILURE:
- throw new LockFailureException("Updating external IDs failed with " + res, u);
- case IO_FAILURE:
- case NOT_ATTEMPTED:
- case REJECTED:
- case REJECTED_CURRENT_BRANCH:
- case REJECTED_MISSING_OBJECT:
- case REJECTED_OTHER_REASON:
- default:
- throw new IOException("Updating external IDs failed with " + res);
- }
- gitRefUpdated.fire(project, u, user != null ? user.getAccount() : null);
- return rw.parseCommit(commitId);
- }
-
- private static ObjectId emptyTree(ObjectInserter ins) throws IOException {
- return ins.insert(OBJ_TREE, new byte[] {});
- }
-
- private void evictAccount(Account.Id accountId) throws IOException {
- if (accountCache != null) {
- accountCache.evict(accountId);
- }
- }
-
- private void evictAccounts(RefsMetaExternalIdsUpdate u) throws IOException {
- if (accountCache != null) {
- for (Account.Id id : u.updatedExtIds().all().map(ExternalId::accountId).collect(toSet())) {
- accountCache.evict(id);
- }
- }
- }
-
@FunctionalInterface
private static interface ExternalIdUpdater {
- UpdatedExternalIds update(OpenRepo openRepo)
+ void update(ExternalIdNotes extIdsNotes)
throws IOException, ConfigInvalidException, OrmException;
}
- @AutoValue
- abstract static class OpenRepo {
- static OpenRepo create(Repository repo, RevWalk rw, ObjectInserter ins, NoteMap noteMap) {
- return new AutoValue_ExternalIdsUpdate_OpenRepo(repo, rw, ins, noteMap);
- }
-
- abstract Repository repo();
-
- abstract RevWalk rw();
-
- abstract ObjectInserter ins();
-
- abstract NoteMap noteMap();
- }
-
@VisibleForTesting
- @AutoValue
- public abstract static class RefsMetaExternalIdsUpdate {
- static RefsMetaExternalIdsUpdate create(
- ObjectId oldRev, ObjectId newRev, UpdatedExternalIds updatedExtIds) {
- return new AutoValue_ExternalIdsUpdate_RefsMetaExternalIdsUpdate(
- oldRev, newRev, updatedExtIds);
- }
-
- abstract ObjectId oldRev();
-
- abstract ObjectId newRev();
-
- abstract UpdatedExternalIds updatedExtIds();
- }
-
- public static class UpdatedExternalIds {
- private Set<ExternalId> updated = new HashSet<>();
- private Set<ExternalId> removed = new HashSet<>();
-
- public void onUpdate(ExternalId extId) {
- if (extId != null) {
- updated.add(extId);
- }
- }
-
- public void onRemove(ExternalId extId) {
- if (extId != null) {
- removed.add(extId);
- }
- }
-
- public Set<ExternalId> getUpdated() {
- return ImmutableSet.copyOf(updated);
- }
-
- public Set<ExternalId> getRemoved() {
- return ImmutableSet.copyOf(removed);
- }
-
- public Stream<ExternalId> all() {
- return Streams.concat(removed.stream(), updated.stream());
- }
+ @FunctionalInterface
+ public static interface MetaDataUpdateFactory {
+ MetaDataUpdate create() throws IOException;
}
}
diff --git a/java/com/google/gerrit/server/api/BUILD b/java/com/google/gerrit/server/api/BUILD
index 2741a0a..910ecd3 100644
--- a/java/com/google/gerrit/server/api/BUILD
+++ b/java/com/google/gerrit/server/api/BUILD
@@ -11,6 +11,7 @@
"//java/com/google/gerrit/lifecycle",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/restapi",
"//lib:guava",
"//lib:gwtorm",
"//lib:servlet-api-3_1",
diff --git a/java/com/google/gerrit/server/api/config/ServerImpl.java b/java/com/google/gerrit/server/api/config/ServerImpl.java
index 2148d97..ce87d1c 100644
--- a/java/com/google/gerrit/server/api/config/ServerImpl.java
+++ b/java/com/google/gerrit/server/api/config/ServerImpl.java
@@ -24,13 +24,13 @@
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.common.ServerInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.config.CheckConsistency;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.config.GetDiffPreferences;
-import com.google.gerrit.server.config.GetPreferences;
-import com.google.gerrit.server.config.GetServerInfo;
-import com.google.gerrit.server.config.SetDiffPreferences;
-import com.google.gerrit.server.config.SetPreferences;
+import com.google.gerrit.server.restapi.config.CheckConsistency;
+import com.google.gerrit.server.restapi.config.GetDiffPreferences;
+import com.google.gerrit.server.restapi.config.GetPreferences;
+import com.google.gerrit.server.restapi.config.GetServerInfo;
+import com.google.gerrit.server.restapi.config.SetDiffPreferences;
+import com.google.gerrit.server.restapi.config.SetPreferences;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/change/ChangesCollection.java b/java/com/google/gerrit/server/change/ChangesCollection.java
index a0b6c96..6ce661e 100644
--- a/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -81,8 +81,8 @@
@Override
public ChangeResource parse(TopLevelResource root, IdString id)
- throws ResourceNotFoundException, OrmException, PermissionBackendException {
- List<ChangeNotes> notes = changeFinder.find(id.encoded());
+ throws RestApiException, OrmException, PermissionBackendException {
+ List<ChangeNotes> notes = changeFinder.find(id.encoded(), true);
if (notes.isEmpty()) {
throw new ResourceNotFoundException(id);
} else if (notes.size() != 1) {
diff --git a/java/com/google/gerrit/server/change/DeletePrivate.java b/java/com/google/gerrit/server/change/DeletePrivate.java
index bb3a38a..ba5403a 100644
--- a/java/com/google/gerrit/server/change/DeletePrivate.java
+++ b/java/com/google/gerrit/server/change/DeletePrivate.java
@@ -40,17 +40,20 @@
private final ChangeMessagesUtil cmUtil;
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
+ private final SetPrivateOp.Factory setPrivateOpFactory;
@Inject
DeletePrivate(
Provider<ReviewDb> dbProvider,
RetryHelper retryHelper,
ChangeMessagesUtil cmUtil,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ SetPrivateOp.Factory setPrivateOpFactory) {
super(retryHelper);
this.dbProvider = dbProvider;
this.cmUtil = cmUtil;
this.permissionBackend = permissionBackend;
+ this.setPrivateOpFactory = setPrivateOpFactory;
}
@Override
@@ -65,7 +68,7 @@
throw new ResourceConflictException("change is not private");
}
- SetPrivateOp op = new SetPrivateOp(cmUtil, false, input);
+ SetPrivateOp op = setPrivateOpFactory.create(cmUtil, false, input);
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(), rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
diff --git a/java/com/google/gerrit/server/change/DeletePrivateByPost.java b/java/com/google/gerrit/server/change/DeletePrivateByPost.java
index 2de57eb..a392492 100644
--- a/java/com/google/gerrit/server/change/DeletePrivateByPost.java
+++ b/java/com/google/gerrit/server/change/DeletePrivateByPost.java
@@ -32,8 +32,9 @@
Provider<ReviewDb> dbProvider,
RetryHelper retryHelper,
ChangeMessagesUtil cmUtil,
- PermissionBackend permissionBackend) {
- super(dbProvider, retryHelper, cmUtil, permissionBackend);
+ PermissionBackend permissionBackend,
+ SetPrivateOp.Factory setPrivateOpFactory) {
+ super(dbProvider, retryHelper, cmUtil, permissionBackend, setPrivateOpFactory);
}
@Override
diff --git a/java/com/google/gerrit/server/change/Module.java b/java/com/google/gerrit/server/change/Module.java
index f648d5a..b4f71af 100644
--- a/java/com/google/gerrit/server/change/Module.java
+++ b/java/com/google/gerrit/server/change/Module.java
@@ -177,6 +177,7 @@
factory(ReviewerResource.Factory.class);
factory(SetAssigneeOp.Factory.class);
factory(SetHashtagsOp.Factory.class);
+ factory(SetPrivateOp.Factory.class);
factory(WorkInProgressOp.Factory.class);
}
}
diff --git a/java/com/google/gerrit/server/change/PostPrivate.java b/java/com/google/gerrit/server/change/PostPrivate.java
index ac0b4c2..307d6df 100644
--- a/java/com/google/gerrit/server/change/PostPrivate.java
+++ b/java/com/google/gerrit/server/change/PostPrivate.java
@@ -43,17 +43,20 @@
private final ChangeMessagesUtil cmUtil;
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
+ private final SetPrivateOp.Factory setPrivateOpFactory;
@Inject
PostPrivate(
Provider<ReviewDb> dbProvider,
RetryHelper retryHelper,
ChangeMessagesUtil cmUtil,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ SetPrivateOp.Factory setPrivateOpFactory) {
super(retryHelper);
this.dbProvider = dbProvider;
this.cmUtil = cmUtil;
this.permissionBackend = permissionBackend;
+ this.setPrivateOpFactory = setPrivateOpFactory;
}
@Override
@@ -68,7 +71,7 @@
return Response.ok("");
}
- SetPrivateOp op = new SetPrivateOp(cmUtil, true, input);
+ SetPrivateOp op = setPrivateOpFactory.create(cmUtil, true, input);
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(), rsrc.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
diff --git a/java/com/google/gerrit/server/change/SetPrivateOp.java b/java/com/google/gerrit/server/change/SetPrivateOp.java
index d0bb70b..9aa4636 100644
--- a/java/com/google/gerrit/server/change/SetPrivateOp.java
+++ b/java/com/google/gerrit/server/change/SetPrivateOp.java
@@ -19,10 +19,14 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.extensions.events.PrivateStateChanged;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.update.Context;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
public class SetPrivateOp implements BatchUpdateOp {
public static class Input {
@@ -35,19 +39,32 @@
}
}
+ public interface Factory {
+ SetPrivateOp create(ChangeMessagesUtil cmUtil, boolean isPrivate, Input input);
+ }
+
private final ChangeMessagesUtil cmUtil;
private final boolean isPrivate;
private final Input input;
+ private final PrivateStateChanged privateStateChanged;
- SetPrivateOp(ChangeMessagesUtil cmUtil, boolean isPrivate, Input input) {
+ private Change change;
+
+ @Inject
+ SetPrivateOp(
+ PrivateStateChanged privateStateChanged,
+ @Assisted ChangeMessagesUtil cmUtil,
+ @Assisted boolean isPrivate,
+ @Assisted Input input) {
this.cmUtil = cmUtil;
this.isPrivate = isPrivate;
this.input = input;
+ this.privateStateChanged = privateStateChanged;
}
@Override
public boolean updateChange(ChangeContext ctx) throws ResourceConflictException, OrmException {
- Change change = ctx.getChange();
+ change = ctx.getChange();
ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
change.setPrivate(isPrivate);
change.setLastUpdatedOn(ctx.getWhen());
@@ -56,6 +73,11 @@
return true;
}
+ @Override
+ public void postUpdate(Context ctx) {
+ privateStateChanged.fire(change, ctx.getAccount(), ctx.getWhen());
+ }
+
private void addMessage(ChangeContext ctx, ChangeUpdate update) throws OrmException {
Change c = ctx.getChange();
StringBuilder buf = new StringBuilder(c.isPrivate() ? "Set private" : "Unset private");
diff --git a/java/com/google/gerrit/server/change/WorkInProgressOp.java b/java/com/google/gerrit/server/change/WorkInProgressOp.java
index b5a8220..43de55c3 100644
--- a/java/com/google/gerrit/server/change/WorkInProgressOp.java
+++ b/java/com/google/gerrit/server/change/WorkInProgressOp.java
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -58,6 +59,7 @@
private final boolean workInProgress;
private final Input in;
private final NotifyHandling notify;
+ private final WorkInProgressStateChanged stateChanged;
private Change change;
private ChangeNotes notes;
@@ -69,11 +71,13 @@
ChangeMessagesUtil cmUtil,
EmailReviewComments.Factory email,
PatchSetUtil psUtil,
+ WorkInProgressStateChanged stateChanged,
@Assisted boolean workInProgress,
@Assisted Input in) {
this.cmUtil = cmUtil;
this.email = email;
this.psUtil = psUtil;
+ this.stateChanged = stateChanged;
this.workInProgress = workInProgress;
this.in = in;
notify =
@@ -121,6 +125,7 @@
@Override
public void postUpdate(Context ctx) {
+ stateChanged.fire(change, ctx.getAccount(), ctx.getWhen());
if (workInProgress || notify.ordinal() < NotifyHandling.OWNER_REVIEWERS.ordinal()) {
return;
}
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 224bf29..e0d4827 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -46,6 +46,7 @@
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.events.PluginEventListener;
+import com.google.gerrit.extensions.events.PrivateStateChangedListener;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.gerrit.extensions.events.ProjectIndexedListener;
import com.google.gerrit.extensions.events.ReviewerAddedListener;
@@ -54,6 +55,7 @@
import com.google.gerrit.extensions.events.TopicEditedListener;
import com.google.gerrit.extensions.events.UsageDataPublishedListener;
import com.google.gerrit.extensions.events.VoteDeletedListener;
+import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
@@ -173,6 +175,7 @@
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeQueryProcessor;
import com.google.gerrit.server.query.change.ConflictsCacheImpl;
+import com.google.gerrit.server.restapi.config.ConfigRestModule;
import com.google.gerrit.server.rules.PrologModule;
import com.google.gerrit.server.rules.RulesCache;
import com.google.gerrit.server.ssh.SshAddressesModule;
@@ -305,7 +308,7 @@
install(new com.google.gerrit.server.access.Module());
install(new com.google.gerrit.server.account.Module());
install(new com.google.gerrit.server.change.Module());
- install(new com.google.gerrit.server.config.Module());
+ install(new ConfigRestModule());
install(new com.google.gerrit.server.group.Module(groupsMigration));
install(new com.google.gerrit.server.project.Module());
@@ -325,9 +328,11 @@
DynamicSet.setOf(binder(), ChangeRestoredListener.class);
DynamicSet.setOf(binder(), ChangeRevertedListener.class);
+ DynamicSet.setOf(binder(), PrivateStateChangedListener.class);
DynamicSet.setOf(binder(), ReviewerAddedListener.class);
DynamicSet.setOf(binder(), ReviewerDeletedListener.class);
DynamicSet.setOf(binder(), VoteDeletedListener.class);
+ DynamicSet.setOf(binder(), WorkInProgressStateChangedListener.class);
DynamicSet.setOf(binder(), RevisionCreatedListener.class);
DynamicSet.setOf(binder(), TopicEditedListener.class);
DynamicSet.setOf(binder(), AgreementSignupListener.class);
diff --git a/java/com/google/gerrit/server/data/ChangeAttribute.java b/java/com/google/gerrit/server/data/ChangeAttribute.java
index 0467c92..ec76f50 100644
--- a/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -16,6 +16,7 @@
import com.google.gerrit.extensions.common.PluginDefinedInfo;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gson.annotations.SerializedName;
import java.util.List;
public class ChangeAttribute {
@@ -35,6 +36,10 @@
public Boolean open;
public Change.Status status;
public List<MessageAttribute> comments;
+ public Boolean wip;
+
+ @SerializedName("private")
+ public Boolean isPrivate;
public List<TrackingIdAttribute> trackingIds;
public PatchSetAttribute currentPatchSet;
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 85b91d0..2759de0 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -158,6 +158,8 @@
a.assignee = asAccountAttribute(change.getAssignee());
a.status = change.getStatus();
a.createdOn = change.getCreatedOn().getTime() / 1000L;
+ a.wip = change.isWorkInProgress() ? true : null;
+ a.isPrivate = change.isPrivate() ? true : null;
return a;
}
diff --git a/java/com/google/gerrit/server/events/EventTypes.java b/java/com/google/gerrit/server/events/EventTypes.java
index f261c02..cd2b464 100644
--- a/java/com/google/gerrit/server/events/EventTypes.java
+++ b/java/com/google/gerrit/server/events/EventTypes.java
@@ -30,6 +30,7 @@
register(CommitReceivedEvent.TYPE, CommitReceivedEvent.class);
register(HashtagsChangedEvent.TYPE, HashtagsChangedEvent.class);
register(PatchSetCreatedEvent.TYPE, PatchSetCreatedEvent.class);
+ register(PrivateStateChangedEvent.TYPE, PrivateStateChangedEvent.class);
register(ProjectCreatedEvent.TYPE, ProjectCreatedEvent.class);
register(RefReceivedEvent.TYPE, RefReceivedEvent.class);
register(RefUpdatedEvent.TYPE, RefUpdatedEvent.class);
@@ -37,6 +38,7 @@
register(ReviewerDeletedEvent.TYPE, ReviewerDeletedEvent.class);
register(TopicChangedEvent.TYPE, TopicChangedEvent.class);
register(VoteDeletedEvent.TYPE, VoteDeletedEvent.class);
+ register(WorkInProgressStateChangedEvent.TYPE, WorkInProgressStateChangedEvent.class);
}
/**
diff --git a/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java b/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
new file mode 100644
index 0000000..af42b08
--- /dev/null
+++ b/java/com/google/gerrit/server/events/PrivateStateChangedEvent.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 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.events;
+
+import com.google.common.base.Supplier;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.data.AccountAttribute;
+
+public class PrivateStateChangedEvent extends ChangeEvent {
+ static final String TYPE = "private-state-changed";
+ public Supplier<AccountAttribute> changer;
+
+ protected PrivateStateChangedEvent(Change change) {
+ super(TYPE, change);
+ }
+}
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 96ef7990..a63e1f8 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -31,11 +31,13 @@
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
import com.google.gerrit.extensions.events.HashtagsEditedListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.events.PrivateStateChangedListener;
import com.google.gerrit.extensions.events.ReviewerAddedListener;
import com.google.gerrit.extensions.events.ReviewerDeletedListener;
import com.google.gerrit.extensions.events.RevisionCreatedListener;
import com.google.gerrit.extensions.events.TopicEditedListener;
import com.google.gerrit.extensions.events.VoteDeletedListener;
+import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Account;
@@ -76,6 +78,8 @@
ChangeAbandonedListener,
ChangeMergedListener,
ChangeRestoredListener,
+ WorkInProgressStateChangedListener,
+ PrivateStateChangedListener,
CommentAddedListener,
GitReferenceUpdatedListener,
HashtagsEditedListener,
@@ -99,11 +103,15 @@
.to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), HashtagsEditedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(StreamEventsApiListener.class);
+ DynamicSet.bind(binder(), PrivateStateChangedListener.class)
+ .to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ReviewerAddedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ReviewerDeletedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), RevisionCreatedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), TopicEditedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), VoteDeletedListener.class).to(StreamEventsApiListener.class);
+ DynamicSet.bind(binder(), WorkInProgressStateChangedListener.class)
+ .to(StreamEventsApiListener.class);
}
}
@@ -462,6 +470,36 @@
}
@Override
+ public void onWorkInProgressStateChanged(WorkInProgressStateChangedListener.Event ev) {
+ try {
+ Change change = getChange(ev.getChange());
+ WorkInProgressStateChangedEvent event = new WorkInProgressStateChangedEvent(change);
+
+ event.change = changeAttributeSupplier(change);
+ event.changer = accountAttributeSupplier(ev.getWho());
+
+ dispatcher.get().postEvent(change, event);
+ } catch (OrmException | PermissionBackendException e) {
+ log.error("Failed to dispatch event", e);
+ }
+ }
+
+ @Override
+ public void onPrivateStateChanged(PrivateStateChangedListener.Event ev) {
+ try {
+ Change change = getChange(ev.getChange());
+ PrivateStateChangedEvent event = new PrivateStateChangedEvent(change);
+
+ event.change = changeAttributeSupplier(change);
+ event.changer = accountAttributeSupplier(ev.getWho());
+
+ dispatcher.get().postEvent(change, event);
+ } catch (OrmException | PermissionBackendException e) {
+ log.error("Failed to dispatch event", e);
+ }
+ }
+
+ @Override
public void onVoteDeleted(VoteDeletedListener.Event ev) {
try {
ChangeNotes notes = getNotes(ev.getChange());
diff --git a/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java b/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
new file mode 100644
index 0000000..ad32672
--- /dev/null
+++ b/java/com/google/gerrit/server/events/WorkInProgressStateChangedEvent.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 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.events;
+
+import com.google.common.base.Supplier;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.data.AccountAttribute;
+
+public class WorkInProgressStateChangedEvent extends ChangeEvent {
+ static final String TYPE = "wip-state-changed";
+ public Supplier<AccountAttribute> changer;
+
+ protected WorkInProgressStateChangedEvent(Change change) {
+ super(TYPE, change);
+ }
+}
diff --git a/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java b/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
new file mode 100644
index 0000000..37c9089
--- /dev/null
+++ b/java/com/google/gerrit/server/extensions/events/PrivateStateChanged.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2017 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.extensions.events;
+
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.events.PrivateStateChangedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.sql.Timestamp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PrivateStateChanged {
+ private static final Logger log = LoggerFactory.getLogger(PrivateStateChanged.class);
+
+ private final DynamicSet<PrivateStateChangedListener> listeners;
+ private final EventUtil util;
+
+ @Inject
+ PrivateStateChanged(DynamicSet<PrivateStateChangedListener> listeners, EventUtil util) {
+ this.listeners = listeners;
+ this.util = util;
+ }
+
+ public void fire(Change change, Account account, Timestamp when) {
+
+ if (!listeners.iterator().hasNext()) {
+ return;
+ }
+ try {
+ Event event = new Event(util.changeInfo(change), util.accountInfo(account), when);
+ for (PrivateStateChangedListener l : listeners) {
+ try {
+ l.onPrivateStateChanged(event);
+ } catch (Exception e) {
+ util.logEventListenerError(event, l, e);
+ }
+ }
+ } catch (OrmException e) {
+ log.error("Couldn't fire event", e);
+ }
+ }
+
+ private static class Event extends AbstractChangeEvent
+ implements PrivateStateChangedListener.Event {
+
+ protected Event(ChangeInfo change, AccountInfo who, Timestamp when) {
+ super(change, who, when, NotifyHandling.ALL);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
new file mode 100644
index 0000000..7290b27
--- /dev/null
+++ b/java/com/google/gerrit/server/extensions/events/WorkInProgressStateChanged.java
@@ -0,0 +1,69 @@
+// Copyright (C) 2017 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.extensions.events;
+
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.sql.Timestamp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WorkInProgressStateChanged {
+ private static final Logger log = LoggerFactory.getLogger(WorkInProgressStateChanged.class);
+
+ private final DynamicSet<WorkInProgressStateChangedListener> listeners;
+ private final EventUtil util;
+
+ @Inject
+ WorkInProgressStateChanged(
+ DynamicSet<WorkInProgressStateChangedListener> listeners, EventUtil util) {
+ this.listeners = listeners;
+ this.util = util;
+ }
+
+ public void fire(Change change, Account account, Timestamp when) {
+
+ if (!listeners.iterator().hasNext()) {
+ return;
+ }
+ try {
+ Event event = new Event(util.changeInfo(change), util.accountInfo(account), when);
+ for (WorkInProgressStateChangedListener l : listeners) {
+ try {
+ l.onWorkInProgressStateChanged(event);
+ } catch (Exception e) {
+ util.logEventListenerError(event, l, e);
+ }
+ }
+ } catch (OrmException e) {
+ log.error("Couldn't fire event", e);
+ }
+ }
+
+ private static class Event extends AbstractChangeEvent
+ implements WorkInProgressStateChangedListener.Event {
+
+ protected Event(ChangeInfo change, AccountInfo who, Timestamp when) {
+ super(change, who, when, NotifyHandling.ALL);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index dcbbea8..77c5b8e 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2932,16 +2932,17 @@
accountsUpdate
.create()
.update(
+ "Set Full Name on Receive Commits",
user.getAccountId(),
- a -> {
+ (a, u) -> {
if (Strings.isNullOrEmpty(a.getFullName())) {
- a.setFullName(setFullNameTo);
+ u.setFullName(setFullNameTo);
}
});
if (account != null) {
user.getAccount().setFullName(account.getFullName());
}
- } catch (IOException | ConfigInvalidException e) {
+ } catch (OrmException | IOException | ConfigInvalidException e) {
logWarn("Failed to update full name of caller", e);
}
}
@@ -2950,7 +2951,11 @@
throws OrmException {
Map<Change.Key, ChangeNotes> r = new HashMap<>();
for (ChangeData cd : queryProvider.get().byBranchOpen(branch)) {
- r.put(cd.change().getKey(), cd.notes());
+ try {
+ r.put(cd.change().getKey(), cd.notes());
+ } catch (NoSuchChangeException e) {
+ //Ignore deleted change
+ }
}
return r;
}
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 9a29193..fc280c2 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -718,7 +718,7 @@
throw new CommitValidationException("invalid external IDs", msgs);
}
return msgs;
- } catch (IOException e) {
+ } catch (IOException | ConfigInvalidException e) {
String m = "error validating external IDs";
log.warn(m, e);
throw new CommitValidationException(m, e);
diff --git a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index a7b3441..ef2c9b3 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -147,7 +147,10 @@
throw new OrmException("NoteDb is required to read change " + changeId);
}
boolean readOrWrite = read || args.migration.rawWriteChangesSetting();
- if (!readOrWrite && !autoRebuild) {
+ if (!readOrWrite) {
+ // Don't even open the repo if we neither write to nor read from NoteDb. It's possible that
+ // there is some garbage in the noteDbState field and/or the repo, but at this point NoteDb is
+ // completely off so it's none of our business.
loadDefaults();
return self();
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 7799d2d..66ff57c 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -737,6 +737,9 @@
if (state == null) {
return super.openHandle(repo, id);
} else if (shouldExist) {
+ // TODO(dborowitz): This means we have a state recorded in noteDbState but the ref doesn't
+ // exist for whatever reason. Doesn't this mean we should trigger an auto-rebuild, rather
+ // than throwing?
throw new NoSuchChangeException(getChangeId());
}
}
diff --git a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
index 153c9c3..35e4a12 100644
--- a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
@@ -80,7 +80,9 @@
} else {
pushCert = null;
}
- return noteUtil.parseNote(raw, p, changeId);
+ List<Comment> comments = noteUtil.parseNote(raw, p, changeId);
+ comments.forEach(c -> c.legacyFormat = true);
+ return comments;
}
private static boolean isJson(byte[] raw, int offset) {
diff --git a/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java b/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
index f919efb..e33ece9 100644
--- a/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
+++ b/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
@@ -265,7 +265,7 @@
// the primary storage to NoteDb.
setPrimaryStorageNoteDb(id, rebuiltState);
- log.info("Migrated change {} to NoteDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
+ log.debug("Migrated change {} to NoteDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
}
private Change setReadOnlyInReviewDb(Change.Id id) throws OrmException {
@@ -399,7 +399,7 @@
rebuilder.rebuildReviewDb(db(), project, id);
setPrimaryStorageReviewDb(id, newMetaId);
releaseReadOnlyLeaseInNoteDb(project, id);
- log.info("Migrated change {} to ReviewDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
+ log.debug("Migrated change {} to ReviewDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
}
private ObjectId setReadOnlyInNoteDb(Project.NameKey project, Change.Id id)
diff --git a/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
index d5031dc8..2d1b076 100644
--- a/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
+++ b/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.notedb.rebuild;
import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
@@ -228,9 +229,16 @@
throw new NoSuchChangeException(changeId);
}
- final String oldNoteDbState = change.getNoteDbState();
+ String oldNoteDbStateStr = change.getNoteDbState();
Result r = manager.stageAndApplyDelta(change);
- final String newNoteDbState = change.getNoteDbState();
+ String newNoteDbStateStr = change.getNoteDbState();
+ if (newNoteDbStateStr == null) {
+ throw new OrmException(
+ "Rebuilding change %s produced no writes to NoteDb: "
+ + bundleReader.fromReviewDb(db, changeId));
+ }
+ NoteDbChangeState newNoteDbState =
+ checkNotNull(NoteDbChangeState.parse(changeId, newNoteDbStateStr));
try {
db.changes()
.atomicUpdate(
@@ -241,15 +249,15 @@
if (checkReadOnly) {
NoteDbChangeState.checkNotReadOnly(change, skewMs);
}
- String currNoteDbState = change.getNoteDbState();
- if (Objects.equals(currNoteDbState, newNoteDbState)) {
+ String currNoteDbStateStr = change.getNoteDbState();
+ if (Objects.equals(currNoteDbStateStr, newNoteDbStateStr)) {
// Another thread completed the same rebuild we were about to.
throw new AbortUpdateException();
- } else if (!Objects.equals(oldNoteDbState, currNoteDbState)) {
+ } else if (!Objects.equals(oldNoteDbStateStr, currNoteDbStateStr)) {
// Another thread updated the state to something else.
- throw new ConflictingUpdateRuntimeException(change, oldNoteDbState);
+ throw new ConflictingUpdateRuntimeException(change, oldNoteDbStateStr);
}
- change.setNoteDbState(newNoteDbState);
+ change.setNoteDbState(newNoteDbStateStr);
return change;
}
});
@@ -259,10 +267,9 @@
// rebuild had executed before the other thread.
throw new ConflictingUpdateException(e);
} catch (AbortUpdateException e) {
- if (NoteDbChangeState.parse(changeId, newNoteDbState)
- .isUpToDate(
- manager.getChangeRepo().cmds.getRepoRefCache(),
- manager.getAllUsersRepo().cmds.getRepoRefCache())) {
+ if (newNoteDbState.isUpToDate(
+ manager.getChangeRepo().cmds.getRepoRefCache(),
+ manager.getAllUsersRepo().cmds.getRepoRefCache())) {
// If the state in ReviewDb matches NoteDb at this point, it means another thread
// successfully completed this rebuild. It's ok to not execute the update in this case,
// since the object referenced in the Result was flushed to the repo by whatever thread won
diff --git a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
index 2981174..c8d69b9 100644
--- a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
@@ -91,6 +91,7 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.internal.storage.file.PackInserter;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
@@ -748,9 +749,12 @@
}
private static ObjectInserter newPackInserter(Repository repo) {
- return repo instanceof FileRepository
- ? ((FileRepository) repo).getObjectDatabase().newPackInserter()
- : repo.newObjectInserter();
+ if (!(repo instanceof FileRepository)) {
+ return repo.newObjectInserter();
+ }
+ PackInserter ins = ((FileRepository) repo).getObjectDatabase().newPackInserter();
+ ins.checkExisting(false);
+ return ins;
}
private boolean rebuildProject(
diff --git a/java/com/google/gerrit/server/restapi/BUILD b/java/com/google/gerrit/server/restapi/BUILD
new file mode 100644
index 0000000..e0262bb
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/BUILD
@@ -0,0 +1,22 @@
+package(
+ default_visibility = ["//visibility:public"],
+)
+
+java_library(
+ name = "restapi",
+ srcs = glob(["**/*.java"]),
+ deps = [
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/server",
+ "//java/org/eclipse/jgit:server",
+ "//lib:args4j",
+ "//lib:guava",
+ "//lib:gwtorm",
+ "//lib:servlet-api-3_1",
+ "//lib/guice",
+ "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib/log:api",
+ ],
+)
diff --git a/java/com/google/gerrit/server/config/CachesCollection.java b/java/com/google/gerrit/server/restapi/config/CachesCollection.java
similarity index 95%
rename from java/com/google/gerrit/server/config/CachesCollection.java
rename to java/com/google/gerrit/server/restapi/config/CachesCollection.java
index 7ecfa63..cfdc648 100644
--- a/java/com/google/gerrit/server/config/CachesCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/CachesCollection.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
@@ -28,6 +28,8 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.CacheResource;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/config/CapabilitiesCollection.java b/java/com/google/gerrit/server/restapi/config/CapabilitiesCollection.java
similarity index 91%
rename from java/com/google/gerrit/server/config/CapabilitiesCollection.java
rename to java/com/google/gerrit/server/restapi/config/CapabilitiesCollection.java
index 1124048..ae1278d 100644
--- a/java/com/google/gerrit/server/config/CapabilitiesCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/CapabilitiesCollection.java
@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.config.CapabilityResource;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/config/CheckConsistency.java b/java/com/google/gerrit/server/restapi/config/CheckConsistency.java
similarity index 97%
rename from java/com/google/gerrit/server/config/CheckConsistency.java
rename to java/com/google/gerrit/server/restapi/config/CheckConsistency.java
index 38813de..95b20c2 100644
--- a/java/com/google/gerrit/server/config/CheckConsistency.java
+++ b/java/com/google/gerrit/server/restapi/config/CheckConsistency.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.CheckAccountExternalIdsResultInfo;
@@ -25,6 +25,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountsConsistencyChecker;
import com.google.gerrit.server.account.externalids.ExternalIdsConsistencyChecker;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.group.db.GroupsConsistencyChecker;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
diff --git a/java/com/google/gerrit/server/config/ConfigCollection.java b/java/com/google/gerrit/server/restapi/config/ConfigCollection.java
similarity index 94%
rename from java/com/google/gerrit/server/config/ConfigCollection.java
rename to java/com/google/gerrit/server/restapi/config/ConfigCollection.java
index f268110..934dbc1 100644
--- a/java/com/google/gerrit/server/config/ConfigCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/ConfigCollection.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.IdString;
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/config/Module.java b/java/com/google/gerrit/server/restapi/config/ConfigRestModule.java
similarity index 83%
rename from java/com/google/gerrit/server/config/Module.java
rename to java/com/google/gerrit/server/restapi/config/ConfigRestModule.java
index 7bf5ad5..71b2f9c 100644
--- a/java/com/google/gerrit/server/config/Module.java
+++ b/java/com/google/gerrit/server/restapi/config/ConfigRestModule.java
@@ -12,23 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
-import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND;
import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
import static com.google.gerrit.server.config.TaskResource.TASK_KIND;
-import static com.google.gerrit.server.config.TopMenuResource.TOP_MENU_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.config.CapabilityResource;
+import com.google.gerrit.server.config.TopMenuResource;
-public class Module extends RestApiModule {
+public class ConfigRestModule extends RestApiModule {
@Override
protected void configure() {
- DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+ DynamicMap.mapOf(binder(), CapabilityResource.CAPABILITY_KIND);
DynamicMap.mapOf(binder(), CONFIG_KIND);
DynamicMap.mapOf(binder(), TASK_KIND);
- DynamicMap.mapOf(binder(), TOP_MENU_KIND);
+ DynamicMap.mapOf(binder(), TopMenuResource.TOP_MENU_KIND);
child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
child(CONFIG_KIND, "tasks").to(TasksCollection.class);
get(TASK_KIND).to(GetTask.class);
diff --git a/java/com/google/gerrit/server/config/ConfirmEmail.java b/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
similarity index 94%
rename from java/com/google/gerrit/server/config/ConfirmEmail.java
rename to java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
index 1044bbb..f6ceb68b 100644
--- a/java/com/google/gerrit/server/config/ConfirmEmail.java
+++ b/java/com/google/gerrit/server/restapi/config/ConfirmEmail.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.DefaultInput;
@@ -23,8 +23,9 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
-import com.google.gerrit.server.config.ConfirmEmail.Input;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.mail.EmailTokenVerifier;
+import com.google.gerrit.server.restapi.config.ConfirmEmail.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/config/DeleteTask.java b/java/com/google/gerrit/server/restapi/config/DeleteTask.java
similarity index 93%
rename from java/com/google/gerrit/server/config/DeleteTask.java
rename to java/com/google/gerrit/server/restapi/config/DeleteTask.java
index d20589a..a08b036 100644
--- a/java/com/google/gerrit/server/config/DeleteTask.java
+++ b/java/com/google/gerrit/server/restapi/config/DeleteTask.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.common.data.GlobalCapability.KILL_TASK;
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
@@ -22,6 +22,7 @@
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.config.TaskResource;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/config/FlushCache.java b/java/com/google/gerrit/server/restapi/config/FlushCache.java
similarity index 95%
rename from java/com/google/gerrit/server/config/FlushCache.java
rename to java/com/google/gerrit/server/restapi/config/FlushCache.java
index 5d2ec36..55e9dc3 100644
--- a/java/com/google/gerrit/server/config/FlushCache.java
+++ b/java/com/google/gerrit/server/restapi/config/FlushCache.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
@@ -23,6 +23,7 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.CacheResource;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/config/GetCache.java b/java/com/google/gerrit/server/restapi/config/GetCache.java
similarity index 77%
rename from java/com/google/gerrit/server/config/GetCache.java
rename to java/com/google/gerrit/server/restapi/config/GetCache.java
index 53628cc..5abaf1e 100644
--- a/java/com/google/gerrit/server/config/GetCache.java
+++ b/java/com/google/gerrit/server/restapi/config/GetCache.java
@@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.config.ListCaches.CacheInfo;
+import com.google.gerrit.server.config.CacheResource;
import com.google.inject.Singleton;
@Singleton
public class GetCache implements RestReadView<CacheResource> {
@Override
- public CacheInfo apply(CacheResource rsrc) {
- return new CacheInfo(rsrc.getName(), rsrc.getCache());
+ public ListCaches.CacheInfo apply(CacheResource rsrc) {
+ return new ListCaches.CacheInfo(rsrc.getName(), rsrc.getCache());
}
}
diff --git a/java/com/google/gerrit/server/config/GetDiffPreferences.java b/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
similarity index 94%
rename from java/com/google/gerrit/server/config/GetDiffPreferences.java
rename to java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
index 8393fb4..6e72503 100644
--- a/java/com/google/gerrit/server/config/GetDiffPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetDiffPreferences.java
@@ -11,7 +11,7 @@
// 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.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.server.config.ConfigUtil.loadSection;
@@ -20,6 +20,8 @@
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.UserConfigSections;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/config/GetPreferences.java b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
similarity index 94%
rename from java/com/google/gerrit/server/config/GetPreferences.java
rename to java/com/google/gerrit/server/restapi/config/GetPreferences.java
index ed212f4..c8a173f 100644
--- a/java/com/google/gerrit/server/config/GetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.server.config.ConfigUtil.loadSection;
@@ -20,6 +20,8 @@
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.account.GeneralPreferencesLoader;
import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.UserConfigSections;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
similarity index 95%
rename from java/com/google/gerrit/server/config/GetServerInfo.java
rename to java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 90e2838..c353c5b 100644
--- a/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static java.util.stream.Collectors.toList;
@@ -48,6 +48,16 @@
import com.google.gerrit.server.change.AllowedFormats;
import com.google.gerrit.server.change.ArchiveFormat;
import com.google.gerrit.server.change.Submit;
+import com.google.gerrit.server.config.AgreementJson;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritOptions;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.documentation.QueryDocumentationExecutor;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -229,7 +239,6 @@
toBoolean(
cfg.getBoolean("change", "showAssigneeInChangesTable", false) && hasAssigneeInIndex);
info.largeChange = cfg.getInt("change", "largeChange", 500);
- info.privateByDefault = toBoolean(cfg.getBoolean("change", "privateByDefault", false));
info.replyTooltip =
Optional.ofNullable(cfg.getString("change", null, "replyTooltip")).orElse("Reply and score")
+ " (Shortcut: a)";
diff --git a/java/com/google/gerrit/server/config/GetSummary.java b/java/com/google/gerrit/server/restapi/config/GetSummary.java
similarity index 97%
rename from java/com/google/gerrit/server/config/GetSummary.java
rename to java/com/google/gerrit/server/restapi/config/GetSummary.java
index 82912c0..26f069c 100644
--- a/java/com/google/gerrit/server/config/GetSummary.java
+++ b/java/com/google/gerrit/server/restapi/config/GetSummary.java
@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/config/GetTask.java b/java/com/google/gerrit/server/restapi/config/GetTask.java
similarity index 79%
rename from java/com/google/gerrit/server/config/GetTask.java
rename to java/com/google/gerrit/server/restapi/config/GetTask.java
index e4b3320..a32f3ba 100644
--- a/java/com/google/gerrit/server/config/GetTask.java
+++ b/java/com/google/gerrit/server/restapi/config/GetTask.java
@@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.config.ListTasks.TaskInfo;
+import com.google.gerrit.server.config.TaskResource;
import com.google.inject.Singleton;
@Singleton
public class GetTask implements RestReadView<TaskResource> {
@Override
- public TaskInfo apply(TaskResource rsrc) {
- return new TaskInfo(rsrc.getTask());
+ public ListTasks.TaskInfo apply(TaskResource rsrc) {
+ return new ListTasks.TaskInfo(rsrc.getTask());
}
}
diff --git a/java/com/google/gerrit/server/config/GetVersion.java b/java/com/google/gerrit/server/restapi/config/GetVersion.java
similarity index 91%
rename from java/com/google/gerrit/server/config/GetVersion.java
rename to java/com/google/gerrit/server/restapi/config/GetVersion.java
index c71cb69..8135719 100644
--- a/java/com/google/gerrit/server/config/GetVersion.java
+++ b/java/com/google/gerrit/server/restapi/config/GetVersion.java
@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.common.Version;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Singleton;
@Singleton
diff --git a/java/com/google/gerrit/server/config/ListCaches.java b/java/com/google/gerrit/server/restapi/config/ListCaches.java
similarity index 97%
rename from java/com/google/gerrit/server/config/ListCaches.java
rename to java/com/google/gerrit/server/restapi/config/ListCaches.java
index d78f61d..c0a9d71 100644
--- a/java/com/google/gerrit/server/config/ListCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/ListCaches.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
@@ -27,6 +27,7 @@
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.cache.PersistentCache;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/java/com/google/gerrit/server/config/ListCapabilities.java b/java/com/google/gerrit/server/restapi/config/ListCapabilities.java
similarity index 94%
rename from java/com/google/gerrit/server/config/ListCapabilities.java
rename to java/com/google/gerrit/server/restapi/config/ListCapabilities.java
index b8d1888..6a1e5f6 100644
--- a/java/com/google/gerrit/server/config/ListCapabilities.java
+++ b/java/com/google/gerrit/server/restapi/config/ListCapabilities.java
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.CapabilityConstants;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/config/ListTasks.java b/java/com/google/gerrit/server/restapi/config/ListTasks.java
similarity index 97%
rename from java/com/google/gerrit/server/config/ListTasks.java
rename to java/com/google/gerrit/server/restapi/config/ListTasks.java
index bbda9eb..71ee5ad 100644
--- a/java/com/google/gerrit/server/config/ListTasks.java
+++ b/java/com/google/gerrit/server/restapi/config/ListTasks.java
@@ -12,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.common.collect.ComparisonChain;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
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;
diff --git a/java/com/google/gerrit/server/config/ListTopMenus.java b/java/com/google/gerrit/server/restapi/config/ListTopMenus.java
similarity index 92%
rename from java/com/google/gerrit/server/config/ListTopMenus.java
rename to java/com/google/gerrit/server/restapi/config/ListTopMenus.java
index a7ba938..7a85bcd 100644
--- a/java/com/google/gerrit/server/config/ListTopMenus.java
+++ b/java/com/google/gerrit/server/restapi/config/ListTopMenus.java
@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.webui.TopMenu;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
diff --git a/java/com/google/gerrit/server/config/PostCaches.java b/java/com/google/gerrit/server/restapi/config/PostCaches.java
similarity index 94%
rename from java/com/google/gerrit/server/config/PostCaches.java
rename to java/com/google/gerrit/server/restapi/config/PostCaches.java
index d08f0a9..f21672c 100644
--- a/java/com/google/gerrit/server/config/PostCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/PostCaches.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
@@ -25,8 +25,10 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.server.config.PostCaches.Input;
+import com.google.gerrit.server.config.CacheResource;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.restapi.config.PostCaches.Input;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
diff --git a/java/com/google/gerrit/server/config/RestCacheAdminModule.java b/java/com/google/gerrit/server/restapi/config/RestCacheAdminModule.java
similarity index 95%
rename from java/com/google/gerrit/server/config/RestCacheAdminModule.java
rename to java/com/google/gerrit/server/restapi/config/RestCacheAdminModule.java
index 992c62e..7283033 100644
--- a/java/com/google/gerrit/server/config/RestCacheAdminModule.java
+++ b/java/com/google/gerrit/server/restapi/config/RestCacheAdminModule.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.server.config.CacheResource.CACHE_KIND;
import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
diff --git a/java/com/google/gerrit/server/config/SetDiffPreferences.java b/java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java
similarity index 93%
rename from java/com/google/gerrit/server/config/SetDiffPreferences.java
rename to java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java
index 80c4625..a61b2aa 100644
--- a/java/com/google/gerrit/server/config/SetDiffPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/SetDiffPreferences.java
@@ -11,12 +11,11 @@
// 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.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.server.config.ConfigUtil.loadSection;
import static com.google.gerrit.server.config.ConfigUtil.skipField;
import static com.google.gerrit.server.config.ConfigUtil.storeSection;
-import static com.google.gerrit.server.config.GetDiffPreferences.readFromGit;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -24,6 +23,8 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.UserConfigSections;
@@ -65,7 +66,7 @@
if (!hasSetFields(in)) {
throw new BadRequestException("unsupported option");
}
- return writeToGit(readFromGit(gitManager, allUsersName, in));
+ return writeToGit(GetDiffPreferences.readFromGit(gitManager, allUsersName, in));
}
private DiffPreferencesInfo writeToGit(DiffPreferencesInfo in)
diff --git a/java/com/google/gerrit/server/config/SetPreferences.java b/java/com/google/gerrit/server/restapi/config/SetPreferences.java
similarity index 94%
rename from java/com/google/gerrit/server/config/SetPreferences.java
rename to java/com/google/gerrit/server/restapi/config/SetPreferences.java
index 55337d5..e2671d1 100644
--- a/java/com/google/gerrit/server/config/SetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/SetPreferences.java
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import static com.google.gerrit.server.config.ConfigUtil.loadSection;
import static com.google.gerrit.server.config.ConfigUtil.skipField;
import static com.google.gerrit.server.config.ConfigUtil.storeSection;
-import static com.google.gerrit.server.config.GetPreferences.readFromGit;
+import static com.google.gerrit.server.restapi.config.GetPreferences.readFromGit;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -27,6 +27,8 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.GeneralPreferencesLoader;
import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.UserConfigSections;
diff --git a/java/com/google/gerrit/server/config/TasksCollection.java b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
similarity index 95%
rename from java/com/google/gerrit/server/config/TasksCollection.java
rename to java/com/google/gerrit/server/restapi/config/TasksCollection.java
index fcaee8e..f5b6e56 100644
--- a/java/com/google/gerrit/server/config/TasksCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -21,6 +21,8 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.TaskResource;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.ProjectTask;
import com.google.gerrit.server.git.WorkQueue.Task;
diff --git a/java/com/google/gerrit/server/config/TopMenuCollection.java b/java/com/google/gerrit/server/restapi/config/TopMenuCollection.java
similarity index 91%
rename from java/com/google/gerrit/server/config/TopMenuCollection.java
rename to java/com/google/gerrit/server/restapi/config/TopMenuCollection.java
index 2fc2dc1..36a1b04 100644
--- a/java/com/google/gerrit/server/config/TopMenuCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/TopMenuCollection.java
@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.config;
+package com.google.gerrit.server.restapi.config;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.TopMenuResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/schema/Schema_144.java b/java/com/google/gerrit/server/schema/Schema_144.java
index d43b887..98dcd39 100644
--- a/java/com/google/gerrit/server/schema/Schema_144.java
+++ b/java/com/google/gerrit/server/schema/Schema_144.java
@@ -18,11 +18,11 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdReader;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -34,12 +34,8 @@
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.RevWalk;
public class Schema_144 extends SchemaVersion {
private static final String COMMIT_MSG = "Import external IDs from ReviewDb";
@@ -83,29 +79,16 @@
}
try {
- try (Repository repo = repoManager.openRepository(allUsersName);
- RevWalk rw = new RevWalk(repo);
- ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId rev = ExternalIdReader.readRevision(repo);
-
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
-
- for (ExternalId extId : toAdd) {
- ExternalIdsUpdate.upsert(rw, ins, noteMap, extId);
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
+ extIdNotes.upsert(toAdd);
+ try (MetaDataUpdate metaDataUpdate =
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, repo)) {
+ metaDataUpdate.getCommitBuilder().setAuthor(serverIdent);
+ metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
+ metaDataUpdate.getCommitBuilder().setMessage(COMMIT_MSG);
+ extIdNotes.commit(metaDataUpdate);
}
-
- ExternalIdsUpdate.commit(
- allUsersName,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- COMMIT_MSG,
- serverIdent,
- serverIdent,
- null,
- GitReferenceUpdated.DISABLED);
}
} catch (IOException | ConfigInvalidException e) {
throw new OrmException("Failed to migrate external IDs to NoteDb", e);
diff --git a/java/com/google/gerrit/server/schema/Schema_148.java b/java/com/google/gerrit/server/schema/Schema_148.java
index 47751cd..0c22964 100644
--- a/java/com/google/gerrit/server/schema/Schema_148.java
+++ b/java/com/google/gerrit/server/schema/Schema_148.java
@@ -14,17 +14,15 @@
package com.google.gerrit.server.schema;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-
import com.google.common.primitives.Ints;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdReader;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -32,13 +30,8 @@
import java.sql.SQLException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.Note;
-import org.eclipse.jgit.notes.NoteMap;
-import org.eclipse.jgit.revwalk.RevWalk;
public class Schema_148 extends SchemaVersion {
private static final String COMMIT_MSG = "Make account IDs of external IDs human-readable";
@@ -61,44 +54,22 @@
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- try (Repository repo = repoManager.openRepository(allUsersName);
- RevWalk rw = new RevWalk(repo);
- ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId rev = ExternalIdReader.readRevision(repo);
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
- boolean dirty = false;
- for (Note note : noteMap) {
- byte[] raw =
- rw.getObjectReader()
- .open(note.getData(), OBJ_BLOB)
- .getCachedBytes(ExternalIdReader.MAX_NOTE_SZ);
- try {
- ExternalId extId = ExternalId.parse(note.getName(), raw, note.getData());
-
- if (needsUpdate(extId)) {
- ExternalIdsUpdate.upsert(rw, ins, noteMap, extId);
- dirty = true;
- }
- } catch (ConfigInvalidException e) {
- ui.message(
- String.format("Warning: Ignoring invalid external ID note %s", note.getName()));
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
+ for (ExternalId extId : extIdNotes.all()) {
+ if (needsUpdate(extId)) {
+ extIdNotes.upsert(extId);
}
}
- if (dirty) {
- ExternalIdsUpdate.commit(
- allUsersName,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- COMMIT_MSG,
- serverUser,
- serverUser,
- null,
- GitReferenceUpdated.DISABLED);
+
+ try (MetaDataUpdate metaDataUpdate =
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, allUsersName, repo)) {
+ metaDataUpdate.getCommitBuilder().setAuthor(serverUser);
+ metaDataUpdate.getCommitBuilder().setCommitter(serverUser);
+ metaDataUpdate.getCommitBuilder().setMessage(COMMIT_MSG);
+ extIdNotes.commit(metaDataUpdate);
}
- } catch (IOException e) {
+ } catch (IOException | ConfigInvalidException e) {
throw new OrmException("Failed to update external IDs", e);
}
}
diff --git a/java/com/google/gerrit/server/schema/Schema_154.java b/java/com/google/gerrit/server/schema/Schema_154.java
index 5a4ba13..8e05d38 100644
--- a/java/com/google/gerrit/server/schema/Schema_154.java
+++ b/java/com/google/gerrit/server/schema/Schema_154.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.schema;
+import static java.util.stream.Collectors.toMap;
+
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
@@ -22,23 +25,40 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
+import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Migrate accounts to NoteDb. */
public class Schema_154 extends SchemaVersion {
+ private static final Logger log = LoggerFactory.getLogger(Schema_154.class);
+ private static final String TABLE = "accounts";
+ private static final ImmutableMap<String, AccountSetter> ACCOUNT_FIELDS_MAP =
+ ImmutableMap.<String, AccountSetter>builder()
+ .put("full_name", (a, rs, field) -> a.setFullName(rs.getString(field)))
+ .put("preferred_email", (a, rs, field) -> a.setPreferredEmail(rs.getString(field)))
+ .put("status", (a, rs, field) -> a.setStatus(rs.getString(field)))
+ .put("inactive", (a, rs, field) -> a.setActive(rs.getString(field).equals("N")))
+ .build();
+
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
private final Provider<PersonIdent> serverIdent;
@@ -76,23 +96,24 @@
}
private Set<Account> scanAccounts(ReviewDb db, ProgressMonitor pm) throws SQLException {
+ Map<String, AccountSetter> fields = getFields(db);
+ if (fields.isEmpty()) {
+ log.warn("Only account_id and registered_on fields are migrated for accounts");
+ }
+
+ List<String> queryFields = new ArrayList<>();
+ queryFields.add("account_id");
+ queryFields.add("registered_on");
+ queryFields.addAll(fields.keySet());
+ String query = "SELECT " + String.join(", ", queryFields) + String.format(" FROM %s", TABLE);
try (Statement stmt = newStatement(db);
- ResultSet rs =
- stmt.executeQuery(
- "SELECT account_id,"
- + " registered_on,"
- + " full_name, "
- + " preferred_email,"
- + " status,"
- + " inactive"
- + " FROM accounts")) {
+ ResultSet rs = stmt.executeQuery(query)) {
Set<Account> s = new HashSet<>();
while (rs.next()) {
Account a = new Account(new Account.Id(rs.getInt(1)), rs.getTimestamp(2));
- a.setFullName(rs.getString(3));
- a.setPreferredEmail(rs.getString(4));
- a.setStatus(rs.getString(5));
- a.setActive(rs.getString(6).equals("N"));
+ for (Map.Entry<String, AccountSetter> field : fields.entrySet()) {
+ field.getValue().set(a, rs, field.getKey());
+ }
s.add(a);
pm.update(1);
}
@@ -100,6 +121,17 @@
}
}
+ private Map<String, AccountSetter> getFields(ReviewDb db) throws SQLException {
+ JdbcSchema schema = (JdbcSchema) db;
+ Connection connection = schema.getConnection();
+ Set<String> columns = schema.getDialect().listColumns(connection, TABLE);
+ return ACCOUNT_FIELDS_MAP
+ .entrySet()
+ .stream()
+ .filter(e -> columns.contains(e.getKey()))
+ .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
private void updateAccountInNoteDb(Repository allUsersRepo, Account account)
throws IOException, ConfigInvalidException {
MetaDataUpdate md =
@@ -112,4 +144,9 @@
accountConfig.setAccount(account);
accountConfig.commit(md);
}
+
+ @FunctionalInterface
+ private interface AccountSetter {
+ void set(Account a, ResultSet rs, String field) throws SQLException;
+ }
}
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index a33ce86..3ed1f2f 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -14,6 +14,7 @@
"//java/com/google/gerrit/server/cache/h2",
"//java/com/google/gerrit/server/git/receive",
"//java/com/google/gerrit/server/ioutil",
+ "//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/util/cli",
"//java/org/eclipse/jgit:server",
diff --git a/java/com/google/gerrit/sshd/commands/FlushCaches.java b/java/com/google/gerrit/sshd/commands/FlushCaches.java
index 392fd29..2271ece 100644
--- a/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -16,17 +16,17 @@
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
-import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH;
-import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH_ALL;
+import static com.google.gerrit.server.restapi.config.PostCaches.Operation.FLUSH;
+import static com.google.gerrit.server.restapi.config.PostCaches.Operation.FLUSH_ALL;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.config.ListCaches;
-import com.google.gerrit.server.config.ListCaches.OutputFormat;
-import com.google.gerrit.server.config.PostCaches;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.restapi.config.ListCaches;
+import com.google.gerrit.server.restapi.config.ListCaches.OutputFormat;
+import com.google.gerrit.server.restapi.config.PostCaches;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/sshd/commands/KillCommand.java b/java/com/google/gerrit/sshd/commands/KillCommand.java
index 3465a9c..a7e751a 100644
--- a/java/com/google/gerrit/sshd/commands/KillCommand.java
+++ b/java/com/google/gerrit/sshd/commands/KillCommand.java
@@ -22,10 +22,10 @@
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.config.DeleteTask;
import com.google.gerrit.server.config.TaskResource;
-import com.google.gerrit.server.config.TasksCollection;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.restapi.config.DeleteTask;
+import com.google.gerrit.server.restapi.config.TasksCollection;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/sshd/commands/ShowCaches.java b/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 1ed7db3..a356f7f 100644
--- a/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -26,18 +26,18 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.config.GetSummary;
-import com.google.gerrit.server.config.GetSummary.JvmSummaryInfo;
-import com.google.gerrit.server.config.GetSummary.MemSummaryInfo;
-import com.google.gerrit.server.config.GetSummary.SummaryInfo;
-import com.google.gerrit.server.config.GetSummary.TaskSummaryInfo;
-import com.google.gerrit.server.config.GetSummary.ThreadSummaryInfo;
-import com.google.gerrit.server.config.ListCaches;
-import com.google.gerrit.server.config.ListCaches.CacheInfo;
-import com.google.gerrit.server.config.ListCaches.CacheType;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.restapi.config.GetSummary;
+import com.google.gerrit.server.restapi.config.GetSummary.JvmSummaryInfo;
+import com.google.gerrit.server.restapi.config.GetSummary.MemSummaryInfo;
+import com.google.gerrit.server.restapi.config.GetSummary.SummaryInfo;
+import com.google.gerrit.server.restapi.config.GetSummary.TaskSummaryInfo;
+import com.google.gerrit.server.restapi.config.GetSummary.ThreadSummaryInfo;
+import com.google.gerrit.server.restapi.config.ListCaches;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheType;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
diff --git a/java/com/google/gerrit/sshd/commands/ShowQueue.java b/java/com/google/gerrit/sshd/commands/ShowQueue.java
index 0296690..6d2fbb4 100644
--- a/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -23,13 +23,13 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.config.ListTasks;
-import com.google.gerrit.server.config.ListTasks.TaskInfo;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.restapi.config.ListTasks;
+import com.google.gerrit.server.restapi.config.ListTasks.TaskInfo;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index 2715c75..59102a9 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -25,6 +25,7 @@
"//java/com/google/gerrit/server:module",
"//java/com/google/gerrit/server/api",
"//java/com/google/gerrit/server/cache/h2",
+ "//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//lib:gwtorm",
"//lib:h2",
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 25e02c0..4b34ade 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -31,10 +31,12 @@
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import com.github.rholder.retry.StopStrategies;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
@@ -54,6 +56,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.accounts.AccountInput;
import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -64,6 +67,7 @@
import com.google.gerrit.extensions.api.config.ConsistencyCheckInput.CheckAccountsInput;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.EmailInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.events.AccountIndexedListener;
@@ -75,6 +79,7 @@
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.gpg.Fingerprint;
import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.gpg.testing.TestKey;
@@ -90,18 +95,25 @@
import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
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.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.LockFailureException;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.index.account.StalenessChecker;
import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.query.account.InternalAccountQuery;
+import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.FakeEmailSender.Message;
+import com.google.gerrit.testing.TestTimeUtil;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
@@ -119,10 +131,12 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.eclipse.jgit.api.errors.TransportException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -163,10 +177,6 @@
@Inject private ExternalIds externalIds;
- @Inject private ExternalIdsUpdate.User externalIdsUpdateFactory;
-
- @Inject private ExternalIdsUpdate.ServerNoReindex externalIdsUpdateNoReindexFactory;
-
@Inject private DynamicSet<AccountIndexedListener> accountIndexedListeners;
@Inject private DynamicSet<GitReferenceUpdatedListener> refUpdateListeners;
@@ -181,6 +191,16 @@
@Inject private AccountIndexer accountIndexer;
+ @Inject private OutgoingEmailValidator emailValidator;
+
+ @Inject private GitReferenceUpdated gitReferenceUpdated;
+
+ @Inject private RetryHelper.Metrics retryMetrics;
+
+ @Inject private Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
+
+ @Inject private ExternalIdNotes.Factory extIdNotesFactory;
+
@Inject
@Named("accounts")
private LoadingCache<Account.Id, Optional<AccountState>> accountsCache;
@@ -189,8 +209,6 @@
private RegistrationHandle accountIndexEventCounterHandle;
private RefUpdateCounter refUpdateCounter;
private RegistrationHandle refUpdateCounterHandle;
- private ExternalIdsUpdate externalIdsUpdate;
- private List<ExternalId> savedExternalIds;
@Before
public void addAccountIndexEventCounter() {
@@ -218,27 +236,6 @@
}
}
- @Before
- public void saveExternalIds() throws Exception {
- externalIdsUpdate = externalIdsUpdateFactory.create();
-
- savedExternalIds = new ArrayList<>();
- savedExternalIds.addAll(externalIds.byAccount(admin.id));
- savedExternalIds.addAll(externalIds.byAccount(user.id));
- }
-
- @After
- public void restoreExternalIds() throws Exception {
- if (savedExternalIds != null) {
- // savedExternalIds is null when we don't run SSH tests and the assume in
- // @Before in AbstractDaemonTest prevents this class' @Before method from
- // being executed.
- externalIdsUpdate.delete(externalIds.byAccount(admin.id));
- externalIdsUpdate.delete(externalIds.byAccount(user.id));
- externalIdsUpdate.insert(savedExternalIds);
- }
- }
-
@After
public void clearPublicKeyStore() throws Exception {
try (Repository repo = repoManager.openRepository(allUsers)) {
@@ -266,8 +263,8 @@
}
@Test
- public void create() throws Exception {
- Account.Id accountId = create(2); // account creation + external ID creation
+ public void createByAccountCreator() throws Exception {
+ Account.Id accountId = createByAccountCreator(2); // account creation + external ID creation
refUpdateCounter.assertRefUpdateFor(
RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS),
@@ -276,8 +273,9 @@
@Test
@UseSsh
- public void createWithSshKeys() throws Exception {
- Account.Id accountId = create(3); // account creation + external ID creation + adding SSH keys
+ public void createWithSshKeysByAccountCreator() throws Exception {
+ Account.Id accountId =
+ createByAccountCreator(3); // account creation + external ID creation + adding SSH keys
refUpdateCounter.assertRefUpdateFor(
ImmutableMap.of(
RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
@@ -289,7 +287,7 @@
1));
}
- private Account.Id create(int expectedAccountReindexCalls) throws Exception {
+ private Account.Id createByAccountCreator(int expectedAccountReindexCalls) throws Exception {
String name = "foo";
TestAccount foo = accountCreator.create(name);
AccountInfo info = gApi.accounts().id(foo.id.get()).get();
@@ -301,18 +299,115 @@
}
@Test
- public void createAnonymousCoward() throws Exception {
+ public void createAnonymousCowardByAccountCreator() throws Exception {
TestAccount anonymousCoward = accountCreator.create();
accountIndexedCounter.assertReindexOf(anonymousCoward);
assertUserBranchWithoutAccountConfig(anonymousCoward.getId());
}
@Test
+ public void create() throws Exception {
+ AccountInput input = new AccountInput();
+ input.username = "foo";
+ input.name = "Foo";
+ input.email = "foo@example.com";
+ AccountInfo accountInfo = gApi.accounts().create(input).get();
+ assertThat(accountInfo._accountId).isNotNull();
+ assertThat(accountInfo.username).isEqualTo(input.username);
+ assertThat(accountInfo.name).isEqualTo(input.name);
+ assertThat(accountInfo.email).isEqualTo(input.email);
+ assertThat(accountInfo.status).isNull();
+
+ Account.Id accountId = new Account.Id(accountInfo._accountId);
+ accountIndexedCounter.assertReindexOf(accountId, 2); // account creation + external ID creation
+ assertThat(externalIds.byAccount(accountId))
+ .containsExactly(
+ ExternalId.createUsername(input.username, accountId, null),
+ ExternalId.createEmail(accountId, input.email));
+ }
+
+ @Test
+ public void createAccountUsernameAlreadyTaken() throws Exception {
+ AccountInput input = new AccountInput();
+ input.username = admin.username;
+
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage("username '" + admin.username + "' already exists");
+ gApi.accounts().create(input);
+ }
+
+ @Test
+ public void createAccountEmailAlreadyTaken() throws Exception {
+ AccountInput input = new AccountInput();
+ input.username = "foo";
+ input.email = admin.email;
+
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("email '" + admin.email + "' already exists");
+ gApi.accounts().create(input);
+ }
+
+ @Test
+ public void commitMessageOnAccountUpdates() throws Exception {
+ AccountsUpdate au = accountsUpdate.create();
+ Account.Id accountId = new Account.Id(seq.nextAccountId());
+ au.insert("Create Test Account", accountId, u -> {});
+ assertLastCommitMessageOfUserBranch(accountId, "Create Test Account");
+
+ au.update("Set Status", accountId, u -> u.setStatus("Foo"));
+ assertLastCommitMessageOfUserBranch(accountId, "Set Status");
+ }
+
+ private void assertLastCommitMessageOfUserBranch(Account.Id accountId, String expectedMessage)
+ throws Exception {
+ try (Repository repo = repoManager.openRepository(allUsers);
+ RevWalk rw = new RevWalk(repo)) {
+ Ref exactRef = repo.exactRef(RefNames.refsUsers(accountId));
+ assertThat(rw.parseCommit(exactRef.getObjectId()).getShortMessage())
+ .isEqualTo(expectedMessage);
+ }
+ }
+
+ @Test
+ public void createAtomically() throws Exception {
+ TestTimeUtil.resetWithClockStep(1, SECONDS);
+ try {
+ Account.Id accountId = new Account.Id(seq.nextAccountId());
+ String fullName = "Foo";
+ ExternalId extId = ExternalId.createEmail(accountId, "foo@example.com");
+ Account account =
+ accountsUpdate
+ .create()
+ .insert(
+ "Create Account Atomically",
+ accountId,
+ u -> u.setFullName(fullName).addExternalId(extId));
+ assertThat(account.getFullName()).isEqualTo(fullName);
+
+ AccountInfo info = gApi.accounts().id(accountId.get()).get();
+ assertThat(info.name).isEqualTo(fullName);
+
+ List<EmailInfo> emails = gApi.accounts().id(accountId.get()).getEmails();
+ assertThat(emails.stream().map(e -> e.email).collect(toSet())).containsExactly(extId.email());
+
+ RevCommit commitUserBranch = getRemoteHead(allUsers, RefNames.refsUsers(accountId));
+ RevCommit commitRefsMetaExternalIds = getRemoteHead(allUsers, RefNames.REFS_EXTERNAL_IDS);
+ assertThat(commitUserBranch.getCommitTime())
+ .isEqualTo(commitRefsMetaExternalIds.getCommitTime());
+ } finally {
+ TestTimeUtil.useSystemTime();
+ }
+ }
+
+ @Test
public void updateNonExistingAccount() throws Exception {
Account.Id nonExistingAccountId = new Account.Id(999999);
AtomicBoolean consumerCalled = new AtomicBoolean();
Account account =
- accountsUpdate.create().update(nonExistingAccountId, a -> consumerCalled.set(true));
+ accountsUpdate
+ .create()
+ .update(
+ "Update Non-Existing Account", nonExistingAccountId, a -> consumerCalled.set(true));
assertThat(account).isNull();
assertThat(consumerCalled.get()).isFalse();
}
@@ -324,7 +419,9 @@
String status = "OOO";
Account account =
- accountsUpdate.create().update(anonymousCoward.getId(), a -> a.setStatus(status));
+ accountsUpdate
+ .create()
+ .update("Set status", anonymousCoward.getId(), u -> u.setStatus(status));
assertThat(account).isNotNull();
assertThat(account.getFullName()).isNull();
assertThat(account.getStatus()).isEqualTo(status);
@@ -771,11 +868,16 @@
String email = "foo.bar@example.com";
String extId1 = "foo:bar";
String extId2 = "foo:baz";
- List<ExternalId> extIds =
- ImmutableList.of(
- ExternalId.createWithEmail(ExternalId.Key.parse(extId1), admin.id, email),
- ExternalId.createWithEmail(ExternalId.Key.parse(extId2), admin.id, email));
- externalIdsUpdateFactory.create().insert(extIds);
+ accountsUpdate
+ .create()
+ .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()))
@@ -827,9 +929,14 @@
// exact match with other scheme
String email = "foo.bar@example.com";
- externalIdsUpdateFactory
+ accountsUpdate
.create()
- .insert(ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id, email));
+ .update(
+ "Add Email",
+ admin.id,
+ u ->
+ u.addExternalId(
+ ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id, email)));
assertEmail(emails.getAccountFor(email), admin);
// wrong case doesn't match
@@ -854,7 +961,9 @@
String prefix = "foo.preferred";
String prefEmail = prefix + "@example.com";
TestAccount foo = accountCreator.create(name("foo"));
- accountsUpdate.create().update(foo.id, a -> a.setPreferredEmail(prefEmail));
+ accountsUpdate
+ .create()
+ .update("Set Preferred Email", foo.id, u -> u.setPreferredEmail(prefEmail));
// verify that the account is still found when using the preferred email to lookup the account
ImmutableSet<Account.Id> accountsByPrefEmail = emails.getAccountFor(prefEmail);
@@ -1330,7 +1439,9 @@
String userRef = RefNames.refsUsers(foo.id);
String noEmail = "no.email";
- accountsUpdate.create().update(foo.id, a -> a.setPreferredEmail(noEmail));
+ accountsUpdate
+ .create()
+ .update("Set Preferred Email", foo.id, u -> u.setPreferredEmail(noEmail));
accountIndexedCounter.clear();
grant(allUsers, userRef, Permission.PUSH, false, REGISTERED_USERS);
@@ -1591,7 +1702,7 @@
assertIteratorSize(2, getOnlyKeyFromStore(key).getUserIDs());
pk = PGPPublicKey.removeCertification(pk, "foo:myId");
- info = addGpgKey(armor(pk)).get(id);
+ info = addGpgKeyNoReindex(armor(pk)).get(id);
assertThat(info.userIds).hasSize(1);
assertIteratorSize(1, getOnlyKeyFromStore(key).getUserIDs());
}
@@ -1600,7 +1711,12 @@
public void addOtherUsersGpgKey_Conflict() throws Exception {
// Both users have a matching external ID for this key.
addExternalIdEmail(admin, "test5@example.com");
- externalIdsUpdate.insert(ExternalId.create("foo", "myId", user.getId()));
+ accountsUpdate
+ .create()
+ .update(
+ "Add External ID",
+ user.getId(),
+ u -> u.addExternalId(ExternalId.create("foo", "myId", user.getId())));
accountIndexedCounter.assertReindexOf(user);
TestKey key = validKeyWithSecondUserId();
@@ -1774,7 +1890,12 @@
// Delete the external ID for the preferred email. This makes the account inconsistent since it
// now doesn't have an external ID for its preferred email.
- externalIdsUpdate.delete(ExternalId.createEmail(account.getId(), email));
+ accountsUpdate
+ .create()
+ .update(
+ "Delete External ID",
+ account.getId(),
+ u -> u.deleteExternalId(ExternalId.createEmail(account.getId(), email)));
expectedProblems.add(
new ConsistencyProblemInfo(
ConsistencyProblemInfo.Status.ERROR,
@@ -1812,11 +1933,11 @@
// metaId is set when account is created
AccountsUpdate au = accountsUpdate.create();
Account.Id accountId = new Account.Id(seq.nextAccountId());
- Account account = au.insert(accountId, a -> {});
+ Account account = au.insert("Create Test Account", accountId, u -> {});
assertThat(account.getMetaId()).isEqualTo(getMetaId(accountId));
// metaId is set when account is updated
- Account updatedAccount = au.update(accountId, a -> a.setFullName("foo"));
+ Account updatedAccount = au.update("Set Full Name", accountId, u -> u.setFullName("foo"));
assertThat(account.getMetaId()).isNotEqualTo(updatedAccount.getMetaId());
assertThat(updatedAccount.getMetaId()).isEqualTo(getMetaId(accountId));
}
@@ -1881,6 +2002,115 @@
}
@Test
+ public void retryOnLockFailure() throws Exception {
+ String status = "happy";
+ String fullName = "Foo";
+ AtomicBoolean doneBgUpdate = new AtomicBoolean(false);
+ PersonIdent ident = serverIdent.get();
+ AccountsUpdate update =
+ new AccountsUpdate(
+ repoManager,
+ gitReferenceUpdated,
+ null,
+ allUsers,
+ emailValidator,
+ metaDataUpdateInternalFactory,
+ new RetryHelper(
+ cfg,
+ retryMetrics,
+ null,
+ null,
+ null,
+ r -> r.withBlockStrategy(noSleepBlockStrategy)),
+ extIdNotesFactory,
+ ident,
+ ident,
+ () -> {
+ if (!doneBgUpdate.getAndSet(true)) {
+ try {
+ accountsUpdate.create().update("Set Status", admin.id, u -> u.setStatus(status));
+ } catch (IOException | ConfigInvalidException | OrmException e) {
+ // Ignore, the successful update of the account is asserted later
+ }
+ }
+ });
+ assertThat(doneBgUpdate.get()).isFalse();
+ AccountInfo accountInfo = gApi.accounts().id(admin.id.get()).get();
+ assertThat(accountInfo.status).isNull();
+ assertThat(accountInfo.name).isNotEqualTo(fullName);
+
+ Account updatedAccount = update.update("Set Full Name", admin.id, u -> u.setFullName(fullName));
+ assertThat(doneBgUpdate.get()).isTrue();
+
+ assertThat(updatedAccount.getStatus()).isEqualTo(status);
+ assertThat(updatedAccount.getFullName()).isEqualTo(fullName);
+
+ accountInfo = gApi.accounts().id(admin.id.get()).get();
+ assertThat(accountInfo.status).isEqualTo(status);
+ assertThat(accountInfo.name).isEqualTo(fullName);
+ }
+
+ @Test
+ public void failAfterRetryerGivesUp() throws Exception {
+ List<String> status = ImmutableList.of("foo", "bar", "baz");
+ String fullName = "Foo";
+ AtomicInteger bgCounter = new AtomicInteger(0);
+ PersonIdent ident = serverIdent.get();
+ AccountsUpdate update =
+ new AccountsUpdate(
+ repoManager,
+ gitReferenceUpdated,
+ null,
+ allUsers,
+ emailValidator,
+ metaDataUpdateInternalFactory,
+ new RetryHelper(
+ cfg,
+ retryMetrics,
+ null,
+ null,
+ null,
+ r ->
+ r.withStopStrategy(StopStrategies.stopAfterAttempt(status.size()))
+ .withBlockStrategy(noSleepBlockStrategy)),
+ extIdNotesFactory,
+ ident,
+ ident,
+ () -> {
+ try {
+ accountsUpdate
+ .create()
+ .update(
+ "Set Status",
+ admin.id,
+ u -> u.setStatus(status.get(bgCounter.getAndAdd(1))));
+ } catch (IOException | ConfigInvalidException | OrmException e) {
+ // Ignore, the expected exception is asserted later
+ }
+ });
+ assertThat(bgCounter.get()).isEqualTo(0);
+ AccountInfo accountInfo = gApi.accounts().id(admin.id.get()).get();
+ assertThat(accountInfo.status).isNull();
+ assertThat(accountInfo.name).isNotEqualTo(fullName);
+
+ try {
+ update.update("Set Full Name", admin.id, u -> u.setFullName(fullName));
+ fail("expected LockFailureException");
+ } catch (LockFailureException e) {
+ // Ignore, expected
+ }
+ assertThat(bgCounter.get()).isEqualTo(status.size());
+
+ Account updatedAccount = accounts.get(admin.id);
+ assertThat(updatedAccount.getStatus()).isEqualTo(Iterables.getLast(status));
+ assertThat(updatedAccount.getFullName()).isEqualTo(admin.fullName);
+
+ accountInfo = gApi.accounts().id(admin.id.get()).get();
+ assertThat(accountInfo.status).isEqualTo(Iterables.getLast(status));
+ assertThat(accountInfo.name).isEqualTo(admin.fullName);
+ }
+
+ @Test
public void stalenessChecker() throws Exception {
// Newly created account is not stale.
AccountInfo accountInfo = gApi.accounts().create(name("foo")).get();
@@ -1912,17 +2142,28 @@
// Manually inserting/updating/deleting an external ID of the user makes the index document
// stale.
- ExternalIdsUpdate externalIdsUpdateNoReindex = externalIdsUpdateNoReindexFactory.create();
- ExternalId.Key key = ExternalId.Key.create("foo", "foo");
- externalIdsUpdateNoReindex.insert(ExternalId.create(key, accountId));
- assertStaleAccountAndReindex(accountId);
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
- externalIdsUpdateNoReindex.upsert(
- ExternalId.createWithEmail(key, accountId, "foo@example.com"));
- assertStaleAccountAndReindex(accountId);
+ ExternalId.Key key = ExternalId.Key.create("foo", "foo");
+ extIdNotes.insert(ExternalId.create(key, accountId));
+ try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
+ extIdNotes.commit(update);
+ }
+ assertStaleAccountAndReindex(accountId);
- externalIdsUpdateNoReindex.delete(accountId, key);
- assertStaleAccountAndReindex(accountId);
+ extIdNotes.upsert(ExternalId.createWithEmail(key, accountId, "foo@example.com"));
+ try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
+ extIdNotes.commit(update);
+ }
+ assertStaleAccountAndReindex(accountId);
+
+ extIdNotes.delete(accountId, key);
+ try (MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
+ extIdNotes.commit(update);
+ }
+ assertStaleAccountAndReindex(accountId);
+ }
// Manually delete account
try (Repository repo = repoManager.openRepository(allUsers);
@@ -2047,8 +2288,14 @@
private void addExternalIdEmail(TestAccount account, String email) throws Exception {
checkNotNull(email);
- externalIdsUpdate.insert(
- ExternalId.createWithEmail(name("test"), email, account.getId(), email));
+ accountsUpdate
+ .create()
+ .update(
+ "Add Email",
+ account.getId(),
+ u ->
+ u.addExternalId(
+ ExternalId.createWithEmail(name("test"), email, account.getId(), email)));
accountIndexedCounter.assertReindexOf(account);
setApiUser(account);
}
@@ -2060,6 +2307,10 @@
return gpgKeys;
}
+ private Map<String, GpgKeyInfo> addGpgKeyNoReindex(String armored) throws Exception {
+ return gApi.accounts().self().putGpgKeys(ImmutableList.of(armored), ImmutableList.<String>of());
+ }
+
private void assertUser(AccountInfo info, TestAccount account) throws Exception {
assertUser(info, account, null);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
index e0fc358..0b7f340 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIdIT.java
@@ -17,10 +17,12 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.extensions.restapi.DeprecatedIdentifierException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Project;
import org.junit.Before;
@@ -119,4 +121,27 @@
exception.expect(ResourceNotFoundException.class);
gApi.changes().id("I1234567890");
}
+
+ @Test
+ @GerritConfig(
+ name = "change.api.allowedIdentifier",
+ values = {"PROJECT_NUMERIC_ID", "NUMERIC_ID"}
+ )
+ public void deprecatedChangeIdReturnsBadRequest() throws Exception {
+ // project~changeNumber still works
+ ChangeApi cApi1 = gApi.changes().id(project.get(), changeInfo._number);
+ assertThat(cApi1.get().changeId).isEqualTo(changeInfo.changeId);
+ // Change number still works
+ ChangeApi cApi2 = gApi.changes().id(changeInfo._number);
+ assertThat(cApi2.get().changeId).isEqualTo(changeInfo.changeId);
+ // IHash throws
+ ChangeInfo ci =
+ gApi.changes().create(new ChangeInput(project.get(), "master", "different message")).get();
+ exception.expect(DeprecatedIdentifierException.class);
+ exception.expectMessage(
+ "The provided change identifier "
+ + ci.changeId
+ + " is deprecated. Use 'project~changeNumber' instead.");
+ gApi.changes().id(ci.changeId);
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index 38230d3..9ccc138 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -24,6 +24,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
import com.github.rholder.retry.StopStrategies;
import com.google.common.collect.ImmutableList;
@@ -43,20 +44,23 @@
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.externalids.DisabledExternalIdCache;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.account.externalids.ExternalIdReader;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.LockFailureException;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmDuplicateKeyException;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -69,11 +73,14 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
@@ -82,11 +89,12 @@
import org.junit.Test;
public class ExternalIdIT extends AbstractDaemonTest {
- @Inject private ExternalIdsUpdate.Server extIdsUpdate;
+ @Inject private AccountsUpdate.Server accountsUpdate;
@Inject private ExternalIds externalIds;
@Inject private ExternalIdReader externalIdReader;
@Inject private MetricMaker metricMaker;
@Inject private RetryHelper.Metrics retryMetrics;
+ @Inject private ExternalIdNotes.Factory externalIdNotesFactory;
@Test
public void getExternalIds() throws Exception {
@@ -454,31 +462,28 @@
return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.ERROR, message);
}
- private void insertValidExternalIds() throws IOException, ConfigInvalidException, OrmException {
+ private void insertValidExternalIds() throws Exception {
MutableInteger i = new MutableInteger();
String scheme = "valid";
- ExternalIdsUpdate u = extIdsUpdate.create();
// create valid external IDs
- u.insert(
+ insertExtId(
ExternalId.createWithPassword(
ExternalId.Key.parse(nextId(scheme, i)),
admin.id,
"admin.other@example.com",
"secret-password"));
- u.insert(createExternalIdWithOtherCaseEmail(nextId(scheme, i)));
+ insertExtId(createExternalIdWithOtherCaseEmail(nextId(scheme, i)));
}
- private Set<ConsistencyProblemInfo> insertInvalidButParsableExternalIds()
- throws IOException, ConfigInvalidException, OrmException {
+ private Set<ConsistencyProblemInfo> insertInvalidButParsableExternalIds() throws Exception {
MutableInteger i = new MutableInteger();
String scheme = "invalid";
- ExternalIdsUpdate u = extIdsUpdate.create();
Set<ConsistencyProblemInfo> expectedProblems = new HashSet<>();
ExternalId extIdForNonExistingAccount =
createExternalIdForNonExistingAccount(nextId(scheme, i));
- u.insert(extIdForNonExistingAccount);
+ insertExtIdForNonExistingAccount(extIdForNonExistingAccount);
expectedProblems.add(
consistencyError(
"External ID '"
@@ -487,7 +492,7 @@
+ extIdForNonExistingAccount.accountId().get()));
ExternalId extIdWithInvalidEmail = createExternalIdWithInvalidEmail(nextId(scheme, i));
- u.insert(extIdWithInvalidEmail);
+ insertExtId(extIdWithInvalidEmail);
expectedProblems.add(
consistencyError(
"External ID '"
@@ -496,7 +501,7 @@
+ extIdWithInvalidEmail.email()));
ExternalId extIdWithDuplicateEmail = createExternalIdWithDuplicateEmail(nextId(scheme, i));
- u.insert(extIdWithDuplicateEmail);
+ insertExtId(extIdWithDuplicateEmail);
expectedProblems.add(
consistencyError(
"Email '"
@@ -508,7 +513,7 @@
+ "'"));
ExternalId extIdWithBadPassword = createExternalIdWithBadPassword("admin-username");
- u.insert(extIdWithBadPassword);
+ insertExtId(extIdWithBadPassword);
expectedProblems.add(
consistencyError(
"External ID '"
@@ -570,117 +575,117 @@
private String insertExternalIdWithoutAccountId(Repository repo, RevWalk rw, String externalId)
throws IOException {
- ObjectId rev = ExternalIdReader.readRevision(repo);
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
-
- ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id);
-
- try (ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId noteId = extId.key().sha1();
- Config c = new Config();
- extId.writeToConfig(c);
- c.unset("externalId", extId.key().get(), "accountId");
- byte[] raw = c.toText().getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
-
- ExternalIdsUpdate.commit(
- allUsers,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- "Add external ID",
- admin.getIdent(),
- admin.getIdent(),
- null,
- GitReferenceUpdated.DISABLED);
- return noteId.getName();
- }
+ return insertExternalId(
+ repo,
+ rw,
+ (ins, noteMap) -> {
+ ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id);
+ ObjectId noteId = extId.key().sha1();
+ Config c = new Config();
+ extId.writeToConfig(c);
+ c.unset("externalId", extId.key().get(), "accountId");
+ byte[] raw = c.toText().getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
}
private String insertExternalIdWithKeyThatDoesntMatchNoteId(
Repository repo, RevWalk rw, String externalId) throws IOException {
- ObjectId rev = ExternalIdReader.readRevision(repo);
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
-
- ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id);
-
- try (ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId noteId = ExternalId.Key.parse(externalId + "x").sha1();
- Config c = new Config();
- extId.writeToConfig(c);
- byte[] raw = c.toText().getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
-
- ExternalIdsUpdate.commit(
- allUsers,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- "Add external ID",
- admin.getIdent(),
- admin.getIdent(),
- null,
- GitReferenceUpdated.DISABLED);
- return noteId.getName();
- }
+ return insertExternalId(
+ repo,
+ rw,
+ (ins, noteMap) -> {
+ ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id);
+ ObjectId noteId = ExternalId.Key.parse(externalId + "x").sha1();
+ Config c = new Config();
+ extId.writeToConfig(c);
+ byte[] raw = c.toText().getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
}
private String insertExternalIdWithInvalidConfig(Repository repo, RevWalk rw, String externalId)
throws IOException {
- ObjectId rev = ExternalIdReader.readRevision(repo);
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
-
- try (ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
- byte[] raw = "bad-config".getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
-
- ExternalIdsUpdate.commit(
- allUsers,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- "Add external ID",
- admin.getIdent(),
- admin.getIdent(),
- null,
- GitReferenceUpdated.DISABLED);
- return noteId.getName();
- }
+ return insertExternalId(
+ repo,
+ rw,
+ (ins, noteMap) -> {
+ ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+ byte[] raw = "bad-config".getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
}
private String insertExternalIdWithEmptyNote(Repository repo, RevWalk rw, String externalId)
throws IOException {
+ return insertExternalId(
+ repo,
+ rw,
+ (ins, noteMap) -> {
+ ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+ byte[] raw = "".getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+ return noteId;
+ });
+ }
+
+ private String insertExternalId(Repository repo, RevWalk rw, ExternalIdInserter extIdInserter)
+ throws IOException {
ObjectId rev = ExternalIdReader.readRevision(repo);
NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
try (ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
- byte[] raw = "".getBytes(UTF_8);
- ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
- noteMap.set(noteId, dataBlob);
+ ObjectId noteId = extIdInserter.addNote(ins, noteMap);
- ExternalIdsUpdate.commit(
- allUsers,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- "Add external ID",
- admin.getIdent(),
- admin.getIdent(),
- null,
- GitReferenceUpdated.DISABLED);
+ CommitBuilder cb = new CommitBuilder();
+ cb.setMessage("Update external IDs");
+ cb.setTreeId(noteMap.writeTree(ins));
+ cb.setAuthor(admin.getIdent());
+ cb.setCommitter(admin.getIdent());
+ if (!rev.equals(ObjectId.zeroId())) {
+ cb.setParentId(rev);
+ } else {
+ cb.setParentIds(); // Ref is currently nonexistent, commit has no parents.
+ }
+ if (cb.getTreeId() == null) {
+ if (rev.equals(ObjectId.zeroId())) {
+ cb.setTreeId(ins.insert(OBJ_TREE, new byte[] {})); // No parent, assume empty tree.
+ } else {
+ RevCommit p = rw.parseCommit(rev);
+ cb.setTreeId(p.getTree()); // Copy tree from parent.
+ }
+ }
+ ObjectId commitId = ins.insert(cb);
+ ins.flush();
+
+ RefUpdate u = repo.updateRef(RefNames.REFS_EXTERNAL_IDS);
+ u.setExpectedOldObjectId(rev);
+ u.setNewObjectId(commitId);
+ RefUpdate.Result res = u.update();
+ switch (res) {
+ case NEW:
+ case FAST_FORWARD:
+ case NO_CHANGE:
+ case RENAMED:
+ case FORCED:
+ break;
+ case LOCK_FAILURE:
+ case IO_FAILURE:
+ case NOT_ATTEMPTED:
+ case REJECTED:
+ case REJECTED_CURRENT_BRANCH:
+ case REJECTED_MISSING_OBJECT:
+ case REJECTED_OTHER_REASON:
+ default:
+ throw new IOException("Updating external IDs failed with " + res);
+ }
return noteId.getName();
}
}
@@ -718,15 +723,12 @@
ExternalIdsUpdate update =
new ExternalIdsUpdate(
repoManager,
+ () -> metaDataUpdateFactory.create(allUsers),
accountCache,
allUsers,
metricMaker,
externalIds,
new DisabledExternalIdCache(),
- serverIdent.get(),
- serverIdent.get(),
- null,
- GitReferenceUpdated.DISABLED,
new RetryHelper(
cfg,
retryMetrics,
@@ -737,8 +739,8 @@
() -> {
if (!doneBgUpdate.getAndSet(true)) {
try {
- extIdsUpdate.create().insert(ExternalId.create(barId, admin.id));
- } catch (IOException | ConfigInvalidException | OrmException e) {
+ insertExtId(ExternalId.create(barId, admin.id));
+ } catch (Exception e) {
// Ignore, the successful insertion of the external ID is asserted later
}
}
@@ -762,15 +764,12 @@
ExternalIdsUpdate update =
new ExternalIdsUpdate(
repoManager,
+ () -> metaDataUpdateFactory.create(allUsers),
accountCache,
allUsers,
metricMaker,
externalIds,
new DisabledExternalIdCache(),
- serverIdent.get(),
- serverIdent.get(),
- null,
- GitReferenceUpdated.DISABLED,
new RetryHelper(
cfg,
retryMetrics,
@@ -782,10 +781,8 @@
.withBlockStrategy(noSleepBlockStrategy)),
() -> {
try {
- extIdsUpdate
- .create()
- .insert(ExternalId.create(extIdsKeys[bgCounter.getAndAdd(1)], admin.id));
- } catch (IOException | ConfigInvalidException | OrmException e) {
+ insertExtId(ExternalId.create(extIdsKeys[bgCounter.getAndAdd(1)], admin.id));
+ } catch (Exception e) {
// Ignore, the successful insertion of the external ID is asserted later
}
});
@@ -806,7 +803,12 @@
public void readExternalIdWithAccountIdThatCanBeExpressedInKiB() throws Exception {
ExternalId.Key extIdKey = ExternalId.Key.parse("foo:bar");
Account.Id accountId = new Account.Id(1024 * 100);
- extIdsUpdate.create().insert(ExternalId.create(extIdKey, accountId));
+ accountsUpdate
+ .create()
+ .insert(
+ "Create Account with Bad External ID",
+ accountId,
+ u -> u.addExternalId(ExternalId.create(extIdKey, accountId)));
ExternalId extId = externalIds.get(extIdKey);
assertThat(extId.accountId()).isEqualTo(accountId);
}
@@ -817,20 +819,24 @@
try (AutoCloseable ctx = createFailOnLoadContext()) {
// insert external ID
ExternalId extId = ExternalId.create("foo", "bar", admin.id);
- extIdsUpdate.create().insert(extId);
+ insertExtId(extId);
expectedExtIds.add(extId);
assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExtIds);
// update external ID
expectedExtIds.remove(extId);
- extId = ExternalId.createWithEmail("foo", "bar", admin.id, "foo.bar@example.com");
- extIdsUpdate.create().upsert(extId);
- expectedExtIds.add(extId);
+ ExternalId extId2 = ExternalId.createWithEmail("foo", "bar", admin.id, "foo.bar@example.com");
+ accountsUpdate
+ .create()
+ .update("Update External ID", admin.id, u -> u.updateExternalId(extId2));
+ expectedExtIds.add(extId2);
assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExtIds);
// delete external ID
- extIdsUpdate.create().delete(extId);
- expectedExtIds.remove(extId);
+ accountsUpdate
+ .create()
+ .update("Delete External ID", admin.id, u -> u.deleteExternalId(extId));
+ expectedExtIds.remove(extId2);
assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExtIds);
}
}
@@ -866,50 +872,47 @@
assertThat(externalIds.byAccount(admin.id)).containsExactlyElementsIn(expectedExternalIds);
}
- private void insertExtIdBehindGerritsBack(ExternalId extId) throws Exception {
+ private void insertExtId(ExternalId extId) throws Exception {
+ accountsUpdate
+ .create()
+ .update("Add External ID", extId.accountId(), u -> u.addExternalId(extId));
+ }
+
+ private void insertExtIdForNonExistingAccount(ExternalId extId) throws Exception {
+ // Cannot use AccountsUpdate to insert an external ID for a non-existing account.
try (Repository repo = repoManager.openRepository(allUsers);
- RevWalk rw = new RevWalk(repo);
- ObjectInserter ins = repo.newObjectInserter()) {
- ObjectId rev = ExternalIdReader.readRevision(repo);
- NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
- ExternalIdsUpdate.insert(rw, ins, noteMap, extId);
- ExternalIdsUpdate.commit(
- allUsers,
- repo,
- rw,
- ins,
- rev,
- noteMap,
- "insert new ID",
- serverIdent.get(),
- serverIdent.get(),
- null,
- GitReferenceUpdated.DISABLED);
+ MetaDataUpdate update = metaDataUpdateFactory.create(allUsers)) {
+ ExternalIdNotes extIdNotes = externalIdNotesFactory.load(repo);
+ 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.
+ ExternalIdNotes extIdNotes = ExternalIdNotes.loadNoCacheUpdate(repo);
+ extIdNotes.insert(extId);
+ try (MetaDataUpdate metaDataUpdate =
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, null, repo)) {
+ metaDataUpdate.getCommitBuilder().setAuthor(admin.getIdent());
+ metaDataUpdate.getCommitBuilder().setCommitter(admin.getIdent());
+ extIdNotes.commit(metaDataUpdate);
+ }
}
}
private void addExtId(TestRepository<?> testRepo, ExternalId... extIds)
throws IOException, OrmDuplicateKeyException, ConfigInvalidException {
- ObjectId rev = ExternalIdReader.readRevision(testRepo.getRepository());
-
- try (ObjectInserter ins = testRepo.getRepository().newObjectInserter()) {
- NoteMap noteMap = ExternalIdReader.readNoteMap(testRepo.getRevWalk(), rev);
- for (ExternalId extId : extIds) {
- ExternalIdsUpdate.insert(testRepo.getRevWalk(), ins, noteMap, extId);
- }
-
- ExternalIdsUpdate.commit(
- allUsers,
- testRepo.getRepository(),
- testRepo.getRevWalk(),
- ins,
- rev,
- noteMap,
- "Add external ID",
- admin.getIdent(),
- admin.getIdent(),
- null,
- GitReferenceUpdated.DISABLED);
+ ExternalIdNotes extIdNotes = externalIdNotesFactory.load(testRepo.getRepository());
+ extIdNotes.insert(Arrays.asList(extIds));
+ try (MetaDataUpdate metaDataUpdate =
+ new MetaDataUpdate(GitReferenceUpdated.DISABLED, null, testRepo.getRepository())) {
+ metaDataUpdate.getCommitBuilder().setAuthor(admin.getIdent());
+ metaDataUpdate.getCommitBuilder().setCommitter(admin.getIdent());
+ extIdNotes.commit(metaDataUpdate);
+ extIdNotes.updateCaches();
}
}
@@ -950,4 +953,9 @@
}
};
}
+
+ @FunctionalInterface
+ private interface ExternalIdInserter {
+ public ObjectId addNote(ObjectInserter ins, NoteMap noteMap) throws IOException;
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/BUILD b/javatests/com/google/gerrit/acceptance/rest/config/BUILD
index 825523d..8550423 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/config/BUILD
@@ -4,4 +4,7 @@
srcs = glob(["*IT.java"]),
group = "rest_config",
labels = ["rest"],
+ deps = [
+ "//java/com/google/gerrit/server/restapi",
+ ],
)
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java b/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
index 2ef74b4..65ed7e4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/CacheOperationsIT.java
@@ -15,15 +15,15 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH;
-import static com.google.gerrit.server.config.PostCaches.Operation.FLUSH_ALL;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.restapi.config.PostCaches.Operation.FLUSH;
+import static com.google.gerrit.server.restapi.config.PostCaches.Operation.FLUSH_ALL;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.server.config.ListCaches.CacheInfo;
-import com.google.gerrit.server.config.PostCaches;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
+import com.google.gerrit.server.restapi.config.PostCaches;
import java.util.Arrays;
import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java
index f196684..7133580 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ConfirmEmailIT.java
@@ -15,8 +15,8 @@
package com.google.gerrit.acceptance.rest.config;
import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.server.config.ConfirmEmail;
import com.google.gerrit.server.mail.EmailTokenVerifier;
+import com.google.gerrit.server.restapi.config.ConfirmEmail;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java b/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
index bc27fff..caecefa 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/FlushCacheIT.java
@@ -20,7 +20,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.server.config.ListCaches.CacheInfo;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
import org.junit.Test;
public class FlushCacheIT extends AbstractDaemonTest {
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/GetCacheIT.java b/javatests/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
index fe600cc..247d63b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
@@ -18,8 +18,8 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.config.ListCaches.CacheInfo;
-import com.google.gerrit.server.config.ListCaches.CacheType;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheType;
import org.junit.Test;
public class GetCacheIT extends AbstractDaemonTest {
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java b/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
index 900b4be..6d2c6dfa 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/GetTaskIT.java
@@ -18,7 +18,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.config.ListTasks.TaskInfo;
+import com.google.gerrit.server.restapi.config.ListTasks.TaskInfo;
import com.google.gson.reflect.TypeToken;
import java.util.List;
import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/KillTaskIT.java b/javatests/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
index 7cd9584..c19f5d0 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/KillTaskIT.java
@@ -20,7 +20,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.config.ListTasks.TaskInfo;
+import com.google.gerrit.server.restapi.config.ListTasks.TaskInfo;
import com.google.gson.reflect.TypeToken;
import java.util.List;
import java.util.Optional;
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
index 4d48bf4..ae17be0 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
@@ -20,8 +20,8 @@
import com.google.common.collect.Ordering;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.config.ListCaches.CacheInfo;
-import com.google.gerrit.server.config.ListCaches.CacheType;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
+import com.google.gerrit.server.restapi.config.ListCaches.CacheType;
import com.google.gson.reflect.TypeToken;
import java.util.Arrays;
import java.util.List;
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ListTasksIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ListTasksIT.java
index ee6411a..674ca79 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ListTasksIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ListTasksIT.java
@@ -18,7 +18,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.config.ListTasks.TaskInfo;
+import com.google.gerrit.server.restapi.config.ListTasks.TaskInfo;
import com.google.gson.reflect.TypeToken;
import java.util.List;
import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 492245b..8ec145f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -56,7 +56,6 @@
// change
@GerritConfig(name = "change.allowDrafts", value = "false")
@GerritConfig(name = "change.largeChange", value = "300")
- @GerritConfig(name = "change.privateByDefault", value = "true")
@GerritConfig(name = "change.replyTooltip", value = "Publish votes and draft comments")
@GerritConfig(name = "change.replyLabel", value = "Vote")
@GerritConfig(name = "change.updateDelay", value = "50s")
@@ -101,7 +100,6 @@
// change
assertThat(i.change.allowDrafts).isNull();
assertThat(i.change.largeChange).isEqualTo(300);
- assertThat(i.change.privateByDefault).isTrue();
assertThat(i.change.replyTooltip).startsWith("Publish votes and draft comments");
assertThat(i.change.replyLabel).isEqualTo("Vote\u2026");
assertThat(i.change.updateDelay).isEqualTo(50);
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 7b36126..348f027 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -16,6 +16,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
import static java.util.stream.Collectors.groupingBy;
@@ -61,6 +62,7 @@
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -945,6 +947,23 @@
assertThat(getChangeSortedComments(changeId)).hasSize(3);
}
+ @Test
+ public void jsonCommentHasLegacyFormatFalse() throws Exception {
+ assume().that(notesMigration.readChanges()).isTrue();
+ assertThat(noteUtil.getWriteJson()).isTrue();
+
+ PushOneCommit.Result result = createChange();
+ Change.Id changeId = result.getChange().getId();
+ addComment(result.getChangeId(), "comment");
+
+ Collection<com.google.gerrit.reviewdb.client.Comment> comments =
+ notesFactory.createChecked(db, project, changeId).getComments().values();
+ assertThat(comments).hasSize(1);
+ com.google.gerrit.reviewdb.client.Comment comment = comments.iterator().next();
+ assertThat(comment.message).isEqualTo("comment");
+ assertThat(comment.legacyFormat).isFalse();
+ }
+
private List<CommentInfo> getChangeSortedComments(String changeId) throws Exception {
List<CommentInfo> comments = new ArrayList<>();
Map<String, List<CommentInfo>> commentsMap = getPublishedComments(changeId);
diff --git a/javatests/com/google/gerrit/acceptance/server/change/LegacyCommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/LegacyCommentsIT.java
new file mode 100644
index 0000000..a3a0339
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/change/LegacyCommentsIT.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2017 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.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Comment;
+import com.google.gerrit.server.notedb.ChangeNoteUtil;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
+import java.util.Collection;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+@NoHttpd
+public class LegacyCommentsIT extends AbstractDaemonTest {
+ @Inject private ChangeNoteUtil noteUtil;
+
+ @ConfigSuite.Default
+ public static Config writeJsonFalseConfig() {
+ Config c = new Config();
+ c.setBoolean("noteDb", null, "writeJson", false);
+ return c;
+ }
+
+ @Before
+ public void setUp() {
+ setApiUser(user);
+ }
+
+ @Test
+ public void legacyCommentHasLegacyFormatTrue() throws Exception {
+ assume().that(notesMigration.readChanges()).isTrue();
+ assertThat(noteUtil.getWriteJson()).isFalse();
+
+ PushOneCommit.Result result = createChange();
+ Change.Id changeId = result.getChange().getId();
+
+ CommentInput cin = new CommentInput();
+ cin.message = "comment";
+ cin.path = PushOneCommit.FILE_NAME;
+
+ ReviewInput rin = new ReviewInput();
+ rin.comments = ImmutableMap.of(cin.path, ImmutableList.of(cin));
+ gApi.changes().id(changeId.get()).current().review(rin);
+
+ Collection<Comment> comments =
+ notesFactory.createChecked(db, project, changeId).getComments().values();
+ assertThat(comments).hasSize(1);
+ Comment comment = comments.iterator().next();
+ assertThat(comment.message).isEqualTo("comment");
+ assertThat(comment.legacyFormat).isTrue();
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 3400fe6..98ab8f1 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -1287,6 +1287,28 @@
assertThat(newPs3.getCreatedOn()).isGreaterThan(ps1.getCreatedOn());
}
+ @Test
+ public void ignoreNoteDbStateWithNoCorrespondingRefWhenWritesAndReadsDisabled() throws Exception {
+ PushOneCommit.Result r = createChange();
+ Change.Id id = r.getChange().getId();
+ ReviewDb db = getUnwrappedDb();
+ Change c = db.changes().get(id);
+ c.setNoteDbState("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ db.changes().update(Collections.singleton(c));
+ c = db.changes().get(id);
+
+ String refName = RefNames.changeMetaRef(id);
+ assertThat(getMetaRef(project, refName)).isNull();
+
+ ChangeNotes notes = notesFactory.create(dbProvider.get(), project, id);
+ assertThat(notes.getChange().getRowVersion()).isEqualTo(c.getRowVersion());
+
+ notes = notesFactory.createChecked(dbProvider.get(), project, id);
+ assertThat(notes.getChange().getRowVersion()).isEqualTo(c.getRowVersion());
+
+ assertThat(getMetaRef(project, refName)).isNull();
+ }
+
private void assertChangesReadOnly(RestApiException e) throws Exception {
Throwable cause = e.getCause();
assertThat(cause).isInstanceOf(UpdateException.class);
diff --git a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
index 6d56a122..d130c20 100644
--- a/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
+++ b/javatests/com/google/gerrit/gpg/GerritPublicKeyCheckerTest.java
@@ -40,7 +40,7 @@
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -55,6 +55,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
@@ -84,7 +85,7 @@
@Inject private ThreadLocalRequestContext requestContext;
- @Inject private ExternalIdsUpdate.Server externalIdsUpdateFactory;
+ @Inject private ExternalIds externalIds;
private LifecycleManager lifecycle;
private ReviewDb db;
@@ -116,7 +117,9 @@
schemaCreator.create(db);
userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
// Note: does not match any key in TestKeys.
- accountsUpdate.create().update(userId, a -> a.setPreferredEmail("user@example.com"));
+ accountsUpdate
+ .create()
+ .update("Set Preferred Email", userId, u -> u.setPreferredEmail("user@example.com"));
user = reloadUser();
requestContext.setContext(
@@ -219,8 +222,10 @@
@Test
public void noExternalIds() throws Exception {
- ExternalIdsUpdate externalIdsUpdate = externalIdsUpdateFactory.create();
- externalIdsUpdate.deleteAll(user.getAccountId());
+ Set<ExternalId> extIds = externalIds.byAccount(user.getAccountId());
+ accountsUpdate
+ .create()
+ .update("Delete External IDs", user.getAccountId(), u -> u.deleteExternalIds(extIds));
reloadUser();
TestKey key = validKeyWithSecondUserId();
@@ -233,9 +238,7 @@
checker = checkerFactory.create().setStore(store).disableTrust();
assertProblems(
checker.check(key.getPublicKey()), Status.BAD, "Key is not associated with any users");
- externalIdsUpdate.insert(
- ExternalId.create(toExtIdKey(key.getPublicKey()), user.getAccountId()));
- reloadUser();
+ insertExtId(ExternalId.create(toExtIdKey(key.getPublicKey()), user.getAccountId()));
assertProblems(checker.check(key.getPublicKey()), Status.BAD, "No identities found for user");
}
@@ -402,7 +405,7 @@
cb.setCommitter(ident);
assertThat(store.save(cb)).isAnyOf(NEW, FAST_FORWARD, FORCED);
- externalIdsUpdateFactory.create().insert(newExtIds);
+ accountsUpdate.create().update("Add External IDs", id, u -> u.addExternalIds(newExtIds));
}
private TestKey add(TestKey k, IdentifiedUser user) throws Exception {
@@ -425,9 +428,13 @@
}
private void addExternalId(String scheme, String id, String email) throws Exception {
- externalIdsUpdateFactory
+ insertExtId(ExternalId.createWithEmail(scheme, id, user.getAccountId(), email));
+ }
+
+ private void insertExtId(ExternalId extId) throws Exception {
+ accountsUpdate
.create()
- .insert(ExternalId.createWithEmail(scheme, id, user.getAccountId(), email));
+ .update("Add External ID", extId.accountId(), u -> u.addExternalId(extId));
reloadUser();
}
}
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 5bdfe39..a228ed6 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -42,6 +42,7 @@
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/project/testing:project-test-util",
+ "//java/com/google/gerrit/server/restapi",
"//java/com/google/gerrit/server/schema",
"//java/com/google/gerrit/testing:gerrit-test-util",
"//java/org/eclipse/jgit:server",
diff --git a/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java b/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
index 6fe48dc..bcba665 100644
--- a/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
+++ b/javatests/com/google/gerrit/server/config/ListCapabilitiesTest.java
@@ -22,7 +22,8 @@
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.server.config.ListCapabilities.CapabilityInfo;
+import com.google.gerrit.server.restapi.config.ListCapabilities;
+import com.google.gerrit.server.restapi.config.ListCapabilities.CapabilityInfo;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 634f25e..77c139e 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -46,6 +46,7 @@
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.InternalAccountUpdate;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllProjectsName;
@@ -417,7 +418,7 @@
md.getCommitBuilder().setCommitter(ident);
AccountConfig accountConfig = new AccountConfig(null, accountId);
accountConfig.load(repo);
- accountConfig.getLoadedAccount().get().setFullName(newName);
+ accountConfig.setAccountUpdate(InternalAccountUpdate.builder().setFullName(newName).build());
accountConfig.commit(md);
}
@@ -541,11 +542,10 @@
accountsUpdate
.create()
.update(
+ "Update Test Account",
id,
- a -> {
- a.setFullName(fullName);
- a.setPreferredEmail(email);
- a.setActive(active);
+ u -> {
+ u.setFullName(fullName).setPreferredEmail(email).setActive(active);
});
return id;
}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 49a7e3e..0b7f94a 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -84,7 +84,6 @@
import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.change.PatchSetInserter;
@@ -172,7 +171,6 @@
@Inject protected ThreadLocalRequestContext requestContext;
@Inject protected ProjectCache projectCache;
@Inject protected MetaDataUpdate.Server metaDataUpdateFactory;
- @Inject protected ExternalIdsUpdate.Server externalIdsUpdate;
// Only for use in setting up/tearing down injector; other users should use schemaFactory.
@Inject private InMemoryDatabase inMemoryDatabase;
@@ -223,8 +221,12 @@
userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
String email = "user@example.com";
- externalIdsUpdate.create().insert(ExternalId.createEmail(userId, email));
- accountsUpdate.create().update(userId, a -> a.setPreferredEmail(email));
+ accountsUpdate
+ .create()
+ .update(
+ "Add Email",
+ userId,
+ u -> u.addExternalId(ExternalId.createEmail(userId, email)).setPreferredEmail(email));
user = userFactory.create(userId);
requestContext.setContext(newRequestContext(userId));
}
@@ -2729,11 +2731,10 @@
accountsUpdate
.create()
.update(
+ "Update Test Account",
id,
- a -> {
- a.setFullName(fullName);
- a.setPreferredEmail(email);
- a.setActive(active);
+ u -> {
+ u.setFullName(fullName).setPreferredEmail(email).setActive(active);
});
return id;
}
diff --git a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index f3aaa6b..7dfc08c 100644
--- a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -409,11 +409,10 @@
accountsUpdate
.create()
.update(
+ "Update Test Account",
id,
- a -> {
- a.setFullName(fullName);
- a.setPreferredEmail(email);
- a.setActive(active);
+ u -> {
+ u.setFullName(fullName).setPreferredEmail(email).setActive(active);
});
return id;
}
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index fddf4de..e450b42 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -264,11 +264,10 @@
accountsUpdate
.create()
.update(
+ "Update Test Account",
id,
- a -> {
- a.setFullName(fullName);
- a.setPreferredEmail(email);
- a.setActive(active);
+ u -> {
+ u.setFullName(fullName).setPreferredEmail(email).setActive(active);
});
return id;
}
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index e737f47..6ad5859 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,8 +1,8 @@
load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_LOCAL", "MAVEN_CENTRAL", "maven_jar")
-_JGIT_VERS = "4.9.1.201712030800-r.66-gf8eff40ca"
+_JGIT_VERS = "4.9.2.201712150930-r.175-gd8a24ac1c"
-_DOC_VERS = "4.9.1.201712030800-r" # Set to _JGIT_VERS unless using a snapshot
+_DOC_VERS = "4.9.2.201712150930-r" # Set to _JGIT_VERS unless using a snapshot
JGIT_DOC_URL = "http://download.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
@@ -26,28 +26,28 @@
name = "jgit_lib",
artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "0b974aa9c6c929c39c506ab2705d42f3d7da84c7",
- src_sha1 = "8884bef0415e092563b60b2167adbb09ac19d131",
+ sha1 = "4286555f5851fbfcf0ff89ec884f7f806b0c7e37",
+ src_sha1 = "5e38b7e7936ebbd778914dc4f9d76d245a5a4518",
unsign = True,
)
maven_jar(
name = "jgit_servlet",
artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "19c6bcdf5e0ba1907f6eeb18ae02d6ae04f630e3",
+ sha1 = "da0d2c7a048cc213274cd06a5baf277c85ea152e",
unsign = True,
)
maven_jar(
name = "jgit_archive",
artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "0063dde3c017e05ee4e84ae16c97cb8817b91782",
+ sha1 = "1cd91bedf8b591626d341c2d896181ddba5f9aa9",
)
maven_jar(
name = "jgit_junit",
artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
repository = _JGIT_REPO,
- sha1 = "a9cb1e58df9bd876a2e81130f61a9bac0f182520",
+ sha1 = "5b7cc1aa0ba062ad587b6daa64743b704b997f74",
unsign = True,
)
diff --git a/plugins/BUILD b/plugins/BUILD
index 92a4c10..7c3fdd8 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -47,6 +47,7 @@
"//lib/guice:multibindings",
"//lib/httpcomponents:httpclient",
"//lib/httpcomponents:httpcore",
+ "//lib/jackson:jackson-core",
"//lib/jgit/org.eclipse.jgit.http.server:jgit-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/log:api",
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
index 84f73ff..0bc9a99 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
@@ -83,11 +83,7 @@
_getNameForUser(account) {
const accountId = account._account_id ? ' (' +
account._account_id + ')' : '';
- if (account && account.username) {
- return account.username + accountId;
- } else if (account && account.name) {
- return account.name + accountId;
- }
+ return this._getNameForMember(account) + accountId;
},
_getNameForMember(account) {
@@ -95,6 +91,8 @@
return account.name;
} else if (account && account.username) {
return account.username;
+ } else if (account && account.email) {
+ return account.email.split('@')[0];
}
},
});
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
index e437f7d..b179718 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
@@ -47,8 +47,8 @@
_account_id: 12,
},
};
- assert.deepEqual(
- element._getNameForMember(account.member, false), 'test-user');
+ assert.equal(element._getNameForMember(account.member, false),
+ 'test-user');
account = {
member: {
@@ -56,8 +56,14 @@
_account_id: 12,
},
};
- assert.deepEqual(
- element._getNameForMember(account.member), 'test-name');
+ assert.equal(element._getNameForMember(account.member), 'test-name');
+
+ account = {
+ user: {
+ email: 'test-email@gmail.com',
+ },
+ };
+ assert.equal(element._getNameForMember(account.user), 'test-email');
});
});
@@ -69,8 +75,7 @@
_account_id: 12,
},
};
- assert.deepEqual(
- element._getNameForUser(account.user), 'test-user (12)');
+ assert.equal(element._getNameForUser(account.user), 'test-user (12)');
account = {
user: {
@@ -78,8 +83,15 @@
_account_id: 12,
},
};
- assert.deepEqual(
- element._getNameForUser(account.user), 'test-name (12)');
+ assert.equal(element._getNameForUser(account.user), 'test-name (12)');
+
+ account = {
+ user: {
+ email: 'test-email@gmail.com',
+ _account_id: 12,
+ },
+ };
+ assert.equal(element._getNameForUser(account.user), 'test-email (12)');
});
test('test _account_id not present', () => {
@@ -88,14 +100,21 @@
username: 'test-user',
},
};
- assert.deepEqual(element._getNameForUser(account.user), 'test-user');
+ assert.equal(element._getNameForUser(account.user), 'test-user');
account = {
user: {
name: 'test-name',
},
};
- assert.deepEqual(element._getNameForUser(account.user), 'test-name');
+ assert.equal(element._getNameForUser(account.user), 'test-name');
+
+ account = {
+ user: {
+ email: 'test-email@gmail.com',
+ },
+ };
+ assert.equal(element._getNameForUser(account.user), 'test-email');
});
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index a947af4..3189cd4 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -110,6 +110,11 @@
align-items: center;
display: flex;
}
+ .fileViewActions gr-button {
+ --gr-button: {
+ padding: 2px 4px;
+ }
+ }
.fileViewActions > *:not(:last-child) {
margin-right: 5px;
}
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 c969c51..8ce0e9d 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
@@ -218,6 +218,9 @@
.status {
justify-content: flex-start;
}
+ .reviewed {
+ display: none;
+ }
.comments {
min-width: initial;
}
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 4c1326a..06799ff3 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
@@ -1333,19 +1333,12 @@
});
suite('editLoaded behavior', () => {
- const isVisible = el => {
- assert.ok(el);
- return getComputedStyle(el).getPropertyValue('display') !== 'none';
- };
-
test('reviewed checkbox', () => {
const alertStub = sandbox.stub();
const saveReviewStub = sandbox.stub(element, '_saveReviewedState');
element.addEventListener('show-alert', alertStub);
element.editLoaded = false;
- // Reviewed checkbox should be shown.
- assert.isTrue(isVisible(element.$$('.reviewed')));
MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
assert.isFalse(alertStub.called);
assert.isTrue(saveReviewStub.calledOnce);
@@ -1353,7 +1346,6 @@
element.editLoaded = true;
flushAsynchronousOperations();
- assert.isFalse(isVisible(element.$$('.reviewed')));
MockInteractions.pressAndReleaseKeyOn(element, 82, null, 'r');
assert.isTrue(alertStub.called);
assert.isTrue(saveReviewStub.calledOnce);
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
index 968766d..cf743a4 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
@@ -66,7 +66,7 @@
},
_computeBlankItems(permittedLabels, label, side) {
- if (!permittedLabels || !permittedLabels[label] ||
+ if (!permittedLabels || !permittedLabels[label] || !this.labelValues ||
!Object.keys(this.labelValues).length) {
return [];
}
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index f43ff91..8ef8b7b 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -45,10 +45,7 @@
native.java_binary(
name = '%s__non_stamped' % name,
- deploy_manifest_lines = manifest_entries + [
- "Gerrit-ApiType: plugin",
- "Implementation-Vendor: Gerrit Code Review",
- ],
+ deploy_manifest_lines = manifest_entries + ["Gerrit-ApiType: plugin"],
main_class = 'Dummy',
runtime_deps = [
':%s__plugin' % name,