Merge "Add directory listing to server.go"
diff --git a/Documentation/config-accounts.txt b/Documentation/config-accounts.txt
index b1ce1ce..b4a5cef 100644
--- a/Documentation/config-accounts.txt
+++ b/Documentation/config-accounts.txt
@@ -7,10 +7,10 @@
link:note-db.html[NoteDb].
The account data consists of a sequence number (account ID), account
-properties (full name, preferred email, registration date, status,
-inactive flag), preferences (general, diff and edit preferences),
-project watches, SSH keys, external IDs, starred changes and reviewed
-flags.
+properties (full name, display name, preferred email, registration
+date, status, inactive flag), preferences (general, diff and edit
+preferences), project watches, SSH keys, external IDs, starred changes
+and reviewed flags.
Most account data is stored in a special link:#all-users[All-Users]
repository, which has one branch per user. Within the user branch there
@@ -155,6 +155,7 @@
----
[account]
fullName = John Doe
+ displayName = John
preferredEmail = john.doe@example.com
status = OOO
active = false
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index bb83d95..28ecc97 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -58,8 +58,8 @@
[[details]]
--
-* `DETAILS`: Includes full name, preferred email, username, avatars,
-status and state for each account.
+* `DETAILS`: Includes full name, preferred email, username, display
+name, avatars, status and state for each account.
--
[[all-emails]]
@@ -99,12 +99,14 @@
"name": "John Doe",
"email": "john.doe@example.com",
"username": "john"
+ "display_name": "John D"
},
{
"_account_id": 1001439,
"name": "John Smith",
"email": "john.smith@example.com",
"username": "jsmith"
+ "display_name": "Johnny"
},
]
----
@@ -134,6 +136,7 @@
"name": "John Doe",
"email": "john.doe@example.com",
"username": "john"
+ "display_name": "Super John"
}
----
@@ -155,6 +158,7 @@
{
"name": "John Doe",
+ "display_name": "Super John",
"email": "john.doe@example.com",
"ssh_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw==",
"http_password": "19D9aIn7zePb",
@@ -208,6 +212,7 @@
"name": "John Doe",
"email": "john.doe@example.com",
"username": "john"
+ "display_name": "Super John"
}
----
@@ -401,6 +406,27 @@
As response the new username is returned.
+[[set-display-name]]
+=== Set Display Name
+--
+'PUT /accounts/link:#account-id[\{account-id\}]/displayname'
+--
+
+The new display name must be provided in the request body inside
+a link:#display-name-input[DisplayNameInput] entity.
+
+.Request
+----
+ PUT /accounts/self/displayname HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "display_name": "John"
+ }
+----
+
+As response the new display name is returned.
+
[[get-active]]
=== Get Active
--
@@ -2228,6 +2254,11 @@
See option link:rest-api-changes.html#detailed-accounts[
DETAILED_ACCOUNTS] for change queries +
and option link:#details[DETAILS] for account queries.
+|`display_name` |optional|The display name of the user. +
+Only set if detailed account information is requested. +
+See option link:rest-api-changes.html#detailed-accounts[
+DETAILED_ACCOUNTS] for change queries +
+and option link:#details[DETAILS] for account queries.
|`email` |optional|
The email address the user prefers to be contacted through. +
Only set if detailed account information is requested. +
@@ -2268,6 +2299,7 @@
|`username` |optional|
The user name. If provided, must match the user name from the URL.
|`name` |optional|The full name of the user.
+|`display_name` |optional|The display name of the user.
|`email` |optional|The email address of the user.
|`ssh_key` |optional|The public SSH key of the user.
|`http_password`|optional|The HTTP password of the user.
@@ -2870,6 +2902,17 @@
|`username` |The new username of the account.
|=======================
+[[display-name-input]]
+=== DisplayNameInput
+The `DisplayNameInput` entity contains information for setting the
+display name for an account.
+
+[options="header",cols="1,6"]
+|=======================
+|Field Name |Description
+|`display_name` |The new display name of the account.
+|=======================
+
[[project-watch-info]]
=== ProjectWatchInfo
The `WatchedProjectsInfo` entity contains information about a project watch
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index a99c3bb..5086e19 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -503,8 +503,9 @@
}
----
-If the group creation fails because the name is already in use the
-response is "`409 Conflict`".
+If the group creation fails because the name is already in use, or the
+UUID was specified and the UUID is already in use, the response is
+"`409 Conflict`".
[[get-group-detail]]
=== Get Group Detail
@@ -1591,6 +1592,7 @@
|Field Name ||Description
|`name` |optional|The name of the group (not encoded). +
If set, must match the group name in the URL.
+|`uuid` |optional|The UUID of the group.
|`description` |optional|The description of the group.
|`visible_to_all`|optional|
Whether the group is visible to all registered users. +
diff --git a/java/com/google/gerrit/entities/Account.java b/java/com/google/gerrit/entities/Account.java
index b2e0017..fd3df97 100644
--- a/java/com/google/gerrit/entities/Account.java
+++ b/java/com/google/gerrit/entities/Account.java
@@ -129,6 +129,10 @@
@Nullable
public abstract String fullName();
+ /** Optional display name of the user to be shown in the UI. */
+ @Nullable
+ public abstract String displayName();
+
/** Email address the user prefers to be contacted through. */
@Nullable
public abstract String preferredEmail();
@@ -236,6 +240,11 @@
public abstract Builder setFullName(String fullName);
@Nullable
+ public abstract String displayName();
+
+ public abstract Builder setDisplayName(String displayName);
+
+ @Nullable
public abstract String preferredEmail();
public abstract Builder setPreferredEmail(String preferredEmail);
diff --git a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
index 67d6fd2..6df9889 100644
--- a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
+++ b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -87,6 +87,8 @@
void setStatus(String status) throws RestApiException;
+ void setDisplayName(String displayName) throws RestApiException;
+
List<SshKeyInfo> listSshKeys() throws RestApiException;
SshKeyInfo addSshKey(String key) throws RestApiException;
@@ -269,6 +271,11 @@
}
@Override
+ public void setDisplayName(String displayName) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public List<SshKeyInfo> listSshKeys() throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/api/accounts/AccountInput.java b/java/com/google/gerrit/extensions/api/accounts/AccountInput.java
index 259838b..2bcce30 100644
--- a/java/com/google/gerrit/extensions/api/accounts/AccountInput.java
+++ b/java/com/google/gerrit/extensions/api/accounts/AccountInput.java
@@ -20,6 +20,7 @@
public class AccountInput {
@DefaultInput public String username;
public String name;
+ public String displayName;
public String email;
public String sshKey;
public String httpPassword;
diff --git a/java/com/google/gerrit/extensions/api/accounts/DisplayNameInput.java b/java/com/google/gerrit/extensions/api/accounts/DisplayNameInput.java
new file mode 100644
index 0000000..16e7afc
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/accounts/DisplayNameInput.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.accounts;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class DisplayNameInput {
+ public @DefaultInput String displayName;
+
+ public DisplayNameInput(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public DisplayNameInput() {}
+}
diff --git a/java/com/google/gerrit/extensions/api/groups/GroupInput.java b/java/com/google/gerrit/extensions/api/groups/GroupInput.java
index ab38434..30af08f 100644
--- a/java/com/google/gerrit/extensions/api/groups/GroupInput.java
+++ b/java/com/google/gerrit/extensions/api/groups/GroupInput.java
@@ -18,6 +18,7 @@
public class GroupInput {
public String name;
+ public String uuid;
public String description;
public Boolean visibleToAll;
public String ownerId;
diff --git a/java/com/google/gerrit/extensions/common/AccountInfo.java b/java/com/google/gerrit/extensions/common/AccountInfo.java
index d39d99a..803225e 100644
--- a/java/com/google/gerrit/extensions/common/AccountInfo.java
+++ b/java/com/google/gerrit/extensions/common/AccountInfo.java
@@ -33,6 +33,14 @@
/** The full name of the user. */
public String name;
+ /**
+ * The display name of the user. This allows users to control how their name is displayed in the
+ * UI. It will likely be unset for most users. Host admins will just choose a default (full name,
+ * user name, first name, ...) for all users, and this account property is just a way to opt out
+ * of the host wide default strategy of choosing the display name.
+ */
+ public String displayName;
+
/** The preferred email address of the user. */
public String email;
@@ -73,6 +81,7 @@
AccountInfo accountInfo = (AccountInfo) o;
return Objects.equals(_accountId, accountInfo._accountId)
&& Objects.equals(name, accountInfo.name)
+ && Objects.equals(displayName, accountInfo.displayName)
&& Objects.equals(email, accountInfo.email)
&& Objects.equals(secondaryEmails, accountInfo.secondaryEmails)
&& Objects.equals(username, accountInfo.username)
@@ -88,6 +97,7 @@
return MoreObjects.toStringHelper(this)
.add("id", _accountId)
.add("name", name)
+ .add("displayname", displayName)
.add("email", email)
.add("username", username)
.toString();
@@ -96,7 +106,15 @@
@Override
public int hashCode() {
return Objects.hash(
- _accountId, name, email, secondaryEmails, username, avatars, _moreAccounts, status);
+ _accountId,
+ name,
+ displayName,
+ email,
+ secondaryEmails,
+ username,
+ avatars,
+ _moreAccounts,
+ status);
}
protected AccountInfo() {}
diff --git a/java/com/google/gerrit/server/account/AccountConfig.java b/java/com/google/gerrit/server/account/AccountConfig.java
index 5a1bb8a..afe8c7b 100644
--- a/java/com/google/gerrit/server/account/AccountConfig.java
+++ b/java/com/google/gerrit/server/account/AccountConfig.java
@@ -190,6 +190,7 @@
InternalAccountUpdate.builder()
.setActive(account.isActive())
.setFullName(account.fullName())
+ .setDisplayName(account.displayName())
.setPreferredEmail(account.preferredEmail())
.setStatus(account.status())
.build());
diff --git a/java/com/google/gerrit/server/account/AccountProperties.java b/java/com/google/gerrit/server/account/AccountProperties.java
index 4f29b25..5ae5567 100644
--- a/java/com/google/gerrit/server/account/AccountProperties.java
+++ b/java/com/google/gerrit/server/account/AccountProperties.java
@@ -33,6 +33,7 @@
* [account]
* active = false
* fullName = John Doe
+ * displayName = John
* preferredEmail = john.doe@foo.com
* status = Overloaded with reviews
* </pre>
@@ -51,6 +52,7 @@
public static final String ACCOUNT = "account";
public static final String KEY_ACTIVE = "active";
public static final String KEY_FULL_NAME = "fullName";
+ public static final String KEY_DISPLAY_NAME = "displayName";
public static final String KEY_PREFERRED_EMAIL = "preferredEmail";
public static final String KEY_STATUS = "status";
@@ -91,6 +93,7 @@
Account.Builder accountBuilder = Account.builder(accountId, registeredOn);
accountBuilder.setActive(accountConfig.getBoolean(ACCOUNT, null, KEY_ACTIVE, true));
accountBuilder.setFullName(get(accountConfig, KEY_FULL_NAME));
+ accountBuilder.setDisplayName(get(accountConfig, KEY_DISPLAY_NAME));
String preferredEmail = get(accountConfig, KEY_PREFERRED_EMAIL);
accountBuilder.setPreferredEmail(preferredEmail);
@@ -109,6 +112,9 @@
accountUpdate.getActive().ifPresent(active -> setActive(cfg, active));
accountUpdate.getFullName().ifPresent(fullName -> set(cfg, KEY_FULL_NAME, fullName));
accountUpdate
+ .getDisplayName()
+ .ifPresent(displayName -> set(cfg, KEY_DISPLAY_NAME, displayName));
+ 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/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 125edfe..8b2a081 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -83,14 +83,14 @@
*
* <p>The account updates are written to NoteDb. In NoteDb accounts are represented as user branches
* in the {@code All-Users} repository. Optionally a user branch can contain a 'account.config' file
- * that stores account properties, such as full name, preferred email, status and the active flag.
- * The timestamp of the first commit on a user branch denotes the registration date. The initial
- * commit on the user branch may be empty (since having an 'account.config' is optional). See {@link
- * AccountConfig} for details of the 'account.config' file format. In addition the user branch can
- * contain a 'preferences.config' config file to store preferences (see {@link StoredPreferences})
- * and a 'watch.config' config file to store project watches (see {@link ProjectWatches}). External
- * IDs are stored separately in the {@code refs/meta/external-ids} notes branch (see {@link
- * ExternalIdNotes}).
+ * that stores account properties, such as full name, display name, preferred email, status and the
+ * active flag. The timestamp of the first commit on a user branch denotes the registration date.
+ * The initial commit on the user branch may be empty (since having an 'account.config' is
+ * optional). See {@link AccountConfig} for details of the 'account.config' file format. In addition
+ * the user branch can contain a 'preferences.config' config file to store preferences (see {@link
+ * StoredPreferences}) and a 'watch.config' config file to store project watches (see {@link
+ * ProjectWatches}). External IDs are stored separately in the {@code refs/meta/external-ids} notes
+ * branch (see {@link ExternalIdNotes}).
*
* <p>On updating an account the account is evicted from the account cache and reindexed. The
* eviction from the account cache and the reindexing is done by the {@link ReindexAfterRefUpdate}
diff --git a/java/com/google/gerrit/server/account/CreateGroupArgs.java b/java/com/google/gerrit/server/account/CreateGroupArgs.java
index 2c59a08..2a764cc 100644
--- a/java/com/google/gerrit/server/account/CreateGroupArgs.java
+++ b/java/com/google/gerrit/server/account/CreateGroupArgs.java
@@ -20,6 +20,7 @@
public class CreateGroupArgs {
private AccountGroup.NameKey groupName;
+ public AccountGroup.UUID uuid;
public String groupDescription;
public boolean visibleToAll;
public AccountGroup.UUID ownerGroupUuid;
diff --git a/java/com/google/gerrit/server/account/InternalAccountUpdate.java b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
index cf77a75..bfbe917 100644
--- a/java/com/google/gerrit/server/account/InternalAccountUpdate.java
+++ b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
@@ -18,6 +18,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
@@ -61,6 +62,15 @@
public abstract Optional<String> getFullName();
/**
+ * Returns the new value for the display name.
+ *
+ * @return the new value for the display name, {@code Optional#empty()} if the display name is not
+ * being updated, {@code Optional#of("")} if the display name is unset, the wrapped value is
+ * never {@code null}
+ */
+ public abstract Optional<String> getDisplayName();
+
+ /**
* Returns the new value for the preferred email.
*
* @return the new value for the preferred email, {@code Optional#empty()} if the preferred email
@@ -166,25 +176,30 @@
* 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);
+ public abstract Builder setFullName(@Nullable String fullName);
+
+ /**
+ * Sets a new display name for the account.
+ *
+ * @param displayName the new display name, if {@code null} or empty string the display name is
+ * unset
+ */
+ public abstract Builder setDisplayName(@Nullable String displayName);
/**
* 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);
+ public abstract Builder setPreferredEmail(@Nullable 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);
@@ -192,9 +207,8 @@
* 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);
+ public abstract Builder setStatus(@Nullable String status);
/**
* Returns a builder for the set of created external IDs.
@@ -487,6 +501,12 @@
}
@Override
+ public Builder setDisplayName(String displayName) {
+ delegate.setDisplayName(Strings.nullToEmpty(displayName));
+ return this;
+ }
+
+ @Override
public Builder setPreferredEmail(String preferredEmail) {
delegate.setPreferredEmail(Strings.nullToEmpty(preferredEmail));
return this;
diff --git a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 7fa9767..4f85412 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -22,6 +22,7 @@
import com.google.gerrit.extensions.api.accounts.AgreementInput;
import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput;
import com.google.gerrit.extensions.api.accounts.DeletedDraftCommentInfo;
+import com.google.gerrit.extensions.api.accounts.DisplayNameInput;
import com.google.gerrit.extensions.api.accounts.EmailApi;
import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
@@ -76,6 +77,7 @@
import com.google.gerrit.server.restapi.account.PostWatchedProjects;
import com.google.gerrit.server.restapi.account.PutActive;
import com.google.gerrit.server.restapi.account.PutAgreement;
+import com.google.gerrit.server.restapi.account.PutDisplayName;
import com.google.gerrit.server.restapi.account.PutHttpPassword;
import com.google.gerrit.server.restapi.account.PutName;
import com.google.gerrit.server.restapi.account.PutStatus;
@@ -134,6 +136,7 @@
private final DeleteExternalIds deleteExternalIds;
private final DeleteDraftComments deleteDraftComments;
private final PutStatus putStatus;
+ private final PutDisplayName putDisplayName;
private final GetGroups getGroups;
private final EmailApiImpl.Factory emailApi;
private final PutName putName;
@@ -177,6 +180,7 @@
DeleteExternalIds deleteExternalIds,
DeleteDraftComments deleteDraftComments,
PutStatus putStatus,
+ PutDisplayName putDisplayName,
GetGroups getGroups,
EmailApiImpl.Factory emailApi,
PutName putName,
@@ -219,6 +223,7 @@
this.deleteExternalIds = deleteExternalIds;
this.deleteDraftComments = deleteDraftComments;
this.putStatus = putStatus;
+ this.putDisplayName = putDisplayName;
this.getGroups = getGroups;
this.emailApi = emailApi;
this.putName = putName;
@@ -477,6 +482,16 @@
}
@Override
+ public void setDisplayName(String displayName) throws RestApiException {
+ DisplayNameInput in = new DisplayNameInput(displayName);
+ try {
+ putDisplayName.apply(account, in);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot set display name", e);
+ }
+ }
+
+ @Override
public List<SshKeyInfo> listSshKeys() throws RestApiException {
try {
return getSshKeys.apply(account).value();
diff --git a/java/com/google/gerrit/server/restapi/account/Module.java b/java/com/google/gerrit/server/restapi/account/Module.java
index f41764d..51055b8 100644
--- a/java/com/google/gerrit/server/restapi/account/Module.java
+++ b/java/com/google/gerrit/server/restapi/account/Module.java
@@ -53,6 +53,7 @@
delete(ACCOUNT_KIND, "name").to(PutName.class);
get(ACCOUNT_KIND, "status").to(GetStatus.class);
put(ACCOUNT_KIND, "status").to(PutStatus.class);
+ put(ACCOUNT_KIND, "displayname").to(PutDisplayName.class);
get(ACCOUNT_KIND, "username").to(GetUsername.class);
put(ACCOUNT_KIND, "username").to(PutUsername.class);
get(ACCOUNT_KIND, "active").to(GetActive.class);
diff --git a/java/com/google/gerrit/server/restapi/account/PutDisplayName.java b/java/com/google/gerrit/server/restapi/account/PutDisplayName.java
new file mode 100644
index 0000000..557e660
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/account/PutDisplayName.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.restapi.account;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.accounts.DisplayNameInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+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.IdentifiedUser;
+import com.google.gerrit.server.ServerInitiated;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+/**
+ * REST endpoint to set the display name of an account.
+ *
+ * <p>This REST endpoint handles {@code PUT /accounts/<account-identifier>/displayname} requests.
+ *
+ * <p>The display name is a free-form text that a user can set for their own account. It defines how
+ * the user's name will be rendered in the UI in most screens. It is optional, and if not set, then
+ * the UI falls back to whatever is configured as the default display name, e.g. the full name.
+ */
+@Singleton
+public class PutDisplayName implements RestModifyView<AccountResource, DisplayNameInput> {
+ private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
+ private final Provider<AccountsUpdate> accountsUpdateProvider;
+
+ @Inject
+ PutDisplayName(
+ Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
+ @ServerInitiated Provider<AccountsUpdate> accountsUpdateProvider) {
+ this.self = self;
+ this.permissionBackend = permissionBackend;
+ this.accountsUpdateProvider = accountsUpdateProvider;
+ }
+
+ @Override
+ public Response<String> apply(AccountResource rsrc, @Nullable DisplayNameInput input)
+ throws AuthException, ResourceNotFoundException, IOException, PermissionBackendException,
+ ConfigInvalidException {
+ IdentifiedUser user = rsrc.getUser();
+ if (!self.get().hasSameAccountId(user)) {
+ permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
+ }
+ if (input == null) {
+ input = new DisplayNameInput();
+ }
+
+ String newDisplayName = input.displayName;
+ AccountState accountState =
+ accountsUpdateProvider
+ .get()
+ .update(
+ "Set Display Name via API",
+ user.getAccountId(),
+ u -> u.setDisplayName(newDisplayName))
+ .orElseThrow(() -> new ResourceNotFoundException("account not found"));
+ return Strings.isNullOrEmpty(accountState.account().displayName())
+ ? Response.none()
+ : Response.ok(accountState.account().displayName());
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index 531d350..e5a1478 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -138,6 +138,16 @@
AccountGroup.UUID ownerUuid = owner(input);
CreateGroupArgs args = new CreateGroupArgs();
args.setGroupName(name);
+ args.uuid = Strings.isNullOrEmpty(input.uuid) ? null : AccountGroup.UUID.parse(input.uuid);
+ if (args.uuid != null) {
+ if (!args.uuid.isInternalGroup()) {
+ throw new BadRequestException(String.format("invalid group UUID '%s'", args.uuid.get()));
+ }
+ if (groupCache.get(args.uuid).isPresent()) {
+ throw new ResourceConflictException(
+ String.format("group with UUID '%s' already exists", args.uuid.get()));
+ }
+ }
args.groupDescription = Strings.emptyToNull(input.description);
args.visibleToAll = MoreObjects.firstNonNull(input.visibleToAll, defaultVisibleToAll);
args.ownerGroupUuid = ownerUuid;
@@ -196,9 +206,11 @@
AccountGroup.Id groupId = AccountGroup.id(sequences.nextGroupId());
AccountGroup.UUID uuid =
- GroupUuid.make(
- createGroupArgs.getGroupName(),
- self.get().newCommitterIdent(serverIdent.getWhen(), serverIdent.getTimeZone()));
+ MoreObjects.firstNonNull(
+ createGroupArgs.uuid,
+ GroupUuid.make(
+ createGroupArgs.getGroupName(),
+ self.get().newCommitterIdent(serverIdent.getWhen(), serverIdent.getTimeZone())));
InternalGroupCreation groupCreation =
InternalGroupCreation.builder()
.setGroupUUID(uuid)
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 988580e..17a0ea6 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -415,6 +415,43 @@
}
@Test
+ public void createGroupWithUuid() throws Exception {
+ AccountGroup.UUID uuid = AccountGroup.UUID.parse("4eb25d1cca562f53b9356117f33840706a36a349");
+ GroupInput input = new GroupInput();
+ input.uuid = uuid.get();
+ input.name = name("new-group");
+ GroupInfo info = gApi.groups().create(input).get();
+ assertThat(info.name).isEqualTo(input.name);
+ assertThat(info.id).isEqualTo(input.uuid);
+ }
+
+ @Test
+ public void createGroupWithExistingUuid_Conflict() throws Exception {
+ GroupInfo existingGroup = gApi.groups().create(name("new-group")).get();
+ GroupInput input = new GroupInput();
+ input.uuid = existingGroup.id;
+ input.name = name("another-new-group");
+ ResourceConflictException thrown =
+ assertThrows(ResourceConflictException.class, () -> gApi.groups().create(input).get());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(String.format("group with UUID '%s' already exists", input.uuid));
+ }
+
+ @Test
+ public void createGroupWithInvalidUuid_BadRequest() throws Exception {
+ AccountGroup.UUID uuid = AccountGroup.UUID.parse("foo:bar");
+ GroupInput input = new GroupInput();
+ input.uuid = uuid.get();
+ input.name = name("new-group");
+ BadRequestException thrown =
+ assertThrows(BadRequestException.class, () -> gApi.groups().create(input).get());
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(String.format("invalid group UUID '%s'", input.uuid));
+ }
+
+ @Test
public void createGroupWithProperties() throws Exception {
GroupInput in = new GroupInput();
in.name = name("newGroup");
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index cbfc728..8e396f1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -75,6 +75,12 @@
header {
padding: var(--spacing-s) var(--spacing-l);
}
+ .changeNumberColon {
+ color: transparent;
+ }
+ .headerSubject {
+ margin-right: var(--spacing-m);
+ }
.patchRangeLeft {
align-items: center;
display: flex;
@@ -215,16 +221,15 @@
>
<header>
<h3>
- <a href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]">
- [[_changeNum]]</a><span>:</span>
- <span>[[_change.subject]]</span>
- <span class="dash">—</span>
+ <a href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]">[[_changeNum]]</a><!--
+ --><span class="changeNumberColon">:</span>
+ <span class="headerSubject">[[_change.subject]]</span>
<input id="reviewed"
class="reviewed hideOnEdit"
type="checkbox"
on-change="_handleReviewedChange"
- hidden$="[[!_loggedIn]]" hidden>
- <div class="jumpToFileContainer">
+ hidden$="[[!_loggedIn]]" hidden><!--
+ --><div class="jumpToFileContainer">
<gr-dropdown-list
id="dropdown"
value="[[_path]]"