Merge "Make SubmoduleOp#createSubmoduleCommitMsg() less wasteful"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 04cdd14..a82370c 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -4632,8 +4632,9 @@
+
By default "Anonymous Coward" is used.
+[[secure.config]]
+== File `etc/secure.config`
-== [[secure.config]]File `etc/secure.config`
The optional file `'$site_path'/etc/secure.config` overrides (or
supplements) the settings supplied by `'$site_path'/etc/gerrit.config`.
The file should be readable only by the daemon process and can be
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 6722824..cb161af 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -517,64 +517,6 @@
}
----
-[[create-merge-patch-set-for-change]]
-=== Create Merge Patch Set For Change
---
-'POST /changes/link:#change-id[\{change-id\}]/merge'
---
-
-Update an existing change by using a
-link:#merge-patch-set-input[MergePatchSetInput] entity.
-
-Gerrit will create a merge commit based on the information of
-MergePatchSetInput and add a new patch set to the change corresponding
-to the new merge commit.
-
-.Request
-----
- POST /changes/test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc/merge HTTP/1.0
- Content-Type: application/json; charset=UTF-8
-
- {
- "subject": "Merge dev_branch into master",
- "merge": {
- "source": "refs/12/1234/1"
- }
- }
-----
-
-As response a link:#change-info[ChangeInfo] entity with current revision is
-returned that describes the resulting change.
-
-.Response
-----
- HTTP/1.1 200 OK
- Content-Disposition: attachment
- Content-Type: application/json; charset=UTF-8
-
- )]}'
- {
- "id": "test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc",
- "project": "test",
- "branch": "master",
- "hashtags": [],
- "change_id": "Ic5466d107c5294414710935a8ef3b0180fb848dc",
- "subject": "Merge dev_branch into master",
- "status": "NEW",
- "created": "2016-09-23 18:08:53.238000000",
- "updated": "2016-09-23 18:09:25.934000000",
- "submit_type": "MERGE_IF_NECESSARY",
- "mergeable": true,
- "insertions": 5,
- "deletions": 0,
- "_number": 72,
- "owner": {
- "_account_id": 1000000
- },
- "current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822"
- }
-----
-
[[get-change-detail]]
=== Get Change Detail
--
@@ -806,6 +748,97 @@
}
----
+[[create-merge-patch-set-for-change]]
+=== Create Merge Patch Set For Change
+--
+'POST /changes/link:#change-id[\{change-id\}]/merge'
+--
+
+Update an existing change by using a
+link:#merge-patch-set-input[MergePatchSetInput] entity.
+
+Gerrit will create a merge commit based on the information of
+MergePatchSetInput and add a new patch set to the change corresponding
+to the new merge commit.
+
+.Request
+----
+ POST /changes/test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc/merge HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "subject": "Merge dev_branch into master",
+ "merge": {
+ "source": "refs/12/1234/1"
+ }
+ }
+----
+
+As response a link:#change-info[ChangeInfo] entity with current revision is
+returned that describes the resulting change.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "id": "test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc",
+ "project": "test",
+ "branch": "master",
+ "hashtags": [],
+ "change_id": "Ic5466d107c5294414710935a8ef3b0180fb848dc",
+ "subject": "Merge dev_branch into master",
+ "status": "NEW",
+ "created": "2016-09-23 18:08:53.238000000",
+ "updated": "2016-09-23 18:09:25.934000000",
+ "submit_type": "MERGE_IF_NECESSARY",
+ "mergeable": true,
+ "insertions": 5,
+ "deletions": 0,
+ "_number": 72,
+ "owner": {
+ "_account_id": 1000000
+ },
+ "current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822"
+ }
+----
+
+[[set-message]]
+=== Set Commit Message
+--
+'PUT /changes/link:#change-id[\{change-id\}]/message'
+--
+
+Creates a new patch set with a new commit message.
+
+The new commit message must be provided in the request body inside a
+link:#commit-message-input[CommitMessageInput] entity and contain the change ID footer if
+link:project-configuration.html#require-change-id[Require Change-Id] was specified.
+
+.Request
+----
+ PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/message HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "message": "New Commit message \n\nChange-Id: I10394472cbd17dd12454f229e4f6de00b143a444\n"
+ }
+----
+
+.Notifications
+
+An email will be sent using the "newpatchset" template.
+
+[options="header",cols="1,1"]
+|=============================
+|WIP State |Default
+|Ready for review|owner, reviewers, CCs, stars, NEW_PATCHSETS watchers
+|Work in progress|owner
+|=============================
+
[[get-topic]]
=== Get Topic
--
@@ -930,6 +963,8 @@
Returns a list of every user ever assigned to a change, in the order in which
they were first assigned.
+[NOTE] Past assignees are only available when NoteDb is enabled.
+
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/past_assignees HTTP/1.0
@@ -2315,6 +2350,80 @@
PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/unmute HTTP/1.0
----
+[[get-hashtags]]
+=== Get Hashtags
+--
+'GET /changes/link:#change-id[\{change-id\}]/hashtags'
+--
+
+Gets the hashtags associated with a change.
+
+[NOTE] Hashtags are only available when NoteDb is enabled.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/hashtags HTTP/1.0
+----
+
+As response the change's hashtags are returned as a list of strings.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ "hashtag1",
+ "hashtag2"
+ ]
+----
+
+[[set-hashtags]]
+=== Set Hashtags
+--
+'POST /changes/link:#change-id[\{change-id\}]/hashtags'
+--
+
+Adds and/or removes hashtags from a change.
+
+[NOTE] Hashtags are only available when NoteDb is enabled.
+
+The hashtags to add or remove must be provided in the request body inside a
+link:#hashtags-input[HashtagsInput] entity.
+
+.Request
+----
+ POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/hashtags HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "add" : [
+ "hashtag3"
+ ],
+ "remove" : [
+ "hashtag2"
+ ]
+ }
+----
+
+As response the change's hashtags are returned as a list of strings.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ "hashtag1",
+ "hashtag3"
+ ]
+----
+
+
[[edit-endpoints]]
== Change Edit Endpoints
@@ -2684,78 +2793,6 @@
HTTP/1.1 204 No Content
----
-[[get-hashtags]]
-=== Get Hashtags
---
-'GET /changes/link:#change-id[\{change-id\}]/hashtags'
---
-
-Gets the hashtags associated with a change.
-
-[NOTE] Hashtags are only available when NoteDb is enabled.
-
-.Request
-----
- GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/hashtags HTTP/1.0
-----
-
-As response the change's hashtags are returned as a list of strings.
-
-.Response
-----
- HTTP/1.1 200 OK
- Content-Disposition: attachment
- Content-Type: application/json; charset=UTF-8
-
- )]}'
- [
- "hashtag1",
- "hashtag2"
- ]
-----
-
-[[set-hashtags]]
-=== Set Hashtags
---
-'POST /changes/link:#change-id[\{change-id\}]/hashtags'
---
-
-Adds and/or removes hashtags from a change.
-
-[NOTE] Hashtags are only available when NoteDb is enabled.
-
-The hashtags to add or remove must be provided in the request body inside a
-link:#hashtags-input[HashtagsInput] entity.
-
-.Request
-----
- POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/hashtags HTTP/1.0
- Content-Type: application/json; charset=UTF-8
-
- {
- "add" : [
- "hashtag3"
- ],
- "remove" : [
- "hashtag2"
- ]
- }
-----
-
-As response the change's hashtags are returned as a list of strings.
-
-.Response
-----
- HTTP/1.1 200 OK
- Content-Disposition: attachment
- Content-Type: application/json; charset=UTF-8
-
- )]}'
- [
- "hashtag1",
- "hashtag3"
- ]
-----
[[reviewer-endpoints]]
== Reviewer Endpoints
@@ -3138,38 +3175,6 @@
HTTP/1.1 204 No Content
----
-[[set-message]]
-=== Set Commit Message
---
-'PUT /changes/link:#change-id[\{change-id\}]/message'
---
-
-Creates a new patch set with a new commit message.
-
-The new commit message must be provided in the request body inside a
-link:#commit-message-input[CommitMessageInput] entity and contain the change ID footer if
-link:project-configuration.html#require-change-id[Require Change-Id] was specified.
-
-.Request
-----
- PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/message HTTP/1.0
- Content-Type: application/json; charset=UTF-8
-
- {
- "message": "New Commit message \n\nChange-Id: I10394472cbd17dd12454f229e4f6de00b143a444\n"
- }
-----
-
-.Notifications
-
-An email will be sent using the "newpatchset" template.
-
-[options="header",cols="1,1"]
-|=============================
-|WIP State |Default
-|Ready for review|owner, reviewers, CCs, stars, NEW_PATCHSETS watchers
-|Work in progress|owner
-|=============================
[[revision-endpoints]]
== Revision Endpoints
@@ -5631,6 +5636,12 @@
The name of the target branch. +
The `refs/heads/` prefix is omitted.
|`topic` |optional|The topic to which this change belongs.
+|`assignee` |optional|
+The assignee of the change as an link:rest-api-accounts.html#account-info[
+AccountInfo] entity.
+|`hashtags` |optional|
+List of hashtags that are set on the change (only populated when NoteDb
+is enabled).
|`change_id` ||The Change-Id of the change.
|`subject` ||
The subject of the change (header line of the commit message).
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 9af1836..f472b50 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -709,7 +709,8 @@
@Test
public void lookUpByPreferredEmail() throws Exception {
// create an inconsistent account that has a preferred email without external ID
- String prefEmail = "foo.preferred@example.com";
+ String prefix = "foo.preferred";
+ String prefEmail = prefix + "@example.com";
TestAccount foo = accountCreator.create(name("foo"));
accountsUpdate.create().update(db, foo.id, a -> a.setPreferredEmail(prefEmail));
@@ -717,6 +718,14 @@
ImmutableSet<Account.Id> accountsByPrefEmail = emails.getAccountFor(prefEmail);
assertThat(accountsByPrefEmail).hasSize(1);
assertThat(Iterables.getOnlyElement(accountsByPrefEmail)).isEqualTo(foo.id);
+
+ // look up by email prefix doesn't find the account
+ accountsByPrefEmail = emails.getAccountFor(prefix);
+ assertThat(accountsByPrefEmail).isEmpty();
+
+ // look up by other case doesn't find the account
+ accountsByPrefEmail = emails.getAccountFor(prefEmail.toUpperCase(Locale.US));
+ assertThat(accountsByPrefEmail).isEmpty();
}
@Test
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
index 3114cb9..e5bc194 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
@@ -15,7 +15,23 @@
package com.google.gerrit.extensions.client;
public enum ProjectState {
- ACTIVE,
- READ_ONLY,
- HIDDEN
+ ACTIVE(true, true),
+ READ_ONLY(true, false),
+ HIDDEN(false, false);
+
+ private final boolean permitsRead;
+ private final boolean permitsWrite;
+
+ ProjectState(boolean permitsRead, boolean permitsWrite) {
+ this.permitsRead = permitsRead;
+ this.permitsWrite = permitsWrite;
+ }
+
+ public boolean permitsRead() {
+ return permitsRead;
+ }
+
+ public boolean permitsWrite() {
+ return permitsWrite;
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 4f7a5ba..01aec6e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -223,8 +223,6 @@
.get()
.byPreferredEmail(email)
.stream()
- // the index query also matches prefixes, filter those out
- .filter(a -> email.equalsIgnoreCase(a.getAccount().getPreferredEmail()))
.map(AccountState::getAccount)
.findFirst();
return match.isPresent() ? auth(match.get()) : null;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
index e6d7e58..e4a1cd5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -19,6 +19,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.client.AuthType;
+import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InitStep;
@@ -29,6 +30,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gwtorm.server.SchemaFactory;
@@ -44,8 +46,9 @@
import org.apache.commons.validator.routines.EmailValidator;
public class InitAdminUser implements InitStep {
- private final ConsoleUI ui;
private final InitFlags flags;
+ private final ConsoleUI ui;
+ private final AllUsersNameOnInitProvider allUsers;
private final AccountsOnInit accounts;
private final VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory;
private final ExternalIdsOnInit externalIds;
@@ -58,6 +61,7 @@
InitAdminUser(
InitFlags flags,
ConsoleUI ui,
+ AllUsersNameOnInitProvider allUsers,
AccountsOnInit accounts,
VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory,
ExternalIdsOnInit externalIds,
@@ -65,6 +69,7 @@
GroupsOnInit groupsOnInit) {
this.flags = flags;
this.ui = ui;
+ this.allUsers = allUsers;
this.accounts = accounts;
this.authorizedKeysFactory = authorizedKeysFactory;
this.externalIds = externalIds;
@@ -128,7 +133,11 @@
AccountState as =
new AccountState(
- a, Collections.singleton(adminGroup.getGroupUUID()), extIds, new HashMap<>());
+ new AllUsersName(allUsers.get()),
+ a,
+ Collections.singleton(adminGroup.getGroupUUID()),
+ extIds,
+ new HashMap<>());
for (AccountIndex accountIndex : indexCollection.getWriteIndexes()) {
accountIndex.replace(as);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 6cb4444..16901ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -30,6 +30,7 @@
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.group.Groups;
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.query.account.InternalAccountQuery;
@@ -76,15 +77,18 @@
};
}
+ private final AllUsersName allUsersName;
private final LoadingCache<Account.Id, Optional<AccountState>> byId;
private final LoadingCache<String, Optional<Account.Id>> byName;
private final Provider<AccountIndexer> indexer;
@Inject
AccountCacheImpl(
+ AllUsersName allUsersName,
@Named(BYID_NAME) LoadingCache<Account.Id, Optional<AccountState>> byId,
@Named(BYUSER_NAME) LoadingCache<String, Optional<Account.Id>> byUsername,
Provider<AccountIndexer> indexer) {
+ this.allUsersName = allUsersName;
this.byId = byId;
this.byName = byUsername;
this.indexer = indexer;
@@ -142,16 +146,21 @@
}
}
- private static AccountState missing(Account.Id accountId) {
+ private AccountState missing(Account.Id accountId) {
Account account = new Account(accountId, TimeUtil.nowTs());
account.setActive(false);
Set<AccountGroup.UUID> anon = ImmutableSet.of();
return new AccountState(
- account, anon, Collections.emptySet(), new HashMap<ProjectWatchKey, Set<NotifyType>>());
+ allUsersName,
+ account,
+ anon,
+ Collections.emptySet(),
+ new HashMap<ProjectWatchKey, Set<NotifyType>>());
}
static class ByIdLoader extends CacheLoader<Account.Id, Optional<AccountState>> {
private final SchemaFactory<ReviewDb> schema;
+ private final AllUsersName allUsersName;
private final Accounts accounts;
private final GroupCache groupCache;
private final Groups groups;
@@ -163,6 +172,7 @@
@Inject
ByIdLoader(
SchemaFactory<ReviewDb> sf,
+ AllUsersName allUsersName,
Accounts accounts,
GroupCache groupCache,
Groups groups,
@@ -170,8 +180,9 @@
@Named(BYUSER_NAME) LoadingCache<String, Optional<Account.Id>> byUsername,
Provider<WatchConfig.Accessor> watchConfig,
ExternalIds externalIds) {
- this.accounts = accounts;
this.schema = sf;
+ this.allUsersName = allUsersName;
+ this.accounts = accounts;
this.groupCache = groupCache;
this.groups = groups;
this.loader = loader;
@@ -219,6 +230,7 @@
return Optional.of(
new AccountState(
+ allUsersName,
account,
internalGroups,
externalIds.byAccount(who),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
index 1eaf34f..dd523a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
@@ -29,6 +29,7 @@
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.config.AllUsersName;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
@@ -43,6 +44,7 @@
public static final Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION =
a -> a.getAccount().getId();
+ private final AllUsersName allUsersName;
private final Account account;
private final Set<AccountGroup.UUID> internalGroups;
private final Collection<ExternalId> externalIds;
@@ -50,10 +52,12 @@
private Cache<IdentifiedUser.PropertyKey<Object>, Object> properties;
public AccountState(
+ AllUsersName allUsersName,
Account account,
Set<AccountGroup.UUID> actualGroups,
Collection<ExternalId> externalIds,
Map<ProjectWatchKey, Set<NotifyType>> projectWatches) {
+ this.allUsersName = allUsersName;
this.account = account;
this.internalGroups = actualGroups;
this.externalIds = externalIds;
@@ -61,6 +65,10 @@
this.account.setUserName(getUserName(externalIds));
}
+ public AllUsersName getAllUsersNameForIndexing() {
+ return allUsersName;
+ }
+
/** Get the cached account metadata. */
public Account getAccount() {
return account;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
index db707a8..3e97265 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
@@ -27,7 +27,6 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.util.List;
/** Class to access accounts by email. */
@Singleton
@@ -61,15 +60,9 @@
* @see #getAccountsFor(String...)
*/
public ImmutableSet<Account.Id> getAccountFor(String email) throws IOException, OrmException {
- List<AccountState> byPreferredEmail = queryProvider.get().byPreferredEmail(email);
return Streams.concat(
externalIds.byEmail(email).stream().map(e -> e.accountId()),
- byPreferredEmail
- .stream()
- // the index query also matches prefixes and emails with other case,
- // filter those out
- .filter(a -> email.equals(a.getAccount().getPreferredEmail()))
- .map(a -> a.getAccount().getId()))
+ queryProvider.get().byPreferredEmail(email).stream().map(a -> a.getAccount().getId()))
.collect(toImmutableSet());
}
@@ -91,9 +84,6 @@
.byPreferredEmail(emails)
.entries()
.stream()
- // the index query also matches prefixes and emails with other case,
- // filter those out
- .filter(e -> e.getKey().equals(e.getValue().getAccount().getPreferredEmail()))
.forEach(e -> builder.put(e.getKey(), e.getValue().getAccount().getId()));
return builder.build();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
index 8c8da7b..ad119ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -19,6 +19,7 @@
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.Iterables;
import com.google.common.hash.Hashing;
@@ -31,6 +32,7 @@
import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@AutoValue
@@ -176,7 +178,8 @@
extId.key(), extId.accountId(), extId.email(), extId.password(), blobId);
}
- static ExternalId create(
+ @VisibleForTesting
+ public static ExternalId create(
Key key,
Account.Id accountId,
@Nullable String email,
@@ -305,6 +308,15 @@
return key().isScheme(scheme);
}
+ public byte[] toByteArray() {
+ checkState(blobId() != null, "Missing blobId in external ID %s", key().get());
+ byte[] b = new byte[2 * Constants.OBJECT_ID_STRING_LENGTH + 1];
+ key().sha1().copyTo(b, 0);
+ b[Constants.OBJECT_ID_STRING_LENGTH] = ':';
+ blobId().copyTo(b, Constants.OBJECT_ID_STRING_LENGTH + 1);
+ return b;
+ }
+
/**
* For checking if two external IDs are equals the blobId is excluded and external IDs that have
* different blob IDs but identical other fields are considered equal. This way an external ID
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index eb005af..fcd6e0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -128,7 +128,9 @@
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
+import com.google.gerrit.server.project.CreateRefControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
@@ -320,6 +322,7 @@
private final String canonicalWebUrl;
private final SubmoduleOp.Factory subOpFactory;
private final TagCache tagCache;
+ private final CreateRefControl createRefControl;
// Assisted injected fields.
private final AllRefsWatcher allRefsWatcher;
@@ -402,6 +405,7 @@
SshInfo sshInfo,
SubmoduleOp.Factory subOpFactory,
TagCache tagCache,
+ CreateRefControl createRefControl,
@Assisted ProjectControl projectControl,
@Assisted ReceivePack rp,
@Assisted AllRefsWatcher allRefsWatcher,
@@ -440,6 +444,7 @@
this.sshInfo = sshInfo;
this.subOpFactory = subOpFactory;
this.tagCache = tagCache;
+ this.createRefControl = createRefControl;
// Assisted injected fields.
this.allRefsWatcher = allRefsWatcher;
@@ -524,7 +529,7 @@
try {
parseCommands(commands);
- } catch (PermissionBackendException err) {
+ } catch (PermissionBackendException | NoSuchProjectException | IOException err) {
for (ReceiveCommand cmd : actualCommands) {
if (cmd.getResult() == NOT_ATTEMPTED) {
cmd.setResult(REJECTED_OTHER_REASON, "internal server error");
@@ -772,7 +777,7 @@
}
private void parseCommands(Collection<ReceiveCommand> commands)
- throws PermissionBackendException {
+ throws PermissionBackendException, NoSuchProjectException, IOException {
List<String> optionList = rp.getPushOptions();
if (optionList != null) {
for (String option : optionList) {
@@ -977,7 +982,8 @@
}
}
- private void parseCreate(ReceiveCommand cmd) throws PermissionBackendException {
+ private void parseCreate(ReceiveCommand cmd)
+ throws PermissionBackendException, NoSuchProjectException, IOException {
RevObject obj;
try {
obj = rp.getRevWalk().parseAny(cmd.getNewId());
@@ -994,8 +1000,8 @@
return;
}
- RefControl ctl = projectControl.controlForRef(cmd.getRefName());
- String rejectReason = ctl.canCreate(rp.getRepository(), obj);
+ Branch.NameKey branch = new Branch.NameKey(project.getName(), cmd.getRefName());
+ String rejectReason = createRefControl.canCreateRef(rp.getRepository(), obj, user, branch);
if (rejectReason != null) {
reject(cmd, "prohibited by Gerrit: " + rejectReason);
return;
@@ -1006,6 +1012,7 @@
return;
}
+ RefControl ctl = projectControl.controlForRef(cmd.getRefName());
validateNewCommits(ctl, cmd);
actualCommands.add(cmd);
}
@@ -1446,9 +1453,8 @@
magicBranch.dest = new Branch.NameKey(project.getNameKey(), ref);
magicBranch.ctl = projectControl.controlForRef(ref);
magicBranch.perm = permissions.ref(ref);
- if (projectControl.getProject().getState()
- != com.google.gerrit.extensions.client.ProjectState.ACTIVE) {
- reject(cmd, "project is read only");
+ if (!projectControl.getProject().getState().permitsWrite()) {
+ reject(cmd, "project state does not permit write");
return;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/RefState.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/RefState.java
new file mode 100644
index 0000000..b55afb6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/RefState.java
@@ -0,0 +1,65 @@
+// 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.index;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Project;
+import java.io.IOException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+
+@AutoValue
+public abstract class RefState {
+ public static RefState create(String ref, String sha) {
+ return new AutoValue_RefState(ref, ObjectId.fromString(sha));
+ }
+
+ public static RefState create(String ref, @Nullable ObjectId id) {
+ return new AutoValue_RefState(ref, firstNonNull(id, ObjectId.zeroId()));
+ }
+
+ public static RefState of(Ref ref) {
+ return new AutoValue_RefState(ref.getName(), ref.getObjectId());
+ }
+
+ public byte[] toByteArray(Project.NameKey project) {
+ byte[] a = (project.toString() + ':' + ref() + ':').getBytes(UTF_8);
+ byte[] b = new byte[a.length + Constants.OBJECT_ID_STRING_LENGTH];
+ System.arraycopy(a, 0, b, 0, a.length);
+ id().copyTo(b, a.length);
+ return b;
+ }
+
+ public static void check(boolean condition, String str) {
+ checkArgument(condition, "invalid RefState: %s", str);
+ }
+
+ public abstract String ref();
+
+ public abstract ObjectId id();
+
+ public boolean match(Repository repo) throws IOException {
+ Ref ref = repo.exactRef(ref());
+ ObjectId expected = ref != null ? ref.getObjectId() : ObjectId.zeroId();
+ return id().equals(expected);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
index b7c5e77..5e12c12 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
@@ -17,20 +17,26 @@
import static com.google.gerrit.index.FieldDef.exact;
import static com.google.gerrit.index.FieldDef.integer;
import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.storedOnly;
import static com.google.gerrit.index.FieldDef.timestamp;
+import static java.util.stream.Collectors.toSet;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.SchemaUtil;
+import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.index.RefState;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
+import org.eclipse.jgit.lib.ObjectId;
/** Secondary index schemas for accounts. */
public class AccountField {
@@ -84,6 +90,9 @@
return preferredEmail != null ? preferredEmail.toLowerCase() : null;
});
+ public static final FieldDef<AccountState, String> PREFERRED_EMAIL_EXACT =
+ exact("preferredemail_exact").build(a -> a.getAccount().getPreferredEmail());
+
public static final FieldDef<AccountState, Timestamp> REGISTERED =
timestamp("registered").build(a -> a.getAccount().getRegisteredOn());
@@ -98,5 +107,43 @@
.transform(k -> k.project().get())
.toSet());
+ /**
+ * All values of all refs that were used in the course of indexing this document, except the
+ * refs/meta/external-ids notes branch which is handled specially (see {@link
+ * #EXTERNAL_ID_STATE}).
+ *
+ * <p>Emitted as UTF-8 encoded strings of the form {@code project:ref/name:[hex sha]}.
+ */
+ public static final FieldDef<AccountState, Iterable<byte[]>> REF_STATE =
+ storedOnly("ref_state")
+ .buildRepeatable(
+ a -> {
+ if (a.getAccount().getMetaId() == null) {
+ return ImmutableList.of();
+ }
+
+ return ImmutableList.of(
+ RefState.create(
+ RefNames.refsUsers(a.getAccount().getId()),
+ ObjectId.fromString(a.getAccount().getMetaId()))
+ .toByteArray(a.getAllUsersNameForIndexing()));
+ });
+
+ /**
+ * All note values of all external IDs that were used in the course of indexing this document.
+ *
+ * <p>Emitted as UTF-8 encoded strings of the form {@code [hex sha of external ID]:[hex sha of
+ * note blob]}, or with other words {@code [note ID]:[note data ID]}.
+ */
+ public static final FieldDef<AccountState, Iterable<byte[]>> EXTERNAL_ID_STATE =
+ storedOnly("external_id_state")
+ .buildRepeatable(
+ a ->
+ a.getExternalIds()
+ .stream()
+ .filter(e -> e.blobId() != null)
+ .map(e -> e.toByteArray())
+ .collect(toSet()));
+
private AccountField() {}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
index 67b507d..2a14f9b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
@@ -18,13 +18,11 @@
import com.google.gerrit.index.IndexCollection;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountState;
-import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class AccountIndexCollection
extends IndexCollection<Account.Id, AccountState, AccountIndex> {
- @Inject
@VisibleForTesting
public AccountIndexCollection() {}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
index 8f9b443..dcdf9e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
@@ -34,7 +34,13 @@
AccountField.USERNAME,
AccountField.WATCHED_PROJECT);
- static final Schema<AccountState> V5 = schema(V4, AccountField.PREFERRED_EMAIL);
+ @Deprecated static final Schema<AccountState> V5 = schema(V4, AccountField.PREFERRED_EMAIL);
+
+ @Deprecated
+ static final Schema<AccountState> V6 =
+ schema(V5, AccountField.REF_STATE, AccountField.EXTERNAL_ID_STATE);
+
+ static final Schema<AccountState> V7 = schema(V6, AccountField.PREFERRED_EMAIL_EXACT);
public static final String NAME = "accounts";
public static final AccountSchemaDefinitions INSTANCE = new AccountSchemaDefinitions();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index cc8f9be..a1c7f14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -50,7 +50,7 @@
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.index.change.StalenessChecker.RefState;
+import com.google.gerrit.server.index.RefState;
import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.notedb.ChangeNotes;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
index 5ce361f..a353a2a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
@@ -18,12 +18,10 @@
import com.google.gerrit.index.IndexCollection;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class ChangeIndexCollection extends IndexCollection<Change.Id, ChangeData, ChangeIndex> {
- @Inject
@VisibleForTesting
public ChangeIndexCollection() {}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
index df92379..e804702 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.index.change;
-import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -35,6 +34,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.RefState;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.query.change.ChangeData;
@@ -47,8 +47,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
@@ -221,43 +219,6 @@
}
}
- @AutoValue
- public abstract static class RefState {
- static RefState create(String ref, String sha) {
- return new AutoValue_StalenessChecker_RefState(ref, ObjectId.fromString(sha));
- }
-
- static RefState create(String ref, @Nullable ObjectId id) {
- return new AutoValue_StalenessChecker_RefState(ref, firstNonNull(id, ObjectId.zeroId()));
- }
-
- static RefState of(Ref ref) {
- return new AutoValue_StalenessChecker_RefState(ref.getName(), ref.getObjectId());
- }
-
- byte[] toByteArray(Project.NameKey project) {
- byte[] a = (project.toString() + ':' + ref() + ':').getBytes(UTF_8);
- byte[] b = new byte[a.length + Constants.OBJECT_ID_STRING_LENGTH];
- System.arraycopy(a, 0, b, 0, a.length);
- id().copyTo(b, a.length);
- return b;
- }
-
- private static void check(boolean condition, String str) {
- checkArgument(condition, "invalid RefState: %s", str);
- }
-
- abstract String ref();
-
- abstract ObjectId id();
-
- private boolean match(Repository repo) throws IOException {
- Ref ref = repo.exactRef(ref());
- ObjectId expected = ref != null ? ref.getObjectId() : ObjectId.zeroId();
- return id().equals(expected);
- }
- }
-
/**
* Pattern for matching refs.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
index 5c49ee5..5ce65a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
@@ -17,13 +17,11 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.gerrit.index.IndexCollection;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class GroupIndexCollection
extends IndexCollection<AccountGroup.UUID, AccountGroup, GroupIndex> {
- @Inject
@VisibleForTesting
public GroupIndexCollection() {}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
index b43dc16..16ede58 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
@@ -105,8 +105,6 @@
this.patches = patches;
}
- protected PatchList() {}
-
/** Old side tree or commit; null only if this is a combined diff. */
@Nullable
public ObjectId getOldId() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index be5a7aa..b985723 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -19,7 +19,6 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.Cache;
-import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
@@ -31,7 +30,6 @@
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
-import java.util.List;
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.lib.Config;
@@ -198,15 +196,11 @@
private static final long serialVersionUID = 1L;
@VisibleForTesting
- public LargeObjectTombstone() {}
-
- /**
- * Return an empty list to prevent {@link NullPointerException}s inside of {@link
- * PatchListWeigher}.
- */
- @Override
- public List<PatchListEntry> getPatches() {
- return ImmutableList.of();
+ public LargeObjectTombstone() {
+ // Initialize super class with valid values. We don't care about the inner state, but need to
+ // pass valid values that don't break (de)serialization.
+ super(
+ null, ObjectId.zeroId(), false, ComparisonType.againstAutoMerge(), new PatchListEntry[0]);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index ea4166f..f1ebf64 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -230,6 +230,7 @@
/** Can this user publish this draft change or any draft patch set of this change? */
public boolean canPublish(ReviewDb db) throws OrmException {
+ // TODO(hiesel) These don't need to be migrated, just remove after support for drafts is removed
return (isOwner() || getRefControl().canPublishDrafts()) && isVisible(db);
}
@@ -439,6 +440,7 @@
}
public boolean isDraftVisible(ReviewDb db, ChangeData cd) throws OrmException {
+ // TODO(hiesel) These don't need to be migrated, just remove after support for drafts is removed
return isOwner()
|| isReviewer(db, cd)
|| getRefControl().canViewDrafts()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 4e2e327..77fb86b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Inject;
@@ -55,6 +56,7 @@
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated referenceUpdated;
private final RefValidationHelper refCreationValidator;
+ private final CreateRefControl createRefControl;
private String ref;
@Inject
@@ -64,18 +66,21 @@
GitRepositoryManager repoManager,
GitReferenceUpdated referenceUpdated,
RefValidationHelper.Factory refHelperFactory,
+ CreateRefControl createRefControl,
@Assisted String ref) {
this.identifiedUser = identifiedUser;
this.permissionBackend = permissionBackend;
this.repoManager = repoManager;
this.referenceUpdated = referenceUpdated;
this.refCreationValidator = refHelperFactory.create(ReceiveCommand.Type.CREATE);
+ this.createRefControl = createRefControl;
this.ref = ref;
}
@Override
public BranchInfo apply(ProjectResource rsrc, BranchInput input)
- throws BadRequestException, AuthException, ResourceConflictException, IOException {
+ throws BadRequestException, AuthException, ResourceConflictException, IOException,
+ PermissionBackendException, NoSuchProjectException {
if (input == null) {
input = new BranchInput();
}
@@ -100,7 +105,6 @@
}
final Branch.NameKey name = new Branch.NameKey(rsrc.getNameKey(), ref);
- final RefControl refControl = rsrc.getControl().controlForRef(name);
try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
ObjectId revid = RefUtil.parseBaseRevision(repo, rsrc.getNameKey(), input.revision);
RevWalk rw = RefUtil.verifyConnected(repo, revid);
@@ -117,7 +121,7 @@
}
}
- String rejectReason = refControl.canCreate(repo, object);
+ String rejectReason = createRefControl.canCreateRef(repo, object, identifiedUser.get(), name);
if (rejectReason != null) {
throw new AuthException("Cannot create \"" + ref + "\": " + rejectReason);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateRefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateRefControl.java
new file mode 100644
index 0000000..aa48a73
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateRefControl.java
@@ -0,0 +1,177 @@
+// 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.project;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.RefPermission;
+import java.io.IOException;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Manages access control for creating Git references (aka branches, tags). */
+@Singleton
+public class CreateRefControl {
+ private static final Logger log = LoggerFactory.getLogger(CreateRefControl.class);
+
+ private final PermissionBackend permissionBackend;
+ private final ProjectCache projectCache;
+
+ @Inject
+ CreateRefControl(PermissionBackend permissionBackend, ProjectCache projectCache) {
+ this.permissionBackend = permissionBackend;
+ this.projectCache = projectCache;
+ }
+
+ /**
+ * Determines whether the user can create a new Git ref.
+ *
+ * @param repo repository on which user want to create
+ * @param object the object the user will start the reference with
+ * @param user the current identified user
+ * @param branch the branch the new {@link RevObject} should be created on
+ * @return {@code null} if the user specified can create a new Git ref, or a String describing why
+ * the creation is not allowed.
+ * @throws PermissionBackendException on failure of permission checks
+ */
+ @Nullable
+ public String canCreateRef(
+ Repository repo, RevObject object, IdentifiedUser user, Branch.NameKey branch)
+ throws PermissionBackendException, NoSuchProjectException, IOException {
+ ProjectState ps = projectCache.checkedGet(branch.getParentKey());
+ if (ps == null) {
+ throw new NoSuchProjectException(branch.getParentKey());
+ }
+ if (!ps.getProject().getState().permitsWrite()) {
+ return "project state does not permit write";
+ }
+
+ PermissionBackend.ForRef perm = permissionBackend.user(user).ref(branch);
+ if (object instanceof RevCommit) {
+ if (!testAuditLogged(perm, RefPermission.CREATE)) {
+ return user.getAccountId() + " lacks permission: " + Permission.CREATE;
+ }
+ return canCreateCommit(repo, (RevCommit) object, ps, user, perm);
+ } else if (object instanceof RevTag) {
+ final RevTag tag = (RevTag) object;
+ try (RevWalk rw = new RevWalk(repo)) {
+ rw.parseBody(tag);
+ } catch (IOException e) {
+ String msg =
+ String.format("RevWalk(%s) for pushing tag %s:", branch.getParentKey(), tag.name());
+ log.error(msg, e);
+
+ return "I/O exception for revwalk";
+ }
+
+ // If tagger is present, require it matches the user's email.
+ //
+ final PersonIdent tagger = tag.getTaggerIdent();
+ if (tagger != null) {
+ boolean valid;
+ if (user.isIdentifiedUser()) {
+ final String addr = tagger.getEmailAddress();
+ valid = user.asIdentifiedUser().hasEmailAddress(addr);
+ } else {
+ valid = false;
+ }
+ if (!valid && !testAuditLogged(perm, RefPermission.FORGE_COMMITTER)) {
+ return user.getAccountId() + " lacks permission: " + Permission.FORGE_COMMITTER;
+ }
+ }
+
+ RevObject tagObject = tag.getObject();
+ if (tagObject instanceof RevCommit) {
+ String rejectReason = canCreateCommit(repo, (RevCommit) tagObject, ps, user, perm);
+ if (rejectReason != null) {
+ return rejectReason;
+ }
+ } else {
+ String rejectReason = canCreateRef(repo, tagObject, user, branch);
+ if (rejectReason != null) {
+ return rejectReason;
+ }
+ }
+
+ // If the tag has a PGP signature, allow a lower level of permission
+ // than if it doesn't have a PGP signature.
+ //
+ RefControl refControl = ps.controlFor(user).controlForRef(branch);
+ if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) {
+ return refControl.canPerform(Permission.CREATE_SIGNED_TAG)
+ ? null
+ : user.getAccountId() + " lacks permission: " + Permission.CREATE_SIGNED_TAG;
+ }
+ return refControl.canPerform(Permission.CREATE_TAG)
+ ? null
+ : user.getAccountId() + " lacks permission " + Permission.CREATE_TAG;
+ }
+
+ return null;
+ }
+
+ /**
+ * Check if the user is allowed to create a new commit object if this introduces a new commit to
+ * the project. If not allowed, returns a string describing why it's not allowed. The userId
+ * argument is only used for the error message.
+ */
+ @Nullable
+ private String canCreateCommit(
+ Repository repo,
+ RevCommit commit,
+ ProjectState projectState,
+ IdentifiedUser user,
+ PermissionBackend.ForRef forRef)
+ throws PermissionBackendException {
+ if (projectState.controlFor(user).isReachableFromHeadsOrTags(repo, commit)) {
+ // If the user has no push permissions, check whether the object is
+ // merged into a branch or tag readable by this user. If so, they are
+ // not effectively "pushing" more objects, so they can create the ref
+ // even if they don't have push permission.
+ return null;
+ } else if (testAuditLogged(forRef, RefPermission.UPDATE)) {
+ // If the user has push permissions, they can create the ref regardless
+ // of whether they are pushing any new objects along with the create.
+ return null;
+ }
+ return user.getAccountId()
+ + " lacks permission "
+ + Permission.PUSH
+ + " for creating new commit object";
+ }
+
+ private boolean testAuditLogged(PermissionBackend.ForRef forRef, RefPermission p)
+ throws PermissionBackendException {
+ try {
+ forRef.check(p);
+ } catch (AuthException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 16c820f..a749759 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -16,11 +16,9 @@
import static com.google.common.base.Preconditions.checkArgument;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
-import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
@@ -34,7 +32,6 @@
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -44,19 +41,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevTag;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/** Manages access control for Git references (aka branches, tags). */
public class RefControl {
- private static final Logger log = LoggerFactory.getLogger(RefControl.class);
-
private final ProjectControl projectControl;
private final String refName;
@@ -209,12 +196,11 @@
}
private boolean isProjectStatePermittingWrite() {
- return getProjectControl().getProject().getState().equals(ProjectState.ACTIVE);
+ return getProjectControl().getProject().getState().permitsWrite();
}
private boolean isProjectStatePermittingRead() {
- return getProjectControl().getProject().getState().equals(ProjectState.READ_ONLY)
- || isProjectStatePermittingWrite();
+ return getProjectControl().getProject().getState().permitsRead();
}
private boolean canPushWithForce() {
@@ -231,108 +217,6 @@
}
/**
- * Determines whether the user can create a new Git ref.
- *
- * @param repo repository on which user want to create
- * @param object the object the user will start the reference with.
- * @return {@code null} if the user specified can create a new Git ref, or a String describing why
- * the creation is not allowed.
- */
- @Nullable
- public String canCreate(Repository repo, RevObject object) {
- if (!isProjectStatePermittingWrite()) {
- return "project state does not permit write";
- }
-
- String userId =
- getUser().isIdentifiedUser() ? "account " + getUser().getAccountId() : "anonymous user";
-
- if (object instanceof RevCommit) {
- if (!canPerform(Permission.CREATE)) {
- return userId + " lacks permission: " + Permission.CREATE;
- }
- return canCreateCommit(repo, (RevCommit) object, userId);
- } else if (object instanceof RevTag) {
- final RevTag tag = (RevTag) object;
- try (RevWalk rw = new RevWalk(repo)) {
- rw.parseBody(tag);
- } catch (IOException e) {
- String msg =
- String.format(
- "RevWalk(%s) for pushing tag %s:",
- projectControl.getProject().getNameKey(), tag.name());
- log.error(msg, e);
-
- return "I/O exception for revwalk";
- }
-
- // If tagger is present, require it matches the user's email.
- //
- final PersonIdent tagger = tag.getTaggerIdent();
- if (tagger != null) {
- boolean valid;
- if (getUser().isIdentifiedUser()) {
- final String addr = tagger.getEmailAddress();
- valid = getUser().asIdentifiedUser().hasEmailAddress(addr);
- } else {
- valid = false;
- }
- if (!valid && !canForgeCommitter()) {
- return userId + " lacks permission: " + Permission.FORGE_COMMITTER;
- }
- }
-
- RevObject tagObject = tag.getObject();
- if (tagObject instanceof RevCommit) {
- String rejectReason = canCreateCommit(repo, (RevCommit) tagObject, userId);
- if (rejectReason != null) {
- return rejectReason;
- }
- } else {
- String rejectReason = canCreate(repo, tagObject);
- if (rejectReason != null) {
- return rejectReason;
- }
- }
-
- // If the tag has a PGP signature, allow a lower level of permission
- // than if it doesn't have a PGP signature.
- //
- if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) {
- return canPerform(Permission.CREATE_SIGNED_TAG)
- ? null
- : userId + " lacks permission: " + Permission.CREATE_SIGNED_TAG;
- }
- return canPerform(Permission.CREATE_TAG)
- ? null
- : userId + " lacks permission " + Permission.CREATE_TAG;
- }
-
- return null;
- }
-
- /**
- * Check if the user is allowed to create a new commit object if this introduces a new commit to
- * the project. If not allowed, returns a string describing why it's not allowed. The userId
- * argument is only used for the error message.
- */
- @Nullable
- private String canCreateCommit(Repository repo, RevCommit commit, String userId) {
- if (canUpdate()) {
- // If the user has push permissions, they can create the ref regardless
- // of whether they are pushing any new objects along with the create.
- return null;
- } else if (projectControl.isReachableFromHeadsOrTags(repo, commit)) {
- // If the user has no push permissions, check whether the object is
- // merged into a branch or tag readable by this user. If so, they are
- // not effectively "pushing" more objects, so they can create the ref
- // even if they don't have push permission.
- return null;
- }
- return userId + " lacks permission " + Permission.PUSH + " for creating new commit object";
- }
-
- /**
* Determines whether the user can delete the Git ref controlled by this object.
*
* @return {@code true} if the user specified can delete a Git ref.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
index d6552e2..9213353 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
@@ -66,6 +66,11 @@
email.toLowerCase());
}
+ public static Predicate<AccountState> preferredEmailExact(String email) {
+ return new AccountPredicate(
+ AccountField.PREFERRED_EMAIL_EXACT, AccountQueryBuilder.FIELD_PREFERRED_EMAIL_EXACT, email);
+ }
+
public static Predicate<AccountState> equalsName(String name) {
return new AccountPredicate(
AccountField.NAME_PART, AccountQueryBuilder.FIELD_NAME, name.toLowerCase());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 9358a7a..946a729 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -37,6 +37,7 @@
public static final String FIELD_LIMIT = "limit";
public static final String FIELD_NAME = "name";
public static final String FIELD_PREFERRED_EMAIL = "preferredemail";
+ public static final String FIELD_PREFERRED_EMAIL_EXACT = "preferredemail_exact";
public static final String FIELD_USERNAME = "username";
public static final String FIELD_VISIBLETO = "visibleto";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index 4821e6f..bbcb811 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.query.account;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
@@ -25,6 +26,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -113,17 +115,60 @@
return query(AccountPredicates.fullName(fullName));
}
+ /**
+ * Queries for accounts that have a preferred email that exactly matches the given email.
+ *
+ * @param email preferred email by which accounts should be found
+ * @return list of accounts that have a preferred email that exactly matches the given email
+ * @throws OrmException if query cannot be parsed
+ */
public List<AccountState> byPreferredEmail(String email) throws OrmException {
- return query(AccountPredicates.preferredEmail(email));
+ if (schema().hasField(AccountField.PREFERRED_EMAIL_EXACT)) {
+ return query(AccountPredicates.preferredEmailExact(email));
+ }
+
+ return query(AccountPredicates.preferredEmail(email))
+ .stream()
+ .filter(a -> a.getAccount().getPreferredEmail().equals(email))
+ .collect(toList());
}
+ /**
+ * Makes multiple queries for accounts by preferred email (exact match).
+ *
+ * @param emails preferred emails by which accounts should be found
+ * @return multimap of the given emails to accounts that have a preferred email that exactly
+ * matches this email
+ * @throws OrmException if query cannot be parsed
+ */
public Multimap<String, AccountState> byPreferredEmail(String... emails) throws OrmException {
List<String> emailList = Arrays.asList(emails);
+
+ if (schema().hasField(AccountField.PREFERRED_EMAIL_EXACT)) {
+ List<List<AccountState>> r =
+ query(
+ emailList
+ .stream()
+ .map(e -> AccountPredicates.preferredEmailExact(e))
+ .collect(toList()));
+ Multimap<String, AccountState> accountsByEmail = ArrayListMultimap.create();
+ for (int i = 0; i < emailList.size(); i++) {
+ accountsByEmail.putAll(emailList.get(i), r.get(i));
+ }
+ return accountsByEmail;
+ }
+
List<List<AccountState>> r =
query(emailList.stream().map(e -> AccountPredicates.preferredEmail(e)).collect(toList()));
Multimap<String, AccountState> accountsByEmail = ArrayListMultimap.create();
for (int i = 0; i < emailList.size(); i++) {
- accountsByEmail.putAll(emailList.get(i), r.get(i));
+ String email = emailList.get(i);
+ Set<AccountState> matchingAccounts =
+ r.get(i)
+ .stream()
+ .filter(a -> a.getAccount().getPreferredEmail().equals(email))
+ .collect(toSet());
+ accountsByEmail.putAll(email, matchingAccounts);
}
return accountsByEmail;
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/account/AccountFieldTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/account/AccountFieldTest.java
new file mode 100644
index 0000000..7eed034
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/account/AccountFieldTest.java
@@ -0,0 +1,94 @@
+// 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.index.account;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.testutil.GerritBaseTests;
+import java.util.List;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class AccountFieldTest extends GerritBaseTests {
+ @Test
+ public void refStateFieldValues() throws Exception {
+ AllUsersName allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
+ Account account = new Account(new Account.Id(1), TimeUtil.nowTs());
+ String metaId = "0e39795bb25dc914118224995c53c5c36923a461";
+ account.setMetaId(metaId);
+ List<String> values =
+ toStrings(
+ AccountField.REF_STATE.get(
+ new AccountState(
+ allUsersName,
+ account,
+ ImmutableSet.of(),
+ ImmutableSet.of(),
+ ImmutableMap.of())));
+ assertThat(values).hasSize(1);
+ String expectedValue =
+ allUsersName.get() + ":" + RefNames.refsUsers(account.getId()) + ":" + metaId;
+ assertThat(Iterables.getOnlyElement(values)).isEqualTo(expectedValue);
+ }
+
+ @Test
+ public void externalIdStateFieldValues() throws Exception {
+ Account.Id id = new Account.Id(1);
+ Account account = new Account(id, TimeUtil.nowTs());
+ ExternalId extId1 =
+ ExternalId.create(
+ ExternalId.Key.create(ExternalId.SCHEME_MAILTO, "foo.bar@example.com"),
+ id,
+ "foo.bar@example.com",
+ null,
+ ObjectId.fromString("1b9a0cf038ea38a0ab08617c39aa8e28413a27ca"));
+ ExternalId extId2 =
+ ExternalId.create(
+ ExternalId.Key.create(ExternalId.SCHEME_USERNAME, "foo"),
+ id,
+ null,
+ "secret",
+ ObjectId.fromString("5b3a73dc9a668a5b89b5f049225261e3e3291d1a"));
+ List<String> values =
+ toStrings(
+ AccountField.EXTERNAL_ID_STATE.get(
+ new AccountState(
+ null,
+ account,
+ ImmutableSet.of(),
+ ImmutableSet.of(extId1, extId2),
+ ImmutableMap.of())));
+ String expectedValue1 = extId1.key().sha1().name() + ":" + extId1.blobId().name();
+ String expectedValue2 = extId2.key().sha1().name() + ":" + extId2.blobId().name();
+ assertThat(values).containsExactly(expectedValue1, expectedValue2);
+ }
+
+ private List<String> toStrings(Iterable<byte[]> values) {
+ return Streams.stream(values).map(v -> new String(v, UTF_8)).collect(toList());
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/StalenessCheckerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/StalenessCheckerTest.java
index 0af642d..b25ed2b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/StalenessCheckerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/StalenessCheckerTest.java
@@ -28,7 +28,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.change.StalenessChecker.RefState;
+import com.google.gerrit.server.index.RefState;
import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
import com.google.gerrit.server.notedb.NoteDbChangeState;
import com.google.gerrit.testutil.GerritBaseTests;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
index aaf723a..5215561 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
@@ -25,6 +25,8 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.mail.Address;
import java.util.Arrays;
import java.util.Collections;
@@ -383,6 +385,10 @@
account.setFullName(name);
account.setPreferredEmail(email);
return new AccountState(
- account, Collections.emptySet(), Collections.emptySet(), new HashMap<>());
+ new AllUsersName(AllUsersNameProvider.DEFAULT),
+ account,
+ Collections.emptySet(),
+ Collections.emptySet(),
+ new HashMap<>());
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListTest.java
index 19adf32..0a7b97cc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/patch/PatchListTest.java
@@ -17,6 +17,11 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.reviewdb.client.Patch;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Comparator;
import org.junit.Test;
@@ -65,4 +70,21 @@
});
assertThat(names).isEqualTo(want);
}
+
+ @Test
+ public void largeObjectTombstoneCanBeSerializedAndDeserialized() throws Exception {
+ // Serialize
+ byte[] serializedObject;
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream objectStream = new ObjectOutputStream(baos)) {
+ objectStream.writeObject(new PatchListCacheImpl.LargeObjectTombstone());
+ serializedObject = baos.toByteArray();
+ assertThat(serializedObject).isNotNull();
+ }
+ // Deserialize
+ try (InputStream is = new ByteArrayInputStream(serializedObject);
+ ObjectInputStream ois = new ObjectInputStream(is)) {
+ assertThat(ois.readObject()).isInstanceOf(PatchListCacheImpl.LargeObjectTombstone.class);
+ }
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
index 242f208..c3b588b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
@@ -20,6 +20,8 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
import java.util.HashMap;
import java.util.Map;
@@ -78,6 +80,11 @@
}
private static AccountState newState(Account account) {
- return new AccountState(account, ImmutableSet.of(), ImmutableSet.of(), new HashMap<>());
+ return new AccountState(
+ new AllUsersName(AllUsersNameProvider.DEFAULT),
+ account,
+ ImmutableSet.of(),
+ ImmutableSet.of(),
+ new HashMap<>());
}
}
diff --git a/plugins/replication b/plugins/replication
index 35d87c0..297b749 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 35d87c092ea7ef55085f2608917a85b2bd909b2f
+Subproject commit 297b749038153527291b43cb08b162eb475adcd7
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 9051695..e3358d4 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -37,554 +37,32 @@
getReporting().timeEnd('WebComponentsReady');
});
- const encode = window.Gerrit.URLEncodingBehavior.encodeURL;
- const patchNumEquals = window.Gerrit.PatchSetBehavior.patchNumEquals;
- const EDIT_NAME = window.Gerrit.PatchSetBehavior.EDIT_NAME;
-
- function startRouter(generateUrl) {
- const base = window.Gerrit.BaseUrlBehavior.getBaseUrl();
- if (base) {
- page.base(base);
- }
-
- const restAPI = document.createElement('gr-rest-api-interface');
- const reporting = getReporting();
-
- Gerrit.Nav.setup(url => { page.show(url); }, generateUrl);
-
- /**
- * Given a set of params without a project, gets the project from the rest
- * API project lookup and then sets the app params.
- *
- * @param {?Object} params
- */
- const normalizeLegacyRouteParams = params => {
- if (!params.changeNum) { return; }
-
- restAPI.getFromProjectLookup(params.changeNum).then(project => {
- params.project = project;
- normalizePatchRangeParams(params);
- page.redirect(generateUrl(params));
- });
- };
-
- // Middleware
- page((ctx, next) => {
- document.body.scrollTop = 0;
-
- // Fire asynchronously so that the URL is changed by the time the event
- // is processed.
- app.async(() => {
- app.fire('location-change', {
- hash: window.location.hash,
- pathname: window.location.pathname,
- });
- reporting.locationChanged();
- }, 1);
- next();
- });
-
- function loadUser(ctx, next) {
- restAPI.getLoggedIn().then(() => { next(); });
- }
-
- // Routes.
- page('/', loadUser, data => {
- if (data.querystring.match(/^closeAfterLogin/)) {
- // Close child window on redirect after login.
- window.close();
- }
- // For backward compatibility with GWT links.
- if (data.hash) {
- // In certain login flows the server may redirect to a hash without
- // a leading slash, which page.js doesn't handle correctly.
- if (data.hash[0] !== '/') {
- data.hash = '/' + data.hash;
- }
- if (data.hash.includes('/ /') && data.canonicalPath.includes('/+/')) {
- // Path decodes all '+' to ' ' -- this breaks project-based URLs.
- // See Issue 6888.
- data.hash = data.hash.replace('/ /', '/+/');
- }
- const hash = data.hash;
- let newUrl = base + hash;
- if (hash.startsWith('/VE/')) {
- newUrl = base + '/settings' + data.hash;
- }
- page.redirect(newUrl);
- return;
- }
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- page.redirect('/dashboard/self');
- } else {
- page.redirect('/q/status:open');
- }
- });
- });
-
- function redirectToLogin(data) {
- const basePath = base || '';
- page('/login/' + encodeURIComponent(data.substring(basePath.length)));
- }
-
- page('/dashboard/(.*)', loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- data.params.view = Gerrit.Nav.View.DASHBOARD;
- app.params = data.params;
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- // Matches /admin/groups/<group>,info (backwords compat with gwtui)
- // Redirects to /admin/groups/<group>
- page(/^\/admin\/groups\/(.+),info$/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- page.redirect('/admin/groups/' + encodeURIComponent(data.params[0]));
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- // Matches /admin/groups/<group>,audit-log
- page(/^\/admin\/groups\/(.+),audit-log$/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-group-audit-log',
- detailType: 'audit-log',
- groupId: data.params[0],
- };
- } else {
- page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
- }
- });
- });
-
- // Matches /admin/groups/<group>,members
- page(/^\/admin\/groups\/(.+),members$/, loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-group-members',
- detailType: 'members',
- groupId: data.params[0],
- };
- });
-
- // Matches /admin/groups[,<offset>][/].
- page(/^\/admin\/groups(,(\d+))?(\/)?$/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-admin-group-list',
- offset: data.params[1] || 0,
- filter: null,
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page('/admin/groups/q/filter::filter,:offset', loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-admin-group-list',
- offset: data.params.offset,
- filter: data.params.filter,
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page('/admin/groups/q/filter::filter', loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-admin-group-list',
- filter: data.params.filter || null,
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- // Matches /admin/groups/<group>
- page(/^\/admin\/groups\/([^,]+)$/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-group',
- groupId: data.params[0],
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- // Matches /admin/projects/<project>,commands.
- page(/^\/admin\/projects\/(.+),commands$/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-project-commands',
- detailType: 'commands',
- project: data.params[0],
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- // Matches /admin/projects/<project>,branches[,<offset>].
- page(/^\/admin\/projects\/(.+),branches(,(.+))?$/, loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-project-detail-list',
- detailType: 'branches',
- project: data.params[0],
- offset: data.params[2] || 0,
- filter: null,
- };
- });
-
- page('/admin/projects/:project,branches/q/filter::filter,:offset',
- loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-project-detail-list',
- detailType: 'branches',
- project: data.params.project,
- offset: data.params.offset,
- filter: data.params.filter,
- };
- });
-
- page('/admin/projects/:project,branches/q/filter::filter',
- loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-project-detail-list',
- detailType: 'branches',
- project: data.params.project,
- filter: data.params.filter || null,
- };
- });
-
- // Matches /admin/projects/<project>,tags[,<offset>].
- page(/^\/admin\/projects\/(.+),tags(,(.+))?$/, loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-project-detail-list',
- detailType: 'tags',
- project: data.params[0],
- offset: data.params[2] || 0,
- filter: null,
- };
- });
-
- page('/admin/projects/:project,tags/q/filter::filter,:offset',
- loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-project-detail-list',
- detailType: 'tags',
- project: data.params.project,
- offset: data.params.offset,
- filter: data.params.filter,
- };
- });
-
- page('/admin/projects/:project,tags/q/filter::filter',
- loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-project-detail-list',
- detailType: 'tags',
- project: data.params.project,
- filter: data.params.filter || null,
- };
- });
-
- // Matches /admin/projects[,<offset>][/].
- page(/^\/admin\/projects(,(\d+))?(\/)?$/, loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-admin-project-list',
- offset: data.params[1] || 0,
- filter: null,
- };
- });
-
- page('/admin/projects/q/filter::filter,:offset', loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-admin-project-list',
- offset: data.params.offset,
- filter: data.params.filter,
- };
- });
-
- page('/admin/projects/q/filter::filter', loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-admin-project-list',
- filter: data.params.filter || null,
- };
- });
-
- // Matches /admin/projects/<project>
- page(/^\/admin\/projects\/([^,]+)$/, loadUser, data => {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- project: data.params[0],
- adminView: 'gr-project',
- };
- });
-
- // Matches /admin/plugins[,<offset>][/].
- page(/^\/admin\/plugins(,(\d+))?(\/)?$/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-plugin-list',
- offset: data.params[1] || 0,
- filter: null,
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page('/admin/plugins/q/filter::filter,:offset', loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-plugin-list',
- offset: data.params.offset,
- filter: data.params.filter,
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page('/admin/plugins/q/filter::filter', loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-plugin-list',
- filter: data.params.filter || null,
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page(/^\/admin\/plugins(\/)?$/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.ADMIN,
- adminView: 'gr-plugin-list',
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page('/admin/(.*)', loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- data.params.view = Gerrit.Nav.View.ADMIN;
- data.params.placeholder = true;
- app.params = data.params;
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- function queryHandler(data) {
- data.params.view = Gerrit.Nav.View.SEARCH;
- app.params = data.params;
- }
-
- page('/q/:query,:offset', queryHandler);
- page('/q/:query', queryHandler);
-
- page(/^\/(\d+)\/?/, ctx => {
- page.redirect('/c/' + encodeURIComponent(ctx.params[0]));
- });
-
- /**
- * Normalizes the params object, and determines if the URL needs to be
- * modified to fit the proper schema.
- *
- * @param {*} params
- * @return {boolean} whether or not the URL needs to be upgraded.
- */
- const normalizePatchRangeParams = params => {
- let needsRedirect = false;
- if (params.basePatchNum &&
- patchNumEquals(params.basePatchNum, params.patchNum)) {
- needsRedirect = true;
- params.basePatchNum = null;
- } else if (params.basePatchNum && !params.patchNum) {
- // Regexes set basePatchNum instead of patchNum when only one is
- // specified. Redirect is not needed in this case.
- params.patchNum = params.basePatchNum;
- params.basePatchNum = null;
- }
- // In GWTUI, edits are represented in URLs with either 0 or 'edit'.
- // TODO(kaspern): Remove this normalization when GWT UI is gone.
- if (patchNumEquals(params.basePatchNum, 0)) {
- params.basePatchNum = EDIT_NAME;
- needsRedirect = true;
- }
- if (patchNumEquals(params.patchNum, 0)) {
- params.patchNum = EDIT_NAME;
- needsRedirect = true;
- }
- return needsRedirect;
- };
-
- // Matches
- // /c/<project>/+/<changeNum>/[<basePatchNum|edit>..][<patchNum|edit>]/[path].
- // TODO(kaspern): Migrate completely to project based URLs, with backwards
- // compatibility for change-only.
- // eslint-disable-next-line max-len
- page(/^\/c\/(.+)\/\+\/(\d+)(\/?((\d+|edit)(\.\.(\d+|edit))?(\/(.+))?))?\/?$/,
- ctx => {
- // Parameter order is based on the regex group number matched.
- const params = {
- project: ctx.params[0],
- changeNum: ctx.params[1],
- basePatchNum: ctx.params[4],
- patchNum: ctx.params[6],
- path: ctx.params[8],
- view: ctx.params[8] ? Gerrit.Nav.View.DIFF : Gerrit.Nav.View.CHANGE,
- hash: ctx.hash,
- };
- const needsRedirect = normalizePatchRangeParams(params);
- if (needsRedirect) {
- page.redirect(generateUrl(params));
- } else {
- app.params = params;
- restAPI.setInProjectLookup(params.changeNum, params.project);
- }
- });
-
- // Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
- page(/^\/c\/(\d+)\/?(((\d+|edit)(\.\.(\d+|edit))?))?\/?$/, ctx => {
- // Parameter order is based on the regex group number matched.
- const params = {
- changeNum: ctx.params[0],
- basePatchNum: ctx.params[3],
- patchNum: ctx.params[5],
- view: Gerrit.Nav.View.CHANGE,
- };
-
- normalizeLegacyRouteParams(params);
- });
-
- // Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
- page(/^\/c\/(\d+)\/((\d+|edit)(\.\.(\d+|edit))?)\/(.+)/, ctx => {
- // Check if path has an '@' which indicates it was using GWT style line
- // numbers. Even if the filename had an '@' in it, it would have already
- // been URI encoded. Redirect to hash version of path.
- if (ctx.path.includes('@')) {
- page.redirect(ctx.path.replace('@', '#'));
- return;
- }
-
- // Parameter order is based on the regex group number matched.
- const params = {
- changeNum: ctx.params[0],
- basePatchNum: ctx.params[2],
- patchNum: ctx.params[4],
- path: ctx.params[5],
- hash: ctx.hash,
- view: Gerrit.Nav.View.DIFF,
- };
-
- normalizeLegacyRouteParams(params);
- });
-
- page(/^\/settings\/(agreements|new-agreement)/, loadUser, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- data.params.view = Gerrit.Nav.View.AGREEMENTS;
- app.params = data.params;
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page(/^\/settings\/VE\/(\S+)/, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {
- view: Gerrit.Nav.View.SETTINGS,
- emailToken: data.params[0],
- };
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page(/^\/settings\/?/, data => {
- restAPI.getLoggedIn().then(loggedIn => {
- if (loggedIn) {
- app.params = {view: Gerrit.Nav.View.SETTINGS};
- } else {
- redirectToLogin(data.canonicalPath);
- }
- });
- });
-
- page(/^\/register(\/.*)?/, ctx => {
- app.params = {justRegistered: true};
- const path = ctx.params[0] || '/';
- if (path[0] !== '/') { return; }
- page.show(base + path);
- });
-
- page.start();
- }
-
Polymer({
is: 'gr-router',
- behaviors: [Gerrit.PatchSetBehavior],
+
+ properties: {
+ _restAPI: {
+ type: Object,
+ value: () => document.createElement('gr-rest-api-interface'),
+ },
+ },
+
+ behaviors: [
+ Gerrit.URLEncodingBehavior,
+ Gerrit.PatchSetBehavior,
+ ],
+
start() {
if (!app) { return; }
- startRouter(this._generateUrl.bind(this));
+ this._startRouter();
+ },
+
+ _setParams(params) {
+ app.params = params;
+ },
+
+ _redirect(url) {
+ page.redirect(url);
},
_generateUrl(params) {
@@ -594,28 +72,30 @@
if (params.view === Gerrit.Nav.View.SEARCH) {
const operators = [];
if (params.owner) {
- operators.push('owner:' + encode(params.owner));
+ operators.push('owner:' + this.encodeURL(params.owner, false));
}
if (params.project) {
- operators.push('project:' + encode(params.project));
+ operators.push('project:' + this.encodeURL(params.project, false));
}
if (params.branch) {
- operators.push('branch:' + encode(params.branch));
+ operators.push('branch:' + this.encodeURL(params.branch, false));
}
if (params.topic) {
- operators.push('topic:"' + encode(params.topic) + '"');
+ operators.push('topic:"' + this.encodeURL(params.topic, false) + '"');
}
if (params.hashtag) {
operators.push('hashtag:"' +
- encode(params.hashtag.toLowerCase()) + '"');
+ this.encodeURL(params.hashtag.toLowerCase(), false) + '"');
}
if (params.statuses) {
if (params.statuses.length === 1) {
- operators.push('status:' + encode(params.statuses[0]));
+ operators.push(
+ 'status:' + this.encodeURL(params.statuses[0], false));
} else if (params.statuses.length > 1) {
operators.push(
'(' +
- params.statuses.map(s => `status:${encode(s)}`).join(' OR ') +
+ params.statuses.map(s => `status:${this.encodeURL(s, false)}`)
+ .join(' OR ') +
')');
}
}
@@ -632,7 +112,7 @@
let range = this._getPatchRangeExpression(params);
if (range.length) { range = '/' + range; }
- let suffix = `${range}/${encode(params.path, true)}`;
+ let suffix = `${range}/${this.encodeURL(params.path, true)}`;
if (params.lineNum) {
suffix += '#';
if (params.leftSide) { suffix += 'b'; }
@@ -657,5 +137,552 @@
if (params.basePatchNum) { range = params.basePatchNum + '..' + range; }
return range;
},
+
+ /**
+ * Given a set of params without a project, gets the project from the rest
+ * API project lookup and then sets the app params.
+ *
+ * @param {?Object} params
+ */
+ _normalizeLegacyRouteParams(params) {
+ if (!params.changeNum) { return Promise.resolve(); }
+
+ return this._restAPI.getFromProjectLookup(params.changeNum)
+ .then(project => {
+ params.project = project;
+ this._normalizePatchRangeParams(params);
+ this._redirect(this._generateUrl(params));
+ });
+ },
+
+ /**
+ * Normalizes the params object, and determines if the URL needs to be
+ * modified to fit the proper schema.
+ *
+ * @param {*} params
+ * @return {boolean} whether or not the URL needs to be upgraded.
+ */
+ _normalizePatchRangeParams(params) {
+ const hasBasePatchNum = params.basePatchNum !== null &&
+ params.basePatchNum !== undefined;
+ const hasPatchNum = params.patchNum !== null &&
+ params.patchNum !== undefined;
+ let needsRedirect = false;
+ if (hasBasePatchNum &&
+ this.patchNumEquals(params.basePatchNum, params.patchNum)) {
+ needsRedirect = true;
+ params.basePatchNum = null;
+ } else if (hasBasePatchNum && !hasPatchNum) {
+ // Regexes set basePatchNum instead of patchNum when only one is
+ // specified. Redirect is not needed in this case.
+ params.patchNum = params.basePatchNum;
+ params.basePatchNum = null;
+ }
+ // In GWTUI, edits are represented in URLs with either 0 or 'edit'.
+ // TODO(kaspern): Remove this normalization when GWT UI is gone.
+ if (this.patchNumEquals(params.basePatchNum, 0)) {
+ params.basePatchNum = this.EDIT_NAME;
+ needsRedirect = true;
+ }
+ if (this.patchNumEquals(params.patchNum, 0)) {
+ params.patchNum = this.EDIT_NAME;
+ needsRedirect = true;
+ }
+ return needsRedirect;
+ },
+
+ _redirectToLogin(data) {
+ const basePath = window.Gerrit.BaseUrlBehavior.getBaseUrl() || '';
+ page('/login/' + encodeURIComponent(data.substring(basePath.length)));
+ },
+
+ _startRouter() {
+ const base = window.Gerrit.BaseUrlBehavior.getBaseUrl();
+ if (base) {
+ page.base(base);
+ }
+
+ const reporting = getReporting();
+
+ Gerrit.Nav.setup(url => { page.show(url); },
+ this._generateUrl.bind(this));
+
+ // Middleware
+ page((ctx, next) => {
+ document.body.scrollTop = 0;
+
+ // Fire asynchronously so that the URL is changed by the time the event
+ // is processed.
+ this.async(() => {
+ app.fire('location-change', {
+ hash: window.location.hash,
+ pathname: window.location.pathname,
+ });
+ reporting.locationChanged();
+ }, 1);
+ next();
+ });
+
+ const loadUser = (ctx, next) => {
+ this._restAPI.getLoggedIn().then(() => { next(); });
+ };
+
+ // Routes.
+ page('/', loadUser, data => {
+ if (data.querystring.match(/^closeAfterLogin/)) {
+ // Close child window on redirect after login.
+ window.close();
+ }
+ // For backward compatibility with GWT links.
+ if (data.hash) {
+ // In certain login flows the server may redirect to a hash without
+ // a leading slash, which page.js doesn't handle correctly.
+ if (data.hash[0] !== '/') {
+ data.hash = '/' + data.hash;
+ }
+ if (data.hash.includes('/ /') && data.canonicalPath.includes('/+/')) {
+ // Path decodes all '+' to ' ' -- this breaks project-based URLs.
+ // See Issue 6888.
+ data.hash = data.hash.replace('/ /', '/+/');
+ }
+ const hash = data.hash;
+ let newUrl = base + hash;
+ if (hash.startsWith('/VE/')) {
+ newUrl = base + '/settings' + data.hash;
+ }
+ this._redirect(newUrl);
+ return;
+ }
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._redirect('/dashboard/self');
+ } else {
+ this._redirect('/q/status:open');
+ }
+ });
+ });
+
+ page('/dashboard/(.*)', loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ data.params.view = Gerrit.Nav.View.DASHBOARD;
+ this._setParams(data.params);
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ // Matches /admin/groups/<group>,info (backwords compat with gwtui)
+ // Redirects to /admin/groups/<group>
+ page(/^\/admin\/groups\/(.+),info$/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._redirect(
+ '/admin/groups/' + encodeURIComponent(data.params[0]));
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ // Matches /admin/groups/<group>,audit-log
+ page(/^\/admin\/groups\/(.+),audit-log$/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-group-audit-log',
+ detailType: 'audit-log',
+ groupId: data.params[0],
+ });
+ } else {
+ this._redirect('/login/' + encodeURIComponent(data.canonicalPath));
+ }
+ });
+ });
+
+ // Matches /admin/groups/<group>,members
+ page(/^\/admin\/groups\/(.+),members$/, loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-group-members',
+ detailType: 'members',
+ groupId: data.params[0],
+ });
+ });
+
+ // Matches /admin/groups[,<offset>][/].
+ page(/^\/admin\/groups(,(\d+))?(\/)?$/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-admin-group-list',
+ offset: data.params[1] || 0,
+ filter: null,
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page('/admin/groups/q/filter::filter,:offset', loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-admin-group-list',
+ offset: data.params.offset,
+ filter: data.params.filter,
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page('/admin/groups/q/filter::filter', loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-admin-group-list',
+ filter: data.params.filter || null,
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ // Matches /admin/groups/<group>
+ page(/^\/admin\/groups\/([^,]+)$/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-group',
+ groupId: data.params[0],
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ // Matches /admin/projects/<project>,commands.
+ page(/^\/admin\/projects\/(.+),commands$/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-project-commands',
+ detailType: 'commands',
+ project: data.params[0],
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ // Matches /admin/projects/<project>,branches[,<offset>].
+ page(/^\/admin\/projects\/(.+),branches(,(.+))?$/, loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-project-detail-list',
+ detailType: 'branches',
+ project: data.params[0],
+ offset: data.params[2] || 0,
+ filter: null,
+ });
+ });
+
+ page('/admin/projects/:project,branches/q/filter::filter,:offset',
+ loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-project-detail-list',
+ detailType: 'branches',
+ project: data.params.project,
+ offset: data.params.offset,
+ filter: data.params.filter,
+ });
+ });
+
+ page('/admin/projects/:project,branches/q/filter::filter',
+ loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-project-detail-list',
+ detailType: 'branches',
+ project: data.params.project,
+ filter: data.params.filter || null,
+ });
+ });
+
+ // Matches /admin/projects/<project>,tags[,<offset>].
+ page(/^\/admin\/projects\/(.+),tags(,(.+))?$/, loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-project-detail-list',
+ detailType: 'tags',
+ project: data.params[0],
+ offset: data.params[2] || 0,
+ filter: null,
+ });
+ });
+
+ page('/admin/projects/:project,tags/q/filter::filter,:offset',
+ loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-project-detail-list',
+ detailType: 'tags',
+ project: data.params.project,
+ offset: data.params.offset,
+ filter: data.params.filter,
+ });
+ });
+
+ page('/admin/projects/:project,tags/q/filter::filter',
+ loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-project-detail-list',
+ detailType: 'tags',
+ project: data.params.project,
+ filter: data.params.filter || null,
+ });
+ });
+
+ // Matches /admin/projects[,<offset>][/].
+ page(/^\/admin\/projects(,(\d+))?(\/)?$/, loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-admin-project-list',
+ offset: data.params[1] || 0,
+ filter: null,
+ });
+ });
+
+ page('/admin/projects/q/filter::filter,:offset', loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-admin-project-list',
+ offset: data.params.offset,
+ filter: data.params.filter,
+ });
+ });
+
+ page('/admin/projects/q/filter::filter', loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-admin-project-list',
+ filter: data.params.filter || null,
+ });
+ });
+
+ // Matches /admin/projects/<project>
+ page(/^\/admin\/projects\/([^,]+)$/, loadUser, data => {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ project: data.params[0],
+ adminView: 'gr-project',
+ });
+ });
+
+ // Matches /admin/plugins[,<offset>][/].
+ page(/^\/admin\/plugins(,(\d+))?(\/)?$/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-plugin-list',
+ offset: data.params[1] || 0,
+ filter: null,
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page('/admin/plugins/q/filter::filter,:offset', loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-plugin-list',
+ offset: data.params.offset,
+ filter: data.params.filter,
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page('/admin/plugins/q/filter::filter', loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-plugin-list',
+ filter: data.params.filter || null,
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page(/^\/admin\/plugins(\/)?$/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.ADMIN,
+ adminView: 'gr-plugin-list',
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page('/admin/(.*)', loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ data.params.view = Gerrit.Nav.View.ADMIN;
+ data.params.placeholder = true;
+ this._setParams(data.params);
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ const queryHandler = data => {
+ data.params.view = Gerrit.Nav.View.SEARCH;
+ this._setParams(data.params);
+ };
+
+ page('/q/:query,:offset', queryHandler);
+ page('/q/:query', queryHandler);
+
+ page(/^\/(\d+)\/?/, ctx => {
+ this._redirect('/c/' + encodeURIComponent(ctx.params[0]));
+ });
+
+ // Matches
+ // /c/<project>/+/<changeNum>/
+ // [<basePatchNum|edit>..][<patchNum|edit>]/[path].
+ // TODO(kaspern): Migrate completely to project based URLs, with backwards
+ // compatibility for change-only.
+ // eslint-disable-next-line max-len
+ page(/^\/c\/(.+)\/\+\/(\d+)(\/?((\d+|edit)(\.\.(\d+|edit))?(\/(.+))?))?\/?$/,
+ ctx => {
+ // Parameter order is based on the regex group number matched.
+ const params = {
+ project: ctx.params[0],
+ changeNum: ctx.params[1],
+ basePatchNum: ctx.params[4],
+ patchNum: ctx.params[6],
+ path: ctx.params[8],
+ view: ctx.params[8] ?
+ Gerrit.Nav.View.DIFF : Gerrit.Nav.View.CHANGE,
+ hash: ctx.hash,
+ };
+ const needsRedirect = this._normalizePatchRangeParams(params);
+ if (needsRedirect) {
+ this._redirect(this._generateUrl(params));
+ } else {
+ this._setParams(params);
+ this._restAPI.setInProjectLookup(params.changeNum,
+ params.project);
+ }
+ });
+
+ // Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
+ page(/^\/c\/(\d+)\/?(((\d+|edit)(\.\.(\d+|edit))?))?\/?$/, ctx => {
+ // Parameter order is based on the regex group number matched.
+ const params = {
+ changeNum: ctx.params[0],
+ basePatchNum: ctx.params[3],
+ patchNum: ctx.params[5],
+ view: Gerrit.Nav.View.CHANGE,
+ };
+
+ this._normalizeLegacyRouteParams(params);
+ });
+
+ // Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
+ page(/^\/c\/(\d+)\/((\d+|edit)(\.\.(\d+|edit))?)\/(.+)/, ctx => {
+ // Check if path has an '@' which indicates it was using GWT style line
+ // numbers. Even if the filename had an '@' in it, it would have already
+ // been URI encoded. Redirect to hash version of path.
+ if (ctx.path.includes('@')) {
+ this._redirect(ctx.path.replace('@', '#'));
+ return;
+ }
+
+ // Parameter order is based on the regex group number matched.
+ const params = {
+ changeNum: ctx.params[0],
+ basePatchNum: ctx.params[2],
+ patchNum: ctx.params[4],
+ path: ctx.params[5],
+ hash: ctx.hash,
+ view: Gerrit.Nav.View.DIFF,
+ };
+
+ this._normalizeLegacyRouteParams(params);
+ });
+
+ page(/^\/settings\/(agreements|new-agreement)/, loadUser, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ data.params.view = Gerrit.Nav.View.AGREEMENTS;
+ this._setParams(data.params);
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page(/^\/settings\/VE\/(\S+)/, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({
+ view: Gerrit.Nav.View.SETTINGS,
+ emailToken: data.params[0],
+ });
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page(/^\/settings\/?/, data => {
+ this._restAPI.getLoggedIn().then(loggedIn => {
+ if (loggedIn) {
+ this._setParams({view: Gerrit.Nav.View.SETTINGS});
+ } else {
+ this._redirectToLogin(data.canonicalPath);
+ }
+ });
+ });
+
+ page(/^\/register(\/.*)?/, ctx => {
+ this._setParams({justRegistered: true});
+ const path = ctx.params[0] || '/';
+ if (path[0] !== '/') { return; }
+ page.show(base + path);
+ });
+
+ page.start();
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index d1392ee..51fcfc1 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -33,13 +33,17 @@
<script>
suite('gr-router tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => { sandbox.restore(); });
+
suite('generateUrl', () => {
- let element;
-
- setup(() => {
- element = fixture('basic');
- });
-
test('search', () => {
let params = {
view: Gerrit.Nav.View.SEARCH,
@@ -109,6 +113,104 @@
assert.equal(element._generateUrl(params),
'/c/test/+/42/2/file.cpp#b123');
});
+
+ test('_getPatchRangeExpression', () => {
+ const params = {};
+ let actual = element._getPatchRangeExpression(params);
+ assert.equal(actual, '');
+
+ params.patchNum = 4;
+ actual = element._getPatchRangeExpression(params);
+ assert.equal(actual, '4');
+
+ params.basePatchNum = 2;
+ actual = element._getPatchRangeExpression(params);
+ assert.equal(actual, '2..4');
+
+ delete params.patchNum;
+ actual = element._getPatchRangeExpression(params);
+ assert.equal(actual, '2..');
+ });
+ });
+
+ suite('param normalization', () => {
+ let projectLookupStub;
+
+ setup(() => {
+ projectLookupStub = sandbox
+ .stub(element._restAPI, 'getFromProjectLookup')
+ .returns(Promise.resolve('foo/bar'));
+ sandbox.stub(element, '_generateUrl');
+ });
+
+ suite('_normalizeLegacyRouteParams', () => {
+ let rangeStub;
+
+ setup(() => {
+ rangeStub = sandbox.stub(element, '_normalizePatchRangeParams')
+ .returns(Promise.resolve());
+ });
+
+ test('w/o changeNum', () => {
+ const params = {};
+ return element._normalizeLegacyRouteParams(params).then(() => {
+ assert.isFalse(projectLookupStub.called);
+ assert.isFalse(rangeStub.called);
+ assert.isNotOk(params.project);
+ });
+ });
+
+ test('w/ changeNum', () => {
+ const params = {changeNum: 1234};
+ return element._normalizeLegacyRouteParams(params).then(() => {
+ assert.isTrue(projectLookupStub.called);
+ assert.isTrue(rangeStub.called);
+ assert.equal(params.project, 'foo/bar');
+ });
+ });
+ });
+
+ suite('_normalizePatchRangeParams', () => {
+ test('range n..n normalizes to n', () => {
+ const params = {basePatchNum: 4, patchNum: 4};
+ const needsRedirect = element._normalizePatchRangeParams(params);
+ assert.isTrue(needsRedirect);
+ assert.isNotOk(params.basePatchNum);
+ assert.equal(params.patchNum, 4);
+ });
+
+ test('range n.. normalizes to n', () => {
+ const params = {basePatchNum: 4};
+ const needsRedirect = element._normalizePatchRangeParams(params);
+ assert.isFalse(needsRedirect);
+ assert.isNotOk(params.basePatchNum);
+ assert.equal(params.patchNum, 4);
+ });
+
+ test('range 0..n normalizes to edit..n', () => {
+ const params = {basePatchNum: 0, patchNum: 4};
+ const needsRedirect = element._normalizePatchRangeParams(params);
+ assert.isTrue(needsRedirect);
+ assert.equal(params.basePatchNum, 'edit');
+ assert.equal(params.patchNum, 4);
+ });
+
+ test('range n..0 normalizes to n..edit', () => {
+ const params = {basePatchNum: 4, patchNum: 0};
+ const needsRedirect = element._normalizePatchRangeParams(params);
+ assert.isTrue(needsRedirect);
+ assert.equal(params.basePatchNum, 4);
+ assert.equal(params.patchNum, 'edit');
+ });
+
+ test('range 0..0 normalizes to edit', () => {
+ const params = {basePatchNum: 0, patchNum: 0};
+ const needsRedirect = element._normalizePatchRangeParams(params);
+ assert.isTrue(needsRedirect);
+ assert.isNotOk(params.basePatchNum);
+ assert.equal(params.patchNum, 'edit');
+ });
+ });
});
});
</script>