Merge "Fix: Default values for Elastic index.* config values"
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 39fa333..232e402 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -30,7 +30,8 @@
AutoAnnotation_Commands_named cannot be resolved to a type
----
-In Eclipse, choose 'Import existing project' and select the `gerrit` project
+First, generate the Eclipse project by running the `tools/eclipse/project.py` script.
+Then, in Eclipse, choose 'Import existing project' and select the `gerrit` project
from the current working directory.
Expand the `gerrit` project, right-click on the `eclipse-out` folder, select
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index b6d2c53..04adcbd 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -736,6 +736,68 @@
}
----
+[[query_attributes]]
+=== Query Attributes ===
+
+Plugins can provide additional attributes to be returned in Gerrit queries by
+implementing the ChangeAttributeFactory interface and registering it to the
+ChangeQueryProcessor.ChangeAttributeFactory class in the plugin module's
+'configure()' method. The new attribute(s) will be output under a "plugin"
+attribute in the change query output.
+
+The example below shows a plugin that adds two attributes ('exampleName' and
+'changeValue'), to the change query output.
+
+[source, java]
+----
+public class Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(ChangeAttributeFactory.class)
+ .annotatedWith(Exports.named("example"))
+ .to(AttributeFactory.class);
+ }
+}
+
+public class AttributeFactory implements ChangeAttributeFactory {
+
+ public class PluginAttribute extends PluginDefinedInfo {
+ public String exampleName;
+ public String changeValue;
+
+ public PluginAttribute(ChangeData c) {
+ this.exampleName = "Attribute Example";
+ this.changeValue = Integer.toString(c.getId().get());
+ }
+ }
+
+ @Override
+ public PluginDefinedInfo create(ChangeData c, ChangeQueryProcessor qp, String plugin) {
+ return new PluginAttribute(c);
+ }
+}
+----
+
+Example
+----
+
+ssh -p 29418 localhost gerrit query "change:1" --format json
+
+Output:
+
+{
+ "url" : "http://localhost:8080/1",
+ "plugins" : [
+ {
+ "name" : "myplugin-name",
+ "exampleName" : "Attribute Example",
+ "changeValue" : "1"
+ }
+ ],
+ ...
+}
+----
+
[[simple-configuration]]
== Simple Configuration in `gerrit.config`
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index dc97f97..2d417c5 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -3422,7 +3422,7 @@
}
----
-As response a link:#review-info[ReviewInfo] entity is returned that
+As response a link:#review-result[ReviewResult] entity is returned that
describes the applied labels.
.Response
@@ -6275,6 +6275,24 @@
representing reviewers that should be added to the change.
|============================
+[[review-result]]
+=== ReviewResult
+The `ReviewResult` entity contains information regarding the updates
+that were made to a review.
+
+[options="header",cols="1,^1,5"]
+|============================
+|Field Name ||Description
+|`labels` |optional|
+Map of labels to values after the review was posted. Null if any reviewer
+additions were rejected.
+|`reviewers` |optional|
+Map of account or group identifier to
+link:rest-api-changes.html#add-reviewer-result[AddReviewerResult]
+representing the outcome of adding as a reviewer.
+Absent if no reviewer additions were requested.
+|============================
+
[[reviewer-info]]
=== ReviewerInfo
The `ReviewerInfo` entity contains information about a reviewer and its
@@ -6383,6 +6401,9 @@
patch set as a link:#push-certificate-info[PushCertificateInfo] entity.
This field is always set if the option is requested; if no push
certificate was provided, it is set to an empty object.
+|`description` |optional|
+The description of this patchset, as displayed in the patchset
+selector menu. May be null if no description is set.
|===========================
[[robot-comment-info]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 62e3ee4..fe2025c 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -138,6 +138,51 @@
}
----
+[[check-consistency]]
+=== Check Consistency
+--
+'POST /config/server/check'
+--
+
+Runs consistency checks and returns detected problems.
+
+Input for the consistency checks that should be run must be provided in
+the request body inside a
+link:#consistency-check-input[ConsistencyCheckInput] entity.
+
+.Request
+----
+ POST /config/server/check HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "check_account_external_ids": {}
+ }
+----
+
+As result a link:#consistency-check-info[ConsistencyCheckInfo] entity
+is returned that contains detected consistency problems.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "results": {
+ "account_external_id_result": {
+ "problems": [
+ {
+ "status": "ERROR",
+ "message": "External ID \u0027uuid:ccb8d323-1361-45aa-8874-41987a660c46\u0027 belongs to account that doesn\u0027t exist: 1000012"
+ }
+ ]
+ }
+ }
+ }
+----
+
[[confirm-email]]
=== Confirm Email
--
@@ -1365,6 +1410,66 @@
the whole topic is submitted].
|=============================
+[[check-account-external-ids-input]]
+=== CheckAccountExternalIdsInput
+The `CheckAccountExternalIdsInput` entity contains input for the
+account external IDs consistency check.
+
+Currently this entity contains no fields.
+
+[[check-account-external-ids-result-info]]
+=== CheckAccountExternalIdsResultInfo
+The `CheckAccountExternalIdsResultInfo` entity contains the result of
+running the account external IDs consistency check.
+
+[options="header",cols="1,6"]
+|======================
+|Field Name|Description
+|`problems`|A list of link:#consistency-problem-info[
+ConsistencyProblemInfo] entities.
+|======================
+
+[[consistency-check-info]]
+=== ConsistencyCheckInfo
+The `ConsistencyCheckInfo` entity contains the results of running
+consistency checks.
+
+[options="header",cols="1,^1,5"]
+|================================================
+|Field Name ||Description
+|`check_account_external_ids_result`|optional|
+The result of running the account external ID consistency check as a
+link:#check-account-external-ids-result-info[
+CheckAccountExternalIdsResultInfo] entity.
+|================================================
+
+[[consistency-check-input]]
+=== ConsistencyCheckInput
+The `ConsistencyCheckInput` entity contains information about which
+consistency checks should be run.
+
+[options="header",cols="1,^1,5"]
+|=========================================
+|Field Name ||Description
+|`check_account_external_ids`|optional|
+Input for the account external ID consistency check as
+link:#check-account-external-ids-input[CheckAccountExternalIdsInput]
+entity.
+|=========================================
+
+[[consistency-problem-info]]
+=== ConsistencyProblemInfo
+The `ConsistencyProblemInfo` entity contains information about a
+consistency problem.
+
+[options="header",cols="1,6"]
+|======================
+|Field Name|Description
+|`status` |The status of the consistency problem. +
+Possible values are `ERROR` and `WARNING`.
+|`message` |Message describing the consistency problem.
+|======================
+
[[download-info]]
=== DownloadInfo
The `DownloadInfo` entity contains information about supported download
diff --git a/WORKSPACE b/WORKSPACE
index 0ccbe3d..2e95e9a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -409,8 +409,8 @@
maven_jar(
name = "auto_value",
- artifact = "com.google.auto.value:auto-value:1.4",
- sha1 = "6d1448fcd13074bd3658ef915022410b7c48343b",
+ artifact = "com.google.auto.value:auto-value:1.4.1",
+ sha1 = "8172ebbd7970188aff304c8a420b9f17168f6f48",
)
maven_jar(
@@ -701,9 +701,10 @@
sha1 = "2862787ce34cb6f385ada891e36ec7f9e7bd0902",
)
+# When bumping the easymock version number, make sure to also move powermock to a compatible version
maven_jar(
name = "easymock",
- artifact = "org.easymock:easymock:3.1", # When bumping the version
+ artifact = "org.easymock:easymock:3.1",
sha1 = "3e127311a86fc2e8f550ef8ee4abe094bbcf7e7e",
)
diff --git a/contrib/abandon_stale.py b/contrib/abandon_stale.py
index f62c767..44e7e0e 100755
--- a/contrib/abandon_stale.py
+++ b/contrib/abandon_stale.py
@@ -59,7 +59,11 @@
help='gerrit server URL')
parser.add_option('-b', '--basic-auth', dest='basic_auth',
action='store_true',
- help='use HTTP basic authentication instead of digest')
+ help='(deprecated) use HTTP basic authentication instead'
+ ' of digest')
+ parser.add_option('-d', '--digest-auth', dest='digest_auth',
+ action='store_true',
+ help='use HTTP digest authentication instead of basic')
parser.add_option('-n', '--dry-run', dest='dry_run',
action='store_true',
help='enable dry-run mode: show stale changes but do '
@@ -115,10 +119,10 @@
message = "Abandoning after %s %s or more of inactivity." % \
(match.group(1), match.group(2))
- if options.basic_auth:
- auth_type = HTTPBasicAuthFromNetrc
- else:
+ if options.digest_auth:
auth_type = HTTPDigestAuthFromNetrc
+ else:
+ auth_type = HTTPBasicAuthFromNetrc
try:
auth = auth_type(url=options.gerrit_url)
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
index b77c41a..0e3dffe 100644
--- a/contrib/populate-fixture-data.py
+++ b/contrib/populate-fixture-data.py
@@ -46,7 +46,7 @@
PLUGINS_URL = BASE_URL + "plugins/"
PROJECTS_URL = BASE_URL + "projects/"
-ADMIN_DIGEST = requests.auth.HTTPDigestAuth("admin", "secret")
+ADMIN_BASIC_AUTH = requests.auth.HTTPBasicAuth("admin", "secret")
# GROUP_ADMIN stores a GroupInfo for the admin group (see Gerrit rest docs)
# In addition, GROUP_ADMIN["name"] stores the admin group"s name.
@@ -151,8 +151,8 @@
return json_string
-def digest_auth(user):
- return requests.auth.HTTPDigestAuth(user["username"], user["http_password"])
+def basic_auth(user):
+ return requests.auth.HTTPBasicAuth(user["username"], user["http_password"])
def fetch_admin_group():
@@ -160,7 +160,7 @@
# Get admin group
r = json.loads(clean(requests.get(GROUPS_URL + "?suggest=ad&p=All-Projects",
headers=HEADERS,
- auth=ADMIN_DIGEST).text))
+ auth=ADMIN_BASIC_AUTH).text))
admin_group_name = r.keys()[0]
GROUP_ADMIN = r[admin_group_name]
GROUP_ADMIN["name"] = admin_group_name
@@ -225,7 +225,7 @@
requests.put(GROUPS_URL + g["name"],
json.dumps(g),
headers=HEADERS,
- auth=ADMIN_DIGEST)
+ auth=ADMIN_BASIC_AUTH)
return [g["name"] for g in groups]
@@ -247,7 +247,7 @@
requests.put(PROJECTS_URL + p["name"],
json.dumps(p),
headers=HEADERS,
- auth=ADMIN_DIGEST)
+ auth=ADMIN_BASIC_AUTH)
return [p["name"] for p in projects]
@@ -256,7 +256,7 @@
requests.put(ACCOUNTS_URL + user["username"],
json.dumps(user),
headers=HEADERS,
- auth=ADMIN_DIGEST)
+ auth=ADMIN_BASIC_AUTH)
def create_change(user, project_name):
@@ -270,7 +270,7 @@
requests.post(CHANGES_URL,
json.dumps(change),
headers=HEADERS,
- auth=digest_auth(user))
+ auth=basic_auth(user))
def clean_up():
diff --git a/gerrit-acceptance-framework/pom.xml b/gerrit-acceptance-framework/pom.xml
index 246b1f9..152a003 100644
--- a/gerrit-acceptance-framework/pom.xml
+++ b/gerrit-acceptance-framework/pom.xml
@@ -26,6 +26,9 @@
<name>Andrew Bonventre</name>
</developer>
<developer>
+ <name>Becky Siegel</name>
+ </developer>
+ <developer>
<name>Dave Borowitz</name>
</developer>
<developer>
@@ -41,6 +44,12 @@
<name>Hugo Arès</name>
</developer>
<developer>
+ <name>Kasper Nilsson</name>
+ </developer>
+ <developer>
+ <name>Logan Hanks</name>
+ </developer>
+ <developer>
<name>Martin Fick</name>
</developer>
<developer>
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
index cef6128..711b8cf 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -53,7 +53,7 @@
private final Map<String, TestAccount> accounts;
private final SchemaFactory<ReviewDb> reviewDbProvider;
- private final AccountsUpdate accountsUpdate;
+ private final AccountsUpdate.Server accountsUpdate;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
private final GroupCache groupCache;
private final SshKeyCache sshKeyCache;
@@ -65,7 +65,7 @@
@Inject
AccountCreator(
SchemaFactory<ReviewDb> schema,
- AccountsUpdate accountsUpdate,
+ AccountsUpdate.Server accountsUpdate,
VersionedAuthorizedKeys.Accessor authorizedKeys,
GroupCache groupCache,
SshKeyCache sshKeyCache,
@@ -114,7 +114,7 @@
Account a = new Account(id, TimeUtil.nowTs());
a.setFullName(fullName);
a.setPreferredEmail(email);
- accountsUpdate.insert(db, a);
+ accountsUpdate.create().insert(db, a);
if (groups != null) {
for (String n : groups) {
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 89585c3..079c666 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
@@ -51,7 +51,6 @@
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.StarsInput;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
@@ -74,6 +73,7 @@
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.testutil.ConfigSuite;
@@ -100,6 +100,8 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushCertificateIdent;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate;
@@ -184,6 +186,26 @@
}
@Test
+ public void create() throws Exception {
+ TestAccount foo = accounts.create("foo");
+ AccountInfo info = gApi.accounts().id(foo.id.get()).get();
+ assertThat(info.username).isEqualTo("foo");
+
+ // check user branch
+ try (Repository repo = repoManager.openRepository(allUsers);
+ RevWalk rw = new RevWalk(repo)) {
+ Ref ref = repo.exactRef(RefNames.refsUsers(foo.getId()));
+ assertThat(ref).isNotNull();
+ RevCommit c = rw.parseCommit(ref.getObjectId());
+ long timestampDiffMs =
+ Math.abs(
+ c.getCommitTime() * 1000L
+ - accountCache.get(foo.getId()).getAccount().getRegisteredOn().getTime());
+ assertThat(timestampDiffMs).isAtMost(ChangeRebuilderImpl.MAX_WINDOW_MS);
+ }
+ }
+
+ @Test
public void get() throws Exception {
AccountInfo info = gApi.accounts().id("admin").get();
assertThat(info.name).isEqualTo("Administrator");
@@ -511,7 +533,7 @@
// user cannot delete email of admin
exception.expect(AuthException.class);
- exception.expectMessage("not allowed to delete email address");
+ exception.expectMessage("modify account not permitted");
gApi.accounts().id(admin.id.get()).deleteEmail(admin.email);
}
@@ -553,7 +575,7 @@
@Test
@Sandboxed
public void fetchUserBranch() throws Exception {
- ensureUserBranchCreated(user);
+ setApiUser(user);
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, user);
String userRefName = RefNames.refsUsers(user.id);
@@ -603,8 +625,6 @@
@Test
public void pushToUserBranch() throws Exception {
- ensureUserBranchCreated(admin);
-
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.refsUsers(admin.id) + ":userRef");
allUsersRepo.reset("userRef");
@@ -617,8 +637,6 @@
@Test
public void pushToUserBranchForReview() throws Exception {
- ensureUserBranchCreated(admin);
-
String userRefName = RefNames.refsUsers(admin.id);
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, userRefName + ":userRef");
@@ -640,8 +658,6 @@
@Test
public void pushWatchConfigToUserBranch() throws Exception {
- ensureUserBranchCreated(admin);
-
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
fetch(allUsersRepo, RefNames.refsUsers(admin.id) + ":userRef");
allUsersRepo.reset("userRef");
@@ -683,8 +699,6 @@
@Test
@Sandboxed
public void cannotDeleteUserBranch() throws Exception {
- ensureUserBranchCreated(admin);
-
grant(
Permission.DELETE,
allUsers,
@@ -707,8 +721,6 @@
@Test
@Sandboxed
public void deleteUserBranchWithAccessDatabaseCapability() throws Exception {
- ensureUserBranchCreated(admin);
-
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
grant(
Permission.DELETE,
@@ -896,7 +908,7 @@
// user cannot reindex any account
exception.expect(AuthException.class);
- exception.expectMessage("not allowed to index account");
+ exception.expectMessage("modify account not permitted");
gApi.accounts().id(admin.username).index();
}
@@ -1019,12 +1031,4 @@
assertThat(accounts).hasSize(1);
assertThat(Iterables.getOnlyElement(accounts)).isEqualTo(expectedAccount.getId());
}
-
- private void ensureUserBranchCreated(TestAccount account) throws Exception {
- // Change something in the user preferences to ensure that the user branch is created.
- setApiUser(account);
- GeneralPreferencesInfo input = new GeneralPreferencesInfo();
- input.changesPerPage = GeneralPreferencesInfo.defaults().changesPerPage + 10;
- gApi.accounts().self().setPreferences(input);
- }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index d5d4620..f6c70b0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -19,6 +19,8 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_MAILTO;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.fail;
import com.github.rholder.retry.BlockStrategy;
@@ -33,7 +35,12 @@
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInput.CheckAccountExternalIdsInput;
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -54,6 +61,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -61,11 +69,13 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.MutableInteger;
import org.junit.Test;
@Sandboxed
@@ -199,6 +209,277 @@
}
@Test
+ @GerritConfig(name = "user.readExternalIdsFromGit", value = "true")
+ public void readExternalIdsWhenInvalidExternalIdsExist() throws Exception {
+ allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ resetCurrentApiUser();
+
+ insertValidExternalIds();
+ insertInvalidButParsableExternalIds();
+
+ Set<ExternalId> parseableExtIds = externalIds.all(db);
+
+ insertNonParsableExternalIds();
+
+ Set<ExternalId> extIds = externalIds.all(db);
+ assertThat(extIds).containsExactlyElementsIn(parseableExtIds);
+
+ for (ExternalId parseableExtId : parseableExtIds) {
+ ExternalId extId = externalIds.get(db, parseableExtId.key());
+ assertThat(extId).isEqualTo(parseableExtId);
+ }
+ }
+
+ @Test
+ public void checkConsistency() throws Exception {
+ allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
+ resetCurrentApiUser();
+
+ insertValidExternalIds();
+
+ ConsistencyCheckInput input = new ConsistencyCheckInput();
+ input.checkAccountExternalIds = new CheckAccountExternalIdsInput();
+ ConsistencyCheckInfo checkInfo = gApi.config().server().checkConsistency(input);
+ assertThat(checkInfo.checkAccountExternalIdsResult.problems).isEmpty();
+
+ Set<ConsistencyProblemInfo> expectedProblems = new HashSet<>();
+ expectedProblems.addAll(insertInvalidButParsableExternalIds());
+ expectedProblems.addAll(insertNonParsableExternalIds());
+
+ checkInfo = gApi.config().server().checkConsistency(input);
+ assertThat(checkInfo.checkAccountExternalIdsResult.problems).hasSize(expectedProblems.size());
+ assertThat(checkInfo.checkAccountExternalIdsResult.problems)
+ .containsExactlyElementsIn(expectedProblems);
+ }
+
+ @Test
+ public void checkConsistencyNotAllowed() throws Exception {
+ exception.expect(AuthException.class);
+ exception.expectMessage("not allowed to run consistency checks");
+ gApi.config().server().checkConsistency(new ConsistencyCheckInput());
+ }
+
+ private ConsistencyProblemInfo consistencyError(String message) {
+ return new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.ERROR, message);
+ }
+
+ private void insertValidExternalIds() throws IOException, ConfigInvalidException, OrmException {
+ MutableInteger i = new MutableInteger();
+ String scheme = "valid";
+ ExternalIdsUpdate u = extIdsUpdate.create();
+
+ // create valid external IDs
+ u.insert(
+ db,
+ ExternalId.createWithPassword(
+ ExternalId.Key.parse(nextId(scheme, i)),
+ admin.id,
+ "admin.other@example.com",
+ "secret-password"));
+ u.insert(db, createExternalIdWithOtherCaseEmail(nextId(scheme, i)));
+ }
+
+ private Set<ConsistencyProblemInfo> insertInvalidButParsableExternalIds()
+ throws IOException, ConfigInvalidException, OrmException {
+ MutableInteger i = new MutableInteger();
+ String scheme = "invalid";
+ ExternalIdsUpdate u = extIdsUpdate.create();
+
+ Set<ConsistencyProblemInfo> expectedProblems = new HashSet<>();
+ ExternalId extIdForNonExistingAccount =
+ createExternalIdForNonExistingAccount(nextId(scheme, i));
+ u.insert(db, extIdForNonExistingAccount);
+ expectedProblems.add(
+ consistencyError(
+ "External ID '"
+ + extIdForNonExistingAccount.key().get()
+ + "' belongs to account that doesn't exist: "
+ + extIdForNonExistingAccount.accountId().get()));
+
+ ExternalId extIdWithInvalidEmail = createExternalIdWithInvalidEmail(nextId(scheme, i));
+ u.insert(db, extIdWithInvalidEmail);
+ expectedProblems.add(
+ consistencyError(
+ "External ID '"
+ + extIdWithInvalidEmail.key().get()
+ + "' has an invalid email: "
+ + extIdWithInvalidEmail.email()));
+
+ ExternalId extIdWithDuplicateEmail = createExternalIdWithDuplicateEmail(nextId(scheme, i));
+ u.insert(db, extIdWithDuplicateEmail);
+ expectedProblems.add(
+ consistencyError(
+ "Email '"
+ + extIdWithDuplicateEmail.email()
+ + "' is not unique, it's used by the following external IDs: '"
+ + extIdWithDuplicateEmail.key().get()
+ + "', 'mailto:"
+ + extIdWithDuplicateEmail.email()
+ + "'"));
+
+ ExternalId extIdWithBadPassword = createExternalIdWithBadPassword("admin-username");
+ u.insert(db, extIdWithBadPassword);
+ expectedProblems.add(
+ consistencyError(
+ "External ID '"
+ + extIdWithBadPassword.key().get()
+ + "' has an invalid password: unrecognized algorithm"));
+
+ return expectedProblems;
+ }
+
+ private Set<ConsistencyProblemInfo> insertNonParsableExternalIds() throws IOException {
+ MutableInteger i = new MutableInteger();
+ String scheme = "corrupt";
+
+ Set<ConsistencyProblemInfo> expectedProblems = new HashSet<>();
+ try (Repository repo = repoManager.openRepository(allUsers);
+ RevWalk rw = new RevWalk(repo)) {
+ String externalId = nextId(scheme, i);
+ String noteId = insertExternalIdWithoutAccountId(repo, rw, externalId);
+ expectedProblems.add(
+ consistencyError(
+ "Invalid external ID config for note '"
+ + noteId
+ + "': Value for 'externalId."
+ + externalId
+ + ".accountId' is missing, expected account ID"));
+
+ externalId = nextId(scheme, i);
+ noteId = insertExternalIdWithKeyThatDoesntMatchNoteId(repo, rw, externalId);
+ expectedProblems.add(
+ consistencyError(
+ "Invalid external ID config for note '"
+ + noteId
+ + "': SHA1 of external ID '"
+ + externalId
+ + "' does not match note ID '"
+ + noteId
+ + "'"));
+
+ noteId = insertExternalIdWithInvalidConfig(repo, rw, nextId(scheme, i));
+ expectedProblems.add(
+ consistencyError(
+ "Invalid external ID config for note '" + noteId + "': Invalid line in config file"));
+
+ noteId = insertExternalIdWithEmptyNote(repo, rw, nextId(scheme, i));
+ expectedProblems.add(
+ consistencyError(
+ "Invalid external ID config for note '"
+ + noteId
+ + "': Expected exactly 1 'externalId' section, found 0"));
+ }
+
+ return expectedProblems;
+ }
+
+ private ExternalId createExternalIdWithOtherCaseEmail(String externalId) {
+ return ExternalId.createWithPassword(
+ ExternalId.Key.parse(externalId), admin.id, admin.email.toUpperCase(Locale.US), "password");
+ }
+
+ private String insertExternalIdWithoutAccountId(Repository repo, RevWalk rw, String externalId)
+ throws IOException {
+ ObjectId rev = ExternalIdReader.readRevision(repo);
+ NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
+
+ ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id);
+
+ try (ObjectInserter ins = repo.newObjectInserter()) {
+ ObjectId noteId = extId.key().sha1();
+ Config c = new Config();
+ extId.writeToConfig(c);
+ c.unset("externalId", extId.key().get(), "accountId");
+ byte[] raw = c.toText().getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+
+ ExternalIdsUpdate.commit(
+ repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+ return noteId.getName();
+ }
+ }
+
+ private String insertExternalIdWithKeyThatDoesntMatchNoteId(
+ Repository repo, RevWalk rw, String externalId) throws IOException {
+ ObjectId rev = ExternalIdReader.readRevision(repo);
+ NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
+
+ ExternalId extId = ExternalId.create(ExternalId.Key.parse(externalId), admin.id);
+
+ try (ObjectInserter ins = repo.newObjectInserter()) {
+ ObjectId noteId = ExternalId.Key.parse(externalId + "x").sha1();
+ Config c = new Config();
+ extId.writeToConfig(c);
+ byte[] raw = c.toText().getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+
+ ExternalIdsUpdate.commit(
+ repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+ return noteId.getName();
+ }
+ }
+
+ private String insertExternalIdWithInvalidConfig(Repository repo, RevWalk rw, String externalId)
+ throws IOException {
+ ObjectId rev = ExternalIdReader.readRevision(repo);
+ NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
+
+ try (ObjectInserter ins = repo.newObjectInserter()) {
+ ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+ byte[] raw = "bad-config".getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+
+ ExternalIdsUpdate.commit(
+ repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+ return noteId.getName();
+ }
+ }
+
+ private String insertExternalIdWithEmptyNote(Repository repo, RevWalk rw, String externalId)
+ throws IOException {
+ ObjectId rev = ExternalIdReader.readRevision(repo);
+ NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
+
+ try (ObjectInserter ins = repo.newObjectInserter()) {
+ ObjectId noteId = ExternalId.Key.parse(externalId).sha1();
+ byte[] raw = "".getBytes(UTF_8);
+ ObjectId dataBlob = ins.insert(OBJ_BLOB, raw);
+ noteMap.set(noteId, dataBlob);
+
+ ExternalIdsUpdate.commit(
+ repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+ return noteId.getName();
+ }
+ }
+
+ private ExternalId createExternalIdForNonExistingAccount(String externalId) {
+ return ExternalId.create(ExternalId.Key.parse(externalId), new Account.Id(1));
+ }
+
+ private ExternalId createExternalIdWithInvalidEmail(String externalId) {
+ return ExternalId.createWithEmail(ExternalId.Key.parse(externalId), admin.id, "invalid-email");
+ }
+
+ private ExternalId createExternalIdWithDuplicateEmail(String externalId) {
+ return ExternalId.createWithEmail(ExternalId.Key.parse(externalId), admin.id, admin.email);
+ }
+
+ private ExternalId createExternalIdWithBadPassword(String username) {
+ return ExternalId.create(
+ ExternalId.Key.create(SCHEME_USERNAME, username),
+ admin.id,
+ null,
+ "non-hashed-password-is-not-allowed");
+ }
+
+ private static String nextId(String scheme, MutableInteger i) {
+ return scheme + ":foo" + ++i.value;
+ }
+
+ @Test
public void retryOnLockFailure() throws Exception {
Retryer<RefsMetaExternalIdsUpdate> retryer =
ExternalIdsUpdate.retryerBuilder()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
index 846c580..0809bf2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -23,12 +23,17 @@
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.AddReviewerResult;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.NotifyInfo;
+import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.ReviewResult;
import com.google.gerrit.extensions.client.ReviewerState;
@@ -681,6 +686,40 @@
assertThat(changeLabels.get(crLabel).all).isNull();
}
+ @Test
+ public void notifyDetailsWorkOnPostReview() throws Exception {
+ PushOneCommit.Result r = createChange();
+ TestAccount userToNotify = createAccounts(1, "notify-details-post-review").get(0);
+
+ ReviewInput reviewInput = new ReviewInput();
+ reviewInput.reviewer(user.email, ReviewerState.REVIEWER, true);
+ reviewInput.notify = NotifyHandling.NONE;
+ reviewInput.notifyDetails =
+ ImmutableMap.of(RecipientType.TO, new NotifyInfo(ImmutableList.of(userToNotify.email)));
+
+ sender.clear();
+ gApi.changes().id(r.getChangeId()).current().review(reviewInput);
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).rcpt()).containsExactly(userToNotify.emailAddress);
+ }
+
+ @Test
+ public void notifyDetailsWorkOnPostReviewers() throws Exception {
+ PushOneCommit.Result r = createChange();
+ TestAccount userToNotify = createAccounts(1, "notify-details-post-reviewers").get(0);
+
+ AddReviewerInput addReviewer = new AddReviewerInput();
+ addReviewer.reviewer = user.email;
+ addReviewer.notify = NotifyHandling.NONE;
+ addReviewer.notifyDetails =
+ ImmutableMap.of(RecipientType.TO, new NotifyInfo(ImmutableList.of(userToNotify.email)));
+
+ sender.clear();
+ gApi.changes().id(r.getChangeId()).addReviewer(addReviewer);
+ assertThat(sender.getMessages()).hasSize(1);
+ assertThat(sender.getMessages().get(0).rcpt()).containsExactly(userToNotify.emailAddress);
+ }
+
private AddReviewerResult addReviewer(String changeId, String reviewer) throws Exception {
return addReviewer(changeId, reviewer, SC_OK);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index f7fe4f1..329716f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -41,6 +41,7 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ConsistencyChecker;
import com.google.gerrit.server.change.PatchSetInserter;
@@ -90,6 +91,8 @@
@Inject private Sequences sequences;
+ @Inject private AccountsUpdate.Server accountsUpdate;
+
private RevCommit tip;
private Account.Id adminId;
private ConsistencyChecker checker;
@@ -119,7 +122,7 @@
public void missingOwner() throws Exception {
TestAccount owner = accounts.create("missing");
ChangeControl ctl = insertChange(owner);
- db.accounts().deleteKeys(singleton(owner.getId()));
+ accountsUpdate.create().deleteByKey(db, owner.getId());
assertProblems(ctl, null, problem("Missing change owner: " + owner.getId()));
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
index b1efb4a..19fcff9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/GarbageCollectionIT.java
@@ -83,10 +83,7 @@
assertThat(userSshSession.hasError()).isTrue();
String error = userSshSession.getError();
assertThat(error).isNotNull();
- assertError(
- "One of the following capabilities is required to access this"
- + " resource: [runGC, maintainServer]",
- error);
+ assertError("maintain server not permitted", error);
}
@Test
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index 566e159..53e7c19 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -68,6 +68,6 @@
@Provides
@Singleton
IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
- return IndexConfig.fromConfig(cfg);
+ return IndexConfig.fromConfig(cfg).separateChangeSubIndexes(true).build();
}
}
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index 45b6f8b..2c884be 100644
--- a/gerrit-extension-api/pom.xml
+++ b/gerrit-extension-api/pom.xml
@@ -26,6 +26,9 @@
<name>Andrew Bonventre</name>
</developer>
<developer>
+ <name>Becky Siegel</name>
+ </developer>
+ <developer>
<name>Dave Borowitz</name>
</developer>
<developer>
@@ -41,6 +44,12 @@
<name>Hugo Arès</name>
</developer>
<developer>
+ <name>Kasper Nilsson</name>
+ </developer>
+ <developer>
+ <name>Logan Hanks</name>
+ </developer>
+ <developer>
<name>Martin Fick</name>
</developer>
<developer>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/ConsistencyCheckInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/ConsistencyCheckInfo.java
new file mode 100644
index 0000000..e3b7dd3
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/ConsistencyCheckInfo.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.config;
+
+import java.util.List;
+import java.util.Objects;
+
+public class ConsistencyCheckInfo {
+ public CheckAccountExternalIdsResultInfo checkAccountExternalIdsResult;
+
+ public static class CheckAccountExternalIdsResultInfo {
+ public List<ConsistencyProblemInfo> problems;
+
+ public CheckAccountExternalIdsResultInfo(List<ConsistencyProblemInfo> problems) {
+ this.problems = problems;
+ }
+ }
+
+ public static class ConsistencyProblemInfo {
+ public enum Status {
+ ERROR,
+ WARNING,
+ }
+
+ public final Status status;
+ public final String message;
+
+ public ConsistencyProblemInfo(Status status, String message) {
+ this.status = status;
+ this.message = message;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ConsistencyProblemInfo) {
+ ConsistencyProblemInfo other = ((ConsistencyProblemInfo) o);
+ return Objects.equals(status, other.status) && Objects.equals(message, other.message);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, message);
+ }
+
+ @Override
+ public String toString() {
+ return status.name() + ": " + message;
+ }
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/ConsistencyCheckInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/ConsistencyCheckInput.java
new file mode 100644
index 0000000..170db0f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/ConsistencyCheckInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.config;
+
+public class ConsistencyCheckInput {
+ public CheckAccountExternalIdsInput checkAccountExternalIds;
+
+ public static class CheckAccountExternalIdsInput {}
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java
index 97f4af0..ee0960c 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/config/Server.java
@@ -34,6 +34,8 @@
DiffPreferencesInfo setDefaultDiffPreferences(DiffPreferencesInfo in) throws RestApiException;
+ ConsistencyCheckInfo checkConsistency(ConsistencyCheckInput in) throws RestApiException;
+
/**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
@@ -68,5 +70,10 @@
public DiffPreferencesInfo setDefaultDiffPreferences(DiffPreferencesInfo in) {
throw new NotImplementedException();
}
+
+ @Override
+ public ConsistencyCheckInfo checkConsistency(ConsistencyCheckInput in) {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index e13962d..2cb8384 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -63,4 +63,5 @@
public Boolean _moreChanges;
public List<ProblemInfo> problems;
+ public List<PluginDefinedInfo> plugins;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginDefinedInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginDefinedInfo.java
new file mode 100644
index 0000000..e6fef0f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginDefinedInfo.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+public class PluginDefinedInfo {
+ public String name;
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/IndexServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/IndexServlet.java
index 4ac3da7..b55cf6c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/IndexServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/IndexServlet.java
@@ -47,7 +47,7 @@
.newRenderer("com.google.gerrit.httpd.raw.Index")
.setContentKind(SanitizedContent.ContentKind.HTML)
.setData(getTemplateData(canonicalURL, cdnPath));
- indexSource = renderer.render().getBytes();
+ indexSource = renderer.render().getBytes(UTF_8);
}
@Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
index 6dec9a4..6960fae 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
@@ -36,6 +36,7 @@
import com.google.gson.JsonPrimitive;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
+import com.google.inject.Injector;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashSet;
@@ -51,11 +52,16 @@
ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields");
private final CmdLineParser.Factory parserFactory;
+ private final Injector injector;
private final DynamicMap<DynamicOptions.DynamicBean> dynamicBeans;
@Inject
- ParameterParser(CmdLineParser.Factory pf, DynamicMap<DynamicOptions.DynamicBean> dynamicBeans) {
+ ParameterParser(
+ CmdLineParser.Factory pf,
+ Injector injector,
+ DynamicMap<DynamicOptions.DynamicBean> dynamicBeans) {
this.parserFactory = pf;
+ this.injector = injector;
this.dynamicBeans = dynamicBeans;
}
@@ -63,7 +69,7 @@
T param, ListMultimap<String, String> in, HttpServletRequest req, HttpServletResponse res)
throws IOException {
CmdLineParser clp = parserFactory.create(param);
- DynamicOptions pluginOptions = new DynamicOptions(param, dynamicBeans);
+ DynamicOptions pluginOptions = new DynamicOptions(param, injector, dynamicBeans);
pluginOptions.parseDynamicBeans(clp);
pluginOptions.setDynamicBeans();
pluginOptions.onBeanParseStart();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index abf5323..3385bf2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -95,8 +95,10 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.OptionUtil;
import com.google.gerrit.server.OutputFormat;
-import com.google.gerrit.server.account.CapabilityUtils;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.util.http.RequestUtil;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
@@ -188,6 +190,7 @@
final Provider<CurrentUser> currentUser;
final DynamicItem<WebSession> webSession;
final Provider<ParameterParser> paramParser;
+ final PermissionBackend permissionBackend;
final AuditService auditService;
final RestApiMetrics metrics;
final Pattern allowOrigin;
@@ -197,12 +200,14 @@
Provider<CurrentUser> currentUser,
DynamicItem<WebSession> webSession,
Provider<ParameterParser> paramParser,
+ PermissionBackend permissionBackend,
AuditService auditService,
RestApiMetrics metrics,
@GerritServerConfig Config cfg) {
this.currentUser = currentUser;
this.webSession = webSession;
this.paramParser = paramParser;
+ this.permissionBackend = permissionBackend;
this.auditService = auditService;
this.metrics = metrics;
allowOrigin = makeAllowOrigin(cfg);
@@ -263,7 +268,10 @@
List<IdString> path = splitPath(req);
RestCollection<RestResource, RestResource> rc = members.get();
- CapabilityUtils.checkRequiresCapability(globals.currentUser, null, rc.getClass());
+ globals
+ .permissionBackend
+ .user(globals.currentUser)
+ .checkAny(GlobalPermission.fromAnnotation(rc.getClass()));
viewData = new ViewData(null, null);
@@ -1106,9 +1114,12 @@
return "GET".equals(req.getMethod()) || "HEAD".equals(req.getMethod());
}
- private void checkRequiresCapability(ViewData viewData) throws AuthException {
- CapabilityUtils.checkRequiresCapability(
- globals.currentUser, viewData.pluginName, viewData.view.getClass());
+ private void checkRequiresCapability(ViewData d)
+ throws AuthException, PermissionBackendException {
+ globals
+ .permissionBackend
+ .user(globals.currentUser)
+ .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
}
private static long handleException(
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index 699fd51..89fd819 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -84,7 +84,7 @@
IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
BooleanQuery.setMaxClauseCount(
cfg.getInt("index", "maxTerms", BooleanQuery.getMaxClauseCount()));
- return IndexConfig.fromConfig(cfg);
+ return IndexConfig.fromConfig(cfg).separateChangeSubIndexes(true).build();
}
private static class MultiVersionModule extends LifecycleModule {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AccountsOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AccountsOnInit.java
new file mode 100644
index 0000000..09626d7
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/AccountsOnInit.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm.init;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdentProvider;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.util.FS;
+
+public class AccountsOnInit {
+ private final InitFlags flags;
+ private final SitePaths site;
+ private final String allUsers;
+
+ @Inject
+ public AccountsOnInit(InitFlags flags, SitePaths site, AllUsersNameOnInitProvider allUsers) {
+ this.flags = flags;
+ this.site = site;
+ this.allUsers = allUsers.get();
+ }
+
+ public void insert(ReviewDb db, Account account) throws OrmException, IOException {
+ db.accounts().insert(ImmutableSet.of(account));
+
+ File path = getPath();
+ if (path != null) {
+ try (Repository repo = new FileRepository(path);
+ ObjectInserter oi = repo.newObjectInserter()) {
+ PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get();
+ AccountsUpdate.createUserBranch(repo, oi, serverIdent, serverIdent, account);
+ }
+ }
+ }
+
+ private File getPath() {
+ Path basePath = site.resolve(flags.cfg.getString("gerrit", null, "basePath"));
+ checkArgument(basePath != null, "gerrit.basePath must be configured");
+ return FileKey.resolve(basePath.resolve(allUsers).toFile(), FS.DETECTED);
+ }
+}
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 529b7e7..1e6bfa8 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
@@ -29,7 +29,6 @@
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.account.AccountIndexCollection;
@@ -48,7 +47,7 @@
public class InitAdminUser implements InitStep {
private final ConsoleUI ui;
private final InitFlags flags;
- private final AccountsUpdate accountsUpdate;
+ private final AccountsOnInit accounts;
private final VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory;
private final ExternalIdsOnInit externalIds;
private SchemaFactory<ReviewDb> dbFactory;
@@ -58,12 +57,12 @@
InitAdminUser(
InitFlags flags,
ConsoleUI ui,
- AccountsUpdate accountsUpdate,
+ AccountsOnInit accounts,
VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory,
ExternalIdsOnInit externalIds) {
this.flags = flags;
this.ui = ui;
- this.accountsUpdate = accountsUpdate;
+ this.accounts = accounts;
this.authorizedKeysFactory = authorizedKeysFactory;
this.externalIds = externalIds;
}
@@ -110,7 +109,7 @@
Account a = new Account(id, TimeUtil.nowTs());
a.setFullName(name);
a.setPreferredEmail(email);
- accountsUpdate.insert(db, a);
+ accounts.insert(db, a);
AccountGroupName adminGroupName =
db.accountGroupNames().get(new AccountGroup.NameKey("Administrators"));
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
index 368cf7f..a52d8ba 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAuth.java
@@ -14,9 +14,14 @@
package com.google.gerrit.pgm.init;
+import static com.google.gerrit.extensions.client.GitBasicAuthPolicy.HTTP;
+import static com.google.gerrit.extensions.client.GitBasicAuthPolicy.HTTP_LDAP;
+import static com.google.gerrit.extensions.client.GitBasicAuthPolicy.LDAP;
+import static com.google.gerrit.extensions.client.GitBasicAuthPolicy.OAUTH;
import static com.google.gerrit.pgm.init.api.InitUtil.dnOf;
import com.google.gerrit.extensions.client.AuthType;
+import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InitStep;
@@ -24,6 +29,7 @@
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.util.EnumSet;
/** Initialize the {@code auth} configuration section. */
@Singleton
@@ -78,12 +84,32 @@
break;
}
+ case LDAP:
+ {
+ auth.select(
+ "Git/HTTP authentication",
+ "gitBasicAuthPolicy",
+ HTTP,
+ EnumSet.of(HTTP, HTTP_LDAP, LDAP));
+ break;
+ }
+ case OAUTH:
+ {
+ GitBasicAuthPolicy gitBasicAuth =
+ auth.select(
+ "Git/HTTP authentication", "gitBasicAuthPolicy", HTTP, EnumSet.of(HTTP, OAUTH));
+
+ if (gitBasicAuth == OAUTH) {
+ ui.message(
+ "*WARNING* Please make sure that your chosen OAuth provider\n"
+ + "supports Git token authentication.\n");
+ }
+ break;
+ }
case CLIENT_SSL_CERT_LDAP:
case CUSTOM_EXTENSION:
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
- case LDAP:
case LDAP_BIND:
- case OAUTH:
case OPENID:
case OPENID_SSO:
break;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
index 33c9bfe..18ccb1a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/ConsoleUI.java
@@ -16,7 +16,7 @@
import com.google.gerrit.common.Die;
import java.io.Console;
-import java.lang.reflect.InvocationTargetException;
+import java.util.EnumSet;
import java.util.Set;
/** Console based interaction with the invoking user. */
@@ -37,20 +37,6 @@
return new Die("aborted by user");
}
- /** Obtain all values from an enumeration. */
- @SuppressWarnings("unchecked")
- protected static <T extends Enum<?>> T[] all(final T value) {
- try {
- return (T[]) value.getDeclaringClass().getMethod("values").invoke(null);
- } catch (IllegalArgumentException
- | NoSuchMethodException
- | InvocationTargetException
- | IllegalAccessException
- | SecurityException e) {
- throw new IllegalArgumentException("Cannot obtain enumeration values", e);
- }
- }
-
/** @return true if this is a batch UI that has no user interaction. */
public abstract boolean isBatch();
@@ -95,7 +81,8 @@
}
/** Prompt the user to make a choice from an enumeration's values. */
- public abstract <T extends Enum<?>> T readEnum(T def, String fmt, Object... args);
+ public abstract <T extends Enum<?>, A extends EnumSet<? extends T>> T readEnum(
+ T def, A options, String fmt, Object... args);
private static class Interactive extends ConsoleUI {
private final Console console;
@@ -208,9 +195,9 @@
}
@Override
- public <T extends Enum<?>> T readEnum(T def, String fmt, Object... args) {
+ public <T extends Enum<?>, A extends EnumSet<? extends T>> T readEnum(
+ T def, A options, String fmt, Object... args) {
final String prompt = String.format(fmt, args);
- final T[] options = all(def);
for (; ; ) {
String r = console.readLine("%-30s [%s/?]: ", prompt, def.toString());
if (r == null) {
@@ -277,7 +264,8 @@
}
@Override
- public <T extends Enum<?>> T readEnum(T def, String fmt, Object... args) {
+ public <T extends Enum<?>, A extends EnumSet<? extends T>> T readEnum(
+ T def, A options, String fmt, Object... args) {
return def;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
index 7ec4604..d52005f 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/Section.java
@@ -22,6 +22,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.EnumSet;
import java.util.Set;
/** Helper to edit a section of the configuration files. */
@@ -110,15 +111,28 @@
return site.resolve(string(title, name, defValue));
}
- public <T extends Enum<?>> T select(final String title, final String name, final T defValue) {
+ public <T extends Enum<?>, E extends EnumSet<? extends T>> T select(
+ String title, String name, T defValue) {
return select(title, name, defValue, false);
}
- public <T extends Enum<?>> T select(
- final String title, final String name, final T defValue, final boolean nullIfDefault) {
+ public <T extends Enum<?>, E extends EnumSet<? extends T>> T select(
+ String title, String name, T defValue, boolean nullIfDefault) {
+ @SuppressWarnings("unchecked")
+ E allowedValues = (E) EnumSet.allOf(defValue.getClass());
+ return select(title, name, defValue, allowedValues, nullIfDefault);
+ }
+
+ public <T extends Enum<?>, E extends EnumSet<? extends T>> T select(
+ String title, String name, T defValue, E allowedValues) {
+ return select(title, name, defValue, allowedValues, false);
+ }
+
+ public <T extends Enum<?>, A extends EnumSet<? extends T>> T select(
+ String title, String name, T defValue, A allowedValues, final boolean nullIfDefault) {
final boolean set = get(name) != null;
T oldValue = flags.cfg.getEnum(section, subsection, name, defValue);
- T newValue = ui.readEnum(oldValue, "%s", title);
+ T newValue = ui.readEnum(oldValue, allowedValues, "%s", title);
if (nullIfDefault && newValue == defValue) {
newValue = null;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index c86d5af..e625219 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -68,6 +68,7 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SectionSortCache;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeQueryProcessor;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -112,6 +113,8 @@
bind(new TypeLiteral<List<CommentLinkInfo>>() {})
.toProvider(CommentLinkProvider.class)
.in(SINGLETON);
+ bind(new TypeLiteral<DynamicMap<ChangeQueryProcessor.ChangeAttributeFactory>>() {})
+ .toInstance(DynamicMap.<ChangeQueryProcessor.ChangeAttributeFactory>emptyMap());
bind(String.class)
.annotatedWith(CanonicalWebUrl.class)
.toProvider(CanonicalWebUrlProvider.class);
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index 608bcb1..a03f991 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/gerrit-plugin-api/pom.xml
@@ -26,6 +26,9 @@
<name>Andrew Bonventre</name>
</developer>
<developer>
+ <name>Becky Siegel</name>
+ </developer>
+ <developer>
<name>Dave Borowitz</name>
</developer>
<developer>
@@ -41,6 +44,12 @@
<name>Hugo Arès</name>
</developer>
<developer>
+ <name>Kasper Nilsson</name>
+ </developer>
+ <developer>
+ <name>Logan Hanks</name>
+ </developer>
+ <developer>
<name>Martin Fick</name>
</developer>
<developer>
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
index 048da36..d047206 100644
--- a/gerrit-plugin-gwtui/pom.xml
+++ b/gerrit-plugin-gwtui/pom.xml
@@ -26,6 +26,9 @@
<name>Andrew Bonventre</name>
</developer>
<developer>
+ <name>Becky Siegel</name>
+ </developer>
+ <developer>
<name>Dave Borowitz</name>
</developer>
<developer>
@@ -41,6 +44,12 @@
<name>Hugo Arès</name>
</developer>
<developer>
+ <name>Kasper Nilsson</name>
+ </developer>
+ <developer>
+ <name>Logan Hanks</name>
+ </developer>
+ <developer>
<name>Martin Fick</name>
</developer>
<developer>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java
index 82660cb..b8bc9f0 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/AccountGroupNameAccess.java
@@ -29,8 +29,4 @@
@Query("ORDER BY name")
ResultSet<AccountGroupName> all() throws OrmException;
-
- @Query("WHERE name.name >= ? AND name.name <= ? ORDER BY name LIMIT ?")
- ResultSet<AccountGroupName> suggestByName(String nameA, String nameB, int limit)
- throws OrmException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
index 2f3a76f..2d80ceb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
@@ -14,9 +14,11 @@
package com.google.gerrit.server;
+import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeTriplet;
+import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -29,13 +31,16 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
@Singleton
public class ChangeFinder {
+ private final IndexConfig indexConfig;
private final Provider<InternalChangeQuery> queryProvider;
@Inject
- ChangeFinder(Provider<InternalChangeQuery> queryProvider) {
+ ChangeFinder(IndexConfig indexConfig, Provider<InternalChangeQuery> queryProvider) {
+ this.indexConfig = indexConfig;
this.queryProvider = queryProvider;
}
@@ -93,8 +98,24 @@
private List<ChangeControl> asChangeControls(List<ChangeData> cds, CurrentUser user)
throws OrmException {
List<ChangeControl> ctls = new ArrayList<>(cds.size());
+ if (!indexConfig.separateChangeSubIndexes()) {
+ for (ChangeData cd : cds) {
+ ctls.add(cd.changeControl(user));
+ }
+ return ctls;
+ }
+
+ // If an index implementation uses separate non-atomic subindexes, it's possible to temporarily
+ // observe a change as present in both subindexes, if this search is concurrent with a write.
+ // Dedup to avoid confusing the caller. We can choose an arbitrary ChangeData instance because
+ // the index results have no stored fields, so the data is already reloaded. (It's also possible
+ // that a change might appear in zero subindexes, but there's nothing we can do here to help
+ // this case.)
+ Set<Change.Id> seen = Sets.newHashSetWithExpectedSize(cds.size());
for (ChangeData cd : cds) {
- ctls.add(cd.changeControl(user));
+ if (seen.add(cd.getId())) {
+ ctls.add(cd.changeControl(user));
+ }
}
return ctls;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java b/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java
index c66174d..6267dca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java
@@ -15,7 +15,9 @@
package com.google.gerrit.server;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.server.plugins.DelegatingClassLoader;
import com.google.gerrit.util.cli.CmdLineParser;
+import com.google.inject.Injector;
import com.google.inject.Provider;
import java.util.HashMap;
import java.util.Map;
@@ -80,15 +82,16 @@
void setDynamicBean(String plugin, DynamicBean dynamicBean);
}
- Object bean;
- Map<String, DynamicBean> beansByPlugin;
+ protected Object bean;
+ protected Map<String, DynamicBean> beansByPlugin;
+ protected Injector injector;
/**
* Internal: For Gerrit to include options from DynamicBeans, setup a DynamicMap and instantiate
* this class so the following methods can be called if desired:
*
* <pre>
- * DynamicOptions pluginOptions = new DynamicOptions(bean, dynamicBeans);
+ * DynamicOptions pluginOptions = new DynamicOptions(bean, injector, dynamicBeans);
* pluginOptions.parseDynamicBeans(clp);
* pluginOptions.setDynamicBeans();
* pluginOptions.onBeanParseStart();
@@ -98,18 +101,41 @@
* pluginOptions.onBeanParseEnd();
* </pre>
*/
- public DynamicOptions(Object bean, DynamicMap<DynamicBean> dynamicBeans) {
+ public DynamicOptions(Object bean, Injector injector, DynamicMap<DynamicBean> dynamicBeans) {
this.bean = bean;
+ this.injector = injector;
beansByPlugin = new HashMap<>();
for (String plugin : dynamicBeans.plugins()) {
Provider<DynamicBean> provider =
dynamicBeans.byPlugin(plugin).get(bean.getClass().getCanonicalName());
if (provider != null) {
- beansByPlugin.put(plugin, provider.get());
+ beansByPlugin.put(plugin, getDynamicBean(bean, provider.get()));
}
}
}
+ @SuppressWarnings("unchecked")
+ public DynamicBean getDynamicBean(Object bean, DynamicBean dynamicBean) {
+ ClassLoader coreCl = getClass().getClassLoader();
+ ClassLoader beanCl = bean.getClass().getClassLoader();
+ if (beanCl != coreCl) { // bean from a plugin?
+ ClassLoader dynamicBeanCl = dynamicBean.getClass().getClassLoader();
+ if (beanCl != dynamicBeanCl) { // in a different plugin?
+ ClassLoader mergedCL = new DelegatingClassLoader(beanCl, dynamicBeanCl);
+ try {
+ return injector
+ .createChildInjector()
+ .getInstance(
+ (Class<DynamicOptions.DynamicBean>)
+ mergedCL.loadClass(dynamicBean.getClass().getCanonicalName()));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return dynamicBean;
+ }
+
public void parseDynamicBeans(CmdLineParser clp) {
for (Entry<String, DynamicBean> e : beansByPlugin.entrySet()) {
clp.parseWithPrefix(e.getKey(), e.getValue());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 944d008..012ed5c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -52,6 +52,7 @@
private static final Logger log = LoggerFactory.getLogger(AccountManager.class);
private final SchemaFactory<ReviewDb> schema;
+ private final AccountsUpdate.Server accountsUpdateFactory;
private final AccountCache byIdCache;
private final AccountByEmailCache byEmailCache;
private final Realm realm;
@@ -67,6 +68,7 @@
@Inject
AccountManager(
SchemaFactory<ReviewDb> schema,
+ AccountsUpdate.Server accountsUpdateFactory,
AccountCache byIdCache,
AccountByEmailCache byEmailCache,
Realm accountMapper,
@@ -78,6 +80,7 @@
ExternalIds externalIds,
ExternalIdsUpdate.Server externalIdsUpdateFactory) {
this.schema = schema;
+ this.accountsUpdateFactory = accountsUpdateFactory;
this.byIdCache = byIdCache;
this.byEmailCache = byEmailCache;
this.realm = accountMapper;
@@ -229,12 +232,13 @@
awaitsFirstAccountCheck.getAndSet(false) && db.accounts().anyAccounts().toList().isEmpty();
try {
- db.accounts().upsert(Collections.singleton(account));
+ AccountsUpdate accountsUpdate = accountsUpdateFactory.create();
+ accountsUpdate.upsert(db, account);
ExternalId existingExtId = externalIds.get(db, extId.key());
if (existingExtId != null && !existingExtId.accountId().equals(extId.accountId())) {
// external ID is assigned to another account, do not overwrite
- db.accounts().delete(Collections.singleton(account));
+ accountsUpdate.delete(db, account);
throw new AccountException(
"Cannot assign external ID \""
+ extId.key().get()
@@ -342,7 +346,7 @@
// such an account cannot be used for uploading changes,
// this is why the best we can do here is to fail early and cleanup
// the database
- db.accounts().delete(Collections.singleton(account));
+ accountsUpdateFactory.create().delete(db, account);
externalIdsUpdateFactory.create().delete(db, extId);
throw new AccountUserNameException(errorMessage, e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsUpdate.java
index f45ee7b..de87fc1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -14,22 +14,238 @@
package com.google.gerrit.server.account;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
+import java.sql.Timestamp;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
/** Updates accounts. */
@Singleton
public class AccountsUpdate {
/**
+ * Factory to create an AccountsUpdate instance for updating accounts by the Gerrit server.
+ *
+ * <p>The Gerrit server identity will be used as author and committer for all commits that update
+ * the accounts.
+ */
+ @Singleton
+ public static class Server {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsersName;
+ private final Provider<PersonIdent> serverIdent;
+
+ @Inject
+ public Server(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName,
+ @GerritPersonIdent Provider<PersonIdent> serverIdent) {
+ this.repoManager = repoManager;
+ this.allUsersName = allUsersName;
+ this.serverIdent = serverIdent;
+ }
+
+ public AccountsUpdate create() {
+ PersonIdent i = serverIdent.get();
+ return new AccountsUpdate(repoManager, allUsersName, i, i);
+ }
+ }
+
+ /**
+ * Factory to create an AccountsUpdate instance for updating accounts by the current user.
+ *
+ * <p>The identity of the current user will be used as author for all commits that update the
+ * accounts. The Gerrit server identity will be used as committer.
+ */
+ @Singleton
+ public static class User {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsersName;
+ private final Provider<PersonIdent> serverIdent;
+ private final Provider<IdentifiedUser> identifiedUser;
+
+ @Inject
+ public User(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName,
+ @GerritPersonIdent Provider<PersonIdent> serverIdent,
+ Provider<IdentifiedUser> identifiedUser) {
+ this.repoManager = repoManager;
+ this.allUsersName = allUsersName;
+ this.serverIdent = serverIdent;
+ this.identifiedUser = identifiedUser;
+ }
+
+ public AccountsUpdate create() {
+ PersonIdent i = serverIdent.get();
+ return new AccountsUpdate(
+ repoManager, allUsersName, createPersonIdent(i, identifiedUser.get()), i);
+ }
+
+ private PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
+ return user.newCommitterIdent(ident.getWhen(), ident.getTimeZone());
+ }
+ }
+
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsersName;
+ private final PersonIdent committerIdent;
+ private final PersonIdent authorIdent;
+
+ private AccountsUpdate(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName,
+ PersonIdent committerIdent,
+ PersonIdent authorIdent) {
+ this.repoManager = checkNotNull(repoManager, "repoManager");
+ this.allUsersName = checkNotNull(allUsersName, "allUsersName");
+ this.committerIdent = checkNotNull(committerIdent, "committerIdent");
+ this.authorIdent = checkNotNull(authorIdent, "authorIdent");
+ }
+
+ /**
* Inserts a new account.
*
* @throws OrmDuplicateKeyException if the account already exists
+ * @throws IOException if updating the user branch fails
*/
- public void insert(ReviewDb db, Account account) throws OrmException {
+ public void insert(ReviewDb db, Account account) throws OrmException, IOException {
db.accounts().insert(ImmutableSet.of(account));
+ createUserBranch(account);
+ }
+
+ /**
+ * Inserts or updates an account.
+ *
+ * <p>If the account already exists, it is overwritten, otherwise it is inserted.
+ */
+ public void upsert(ReviewDb db, Account account) throws OrmException, IOException {
+ db.accounts().upsert(ImmutableSet.of(account));
+ createUserBranchIfNeeded(account);
+ }
+
+ /** Deletes the account. */
+ public void delete(ReviewDb db, Account account) throws OrmException, IOException {
+ db.accounts().delete(ImmutableSet.of(account));
+ deleteUserBranch(account.getId());
+ }
+
+ /** Deletes the account. */
+ public void deleteByKey(ReviewDb db, Account.Id accountId) throws OrmException, IOException {
+ db.accounts().deleteKeys(ImmutableSet.of(accountId));
+ deleteUserBranch(accountId);
+ }
+
+ private void createUserBranch(Account account) throws IOException {
+ try (Repository repo = repoManager.openRepository(allUsersName);
+ ObjectInserter oi = repo.newObjectInserter()) {
+ String refName = RefNames.refsUsers(account.getId());
+ if (repo.exactRef(refName) != null) {
+ throw new IOException(
+ String.format(
+ "User branch %s for newly created account %s already exists.",
+ refName, account.getId().get()));
+ }
+ createUserBranch(repo, oi, committerIdent, authorIdent, account);
+ }
+ }
+
+ private void createUserBranchIfNeeded(Account account) throws IOException {
+ try (Repository repo = repoManager.openRepository(allUsersName);
+ ObjectInserter oi = repo.newObjectInserter()) {
+ if (repo.exactRef(RefNames.refsUsers(account.getId())) == null) {
+ createUserBranch(repo, oi, committerIdent, authorIdent, account);
+ }
+ }
+ }
+
+ public static void createUserBranch(
+ Repository repo,
+ ObjectInserter oi,
+ PersonIdent committerIdent,
+ PersonIdent authorIdent,
+ Account account)
+ throws IOException {
+ ObjectId id =
+ createInitialEmptyCommit(oi, committerIdent, authorIdent, account.getRegisteredOn());
+
+ String refName = RefNames.refsUsers(account.getId());
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setExpectedOldObjectId(ObjectId.zeroId());
+ ru.setNewObjectId(id);
+ ru.setForceUpdate(true);
+ ru.setRefLogIdent(committerIdent);
+ ru.setRefLogMessage("Create Account", true);
+ Result result = ru.update();
+ if (result != Result.NEW) {
+ throw new IOException(String.format("Failed to update ref %s: %s", refName, result.name()));
+ }
+ }
+
+ private static ObjectId createInitialEmptyCommit(
+ ObjectInserter oi,
+ PersonIdent committerIdent,
+ PersonIdent authorIdent,
+ Timestamp registrationDate)
+ throws IOException {
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(emptyTree(oi));
+ cb.setCommitter(new PersonIdent(committerIdent, registrationDate));
+ cb.setAuthor(new PersonIdent(authorIdent, registrationDate));
+ cb.setMessage("Create Account");
+ ObjectId id = oi.insert(cb);
+ oi.flush();
+ return id;
+ }
+
+ private static ObjectId emptyTree(ObjectInserter oi) throws IOException {
+ return oi.insert(Constants.OBJ_TREE, new byte[] {});
+ }
+
+ private void deleteUserBranch(Account.Id accountId) throws IOException {
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ deleteUserBranch(repo, committerIdent, accountId);
+ }
+ }
+
+ public static void deleteUserBranch(
+ Repository repo, PersonIdent refLogIdent, Account.Id accountId) throws IOException {
+ String refName = RefNames.refsUsers(accountId);
+ Ref ref = repo.exactRef(refName);
+ if (ref == null) {
+ return;
+ }
+
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setExpectedOldObjectId(ref.getObjectId());
+ ru.setNewObjectId(ObjectId.zeroId());
+ ru.setForceUpdate(true);
+ ru.setRefLogIdent(refLogIdent);
+ ru.setRefLogMessage("Delete Account", true);
+ Result result = ru.delete();
+ if (result != Result.FORCED) {
+ throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Capabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Capabilities.java
index d35656c..545c57f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Capabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Capabilities.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.account;
-import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -22,7 +21,13 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource.Capability;
+import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.PluginPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -30,15 +35,18 @@
@Singleton
class Capabilities implements ChildCollection<AccountResource, AccountResource.Capability> {
private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
private final DynamicMap<RestView<AccountResource.Capability>> views;
private final Provider<GetCapabilities> get;
@Inject
Capabilities(
Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
DynamicMap<RestView<AccountResource.Capability>> views,
Provider<GetCapabilities> get) {
this.self = self;
+ this.permissionBackend = permissionBackend;
this.views = views;
this.get = get;
}
@@ -50,20 +58,39 @@
@Override
public Capability parse(AccountResource parent, IdString id)
- throws ResourceNotFoundException, AuthException {
- if (self.get() != parent.getUser() && !self.get().getCapabilities().canAdministrateServer()) {
- throw new AuthException("restricted to administrator");
+ throws ResourceNotFoundException, AuthException, PermissionBackendException {
+ IdentifiedUser target = parent.getUser();
+ if (self.get() != target) {
+ permissionBackend.user(self).check(GlobalPermission.ADMINISTRATE_SERVER);
}
- String name = id.get();
- CapabilityControl cap = parent.getUser().getCapabilities();
- if (cap.canPerform(name)
- || (cap.canAdministrateServer() && GlobalCapability.isCapability(name))) {
- return new AccountResource.Capability(parent.getUser(), name);
+ GlobalOrPluginPermission perm = parse(id);
+ if (permissionBackend.user(target).test(perm)) {
+ return new AccountResource.Capability(target, perm.permissionName());
}
throw new ResourceNotFoundException(id);
}
+ private GlobalOrPluginPermission parse(IdString id) throws ResourceNotFoundException {
+ String name = id.get();
+ GlobalOrPluginPermission perm = GlobalPermission.byName(name);
+ if (perm != null) {
+ return perm;
+ }
+
+ int dash = name.lastIndexOf('-');
+ if (dash < 0) {
+ throw new ResourceNotFoundException(id);
+ }
+
+ String pluginName = name.substring(0, dash);
+ String capability = name.substring(dash + 1);
+ if (pluginName.isEmpty() || capability.isEmpty()) {
+ throw new ResourceNotFoundException(id);
+ }
+ return new PluginPermission(pluginName, capability);
+ }
+
@Override
public DynamicMap<RestView<Capability>> views() {
return views;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index f678379..f38d019 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -26,8 +26,10 @@
import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.git.QueueProvider;
import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.PluginPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -87,26 +89,11 @@
return canEmailReviewers;
}
- /** @return true if the user can modify an account for another user. */
- public boolean canModifyAccount() {
- return canPerform(GlobalCapability.MODIFY_ACCOUNT) || canAdministrateServer();
- }
-
/** @return true if the user can view all accounts. */
public boolean canViewAllAccounts() {
return canPerform(GlobalCapability.VIEW_ALL_ACCOUNTS) || canAdministrateServer();
}
- /** @return true if the user can perform basic server maintenance. */
- public boolean canMaintainServer() {
- return canPerform(GlobalCapability.MAINTAIN_SERVER) || canAdministrateServer();
- }
-
- /** @return true if the user can view the entire queue. */
- public boolean canViewQueue() {
- return canPerform(GlobalCapability.VIEW_QUEUE) || canMaintainServer();
- }
-
/** @return true if the user can access the database (with gsql). */
public boolean canAccessDatabase() {
try {
@@ -155,11 +142,8 @@
return QueueProvider.QueueType.INTERACTIVE;
}
- /** True if the user has this permission. Works only for non labels. */
- public boolean canPerform(String permissionName) {
- if (GlobalCapability.ADMINISTRATE_SERVER.equals(permissionName)) {
- return canAdministrateServer();
- }
+ /** @return true if the user has this permission. */
+ private boolean canPerform(String permissionName) {
return !access(permissionName).isEmpty();
}
@@ -231,31 +215,39 @@
}
/** Do not use unless inside DefaultPermissionBackend. */
- public boolean doCanForDefaultPermissionBackend(GlobalPermission perm)
+ public boolean doCanForDefaultPermissionBackend(GlobalOrPluginPermission perm)
throws PermissionBackendException {
+ if (perm instanceof GlobalPermission) {
+ return can((GlobalPermission) perm);
+ } else if (perm instanceof PluginPermission) {
+ return canPerform(perm.permissionName()) || canAdministrateServer();
+ }
+ throw new PermissionBackendException(perm + " unsupported");
+ }
+
+ private boolean can(GlobalPermission perm) throws PermissionBackendException {
switch (perm) {
case ADMINISTRATE_SERVER:
return canAdministrateServer();
case EMAIL_REVIEWERS:
return canEmailReviewers();
- case MAINTAIN_SERVER:
- return canMaintainServer();
- case MODIFY_ACCOUNT:
- return canModifyAccount();
case VIEW_ALL_ACCOUNTS:
return canViewAllAccounts();
- case VIEW_QUEUE:
- return canViewQueue();
case FLUSH_CACHES:
case KILL_TASK:
case RUN_GC:
case VIEW_CACHES:
- return canPerform(perm.permissionName()) || canMaintainServer();
+ case VIEW_QUEUE:
+ return canPerform(perm.permissionName())
+ || canPerform(GlobalCapability.MAINTAIN_SERVER)
+ || canAdministrateServer();
case CREATE_ACCOUNT:
case CREATE_GROUP:
case CREATE_PROJECT:
+ case MAINTAIN_SERVER:
+ case MODIFY_ACCOUNT:
case STREAM_EVENTS:
case VIEW_CONNECTIONS:
case VIEW_PLUGINS:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
deleted file mode 100644
index 21399f4..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityUtils.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.account;
-
-import com.google.gerrit.extensions.annotations.CapabilityScope;
-import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.CurrentUser;
-import com.google.inject.Provider;
-import java.lang.annotation.Annotation;
-import java.util.Arrays;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class CapabilityUtils {
- private static final Logger log = LoggerFactory.getLogger(CapabilityUtils.class);
-
- public static void checkRequiresCapability(
- Provider<CurrentUser> userProvider, String pluginName, Class<?> clazz) throws AuthException {
- checkRequiresCapability(userProvider.get(), pluginName, clazz);
- }
-
- public static void checkRequiresCapability(CurrentUser user, String pluginName, Class<?> clazz)
- throws AuthException {
- RequiresCapability rc = getClassAnnotation(clazz, RequiresCapability.class);
- RequiresAnyCapability rac = getClassAnnotation(clazz, RequiresAnyCapability.class);
- if (rc != null && rac != null) {
- log.error(
- String.format(
- "Class %s uses both @%s and @%s",
- clazz.getName(),
- RequiresCapability.class.getSimpleName(),
- RequiresAnyCapability.class.getSimpleName()));
- throw new AuthException("cannot check capability");
- }
- CapabilityControl ctl = user.getCapabilities();
- if (ctl.canAdministrateServer()) {
- return;
- }
- checkRequiresCapability(ctl, pluginName, clazz, rc);
- checkRequiresAnyCapability(ctl, pluginName, clazz, rac);
- }
-
- private static void checkRequiresCapability(
- CapabilityControl ctl, String pluginName, Class<?> clazz, RequiresCapability rc)
- throws AuthException {
- if (rc == null) {
- return;
- }
- String capability = resolveCapability(pluginName, rc.value(), rc.scope(), clazz);
- if (!ctl.canPerform(capability)) {
- throw new AuthException(
- String.format("Capability %s is required to access this resource", capability));
- }
- }
-
- private static void checkRequiresAnyCapability(
- CapabilityControl ctl, String pluginName, Class<?> clazz, RequiresAnyCapability rac)
- throws AuthException {
- if (rac == null) {
- return;
- }
- if (rac.value().length == 0) {
- log.error(
- String.format(
- "Class %s uses @%s with no capabilities listed",
- clazz.getName(), RequiresAnyCapability.class.getSimpleName()));
- throw new AuthException("cannot check capability");
- }
- for (String capability : rac.value()) {
- capability = resolveCapability(pluginName, capability, rac.scope(), clazz);
- if (ctl.canPerform(capability)) {
- return;
- }
- }
- throw new AuthException(
- "One of the following capabilities is required to access this"
- + " resource: "
- + Arrays.asList(rac.value()));
- }
-
- private static String resolveCapability(
- String pluginName, String capability, CapabilityScope scope, Class<?> clazz)
- throws AuthException {
- if (pluginName != null
- && !"gerrit".equals(pluginName)
- && (scope == CapabilityScope.PLUGIN || scope == CapabilityScope.CONTEXT)) {
- capability = String.format("%s-%s", pluginName, capability);
- } else if (scope == CapabilityScope.PLUGIN) {
- log.error(
- String.format(
- "Class %s uses @%s(scope=%s), but is not within a plugin",
- clazz.getName(),
- RequiresCapability.class.getSimpleName(),
- CapabilityScope.PLUGIN.name()));
- throw new AuthException("cannot check capability");
- }
- return capability;
- }
-
- /**
- * Find an instance of the specified annotation, walking up the inheritance tree if necessary.
- *
- * @param <T> Annotation type to search for
- * @param clazz root class to search, may be null
- * @param annotationClass class object of Annotation subclass to search for
- * @return the requested annotation or null if none
- */
- private static <T extends Annotation> T getClassAnnotation(
- Class<?> clazz, Class<T> annotationClass) {
- for (; clazz != null; clazz = clazz.getSuperclass()) {
- T t = clazz.getAnnotation(annotationClass);
- if (t != null) {
- return t;
- }
- }
- return null;
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index b16733c..2f75390 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -69,7 +69,7 @@
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
private final SshKeyCache sshKeyCache;
private final AccountCache accountCache;
- private final AccountsUpdate accountsUpdate;
+ private final AccountsUpdate.User accountsUpdate;
private final AccountIndexer indexer;
private final AccountByEmailCache byEmailCache;
private final AccountLoader.Factory infoLoader;
@@ -87,7 +87,7 @@
VersionedAuthorizedKeys.Accessor authorizedKeys,
SshKeyCache sshKeyCache,
AccountCache accountCache,
- AccountsUpdate accountsUpdate,
+ AccountsUpdate.User accountsUpdate,
AccountIndexer indexer,
AccountByEmailCache byEmailCache,
AccountLoader.Factory infoLoader,
@@ -175,7 +175,7 @@
Account a = new Account(id, TimeUtil.nowTs());
a.setFullName(input.name);
a.setPreferredEmail(input.email);
- accountsUpdate.insert(db, a);
+ accountsUpdate.create().insert(db, a);
for (AccountGroup.Id groupId : groups) {
AccountGroupMember m = new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
index b1a5d3b..4af7162 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
@@ -32,6 +32,9 @@
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -50,6 +53,7 @@
private final Provider<CurrentUser> self;
private final Realm realm;
+ private final PermissionBackend permissionBackend;
private final AccountManager accountManager;
private final RegisterNewEmailSender.Factory registerNewEmailFactory;
private final PutPreferred putPreferred;
@@ -60,6 +64,7 @@
CreateEmail(
Provider<CurrentUser> self,
Realm realm,
+ PermissionBackend permissionBackend,
AuthConfig authConfig,
AccountManager accountManager,
RegisterNewEmailSender.Factory registerNewEmailFactory,
@@ -67,6 +72,7 @@
@Assisted String email) {
this.self = self;
this.realm = realm;
+ this.permissionBackend = permissionBackend;
this.accountManager = accountManager;
this.registerNewEmailFactory = registerNewEmailFactory;
this.putPreferred = putPreferred;
@@ -78,9 +84,9 @@
public Response<EmailInfo> apply(AccountResource rsrc, EmailInput input)
throws AuthException, BadRequestException, ResourceConflictException,
ResourceNotFoundException, OrmException, EmailException, MethodNotAllowedException,
- IOException, ConfigInvalidException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to add email address");
+ IOException, ConfigInvalidException, PermissionBackendException {
+ if (self.get() != rsrc.getUser() || input.noConfirmation) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
if (input == null) {
@@ -91,10 +97,6 @@
throw new BadRequestException("invalid email address");
}
- if (input.noConfirmation && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to use no_confirmation");
- }
-
if (!realm.allowsEdit(AccountFieldName.REGISTER_NEW_EMAIL)) {
throw new MethodNotAllowedException("realm does not allow adding emails");
}
@@ -105,7 +107,7 @@
public Response<EmailInfo> apply(IdentifiedUser user, EmailInput input)
throws AuthException, BadRequestException, ResourceConflictException,
ResourceNotFoundException, OrmException, EmailException, MethodNotAllowedException,
- IOException, ConfigInvalidException {
+ IOException, ConfigInvalidException, PermissionBackendException {
if (input.email != null && !email.equals(input.email)) {
throw new BadRequestException("email address must match URL");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
index bfdf06c..b4e2bdb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -29,6 +29,9 @@
import com.google.gerrit.server.account.DeleteEmail.Input;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -43,6 +46,7 @@
private final Provider<CurrentUser> self;
private final Realm realm;
+ private final PermissionBackend permissionBackend;
private final Provider<ReviewDb> dbProvider;
private final AccountManager accountManager;
private final ExternalIds externalIds;
@@ -51,11 +55,13 @@
DeleteEmail(
Provider<CurrentUser> self,
Realm realm,
+ PermissionBackend permissionBackend,
Provider<ReviewDb> dbProvider,
AccountManager accountManager,
ExternalIds externalIds) {
this.self = self;
this.realm = realm;
+ this.permissionBackend = permissionBackend;
this.dbProvider = dbProvider;
this.accountManager = accountManager;
this.externalIds = externalIds;
@@ -64,9 +70,10 @@
@Override
public Response<?> apply(AccountResource.Email rsrc, Input input)
throws AuthException, ResourceNotFoundException, ResourceConflictException,
- MethodNotAllowedException, OrmException, IOException, ConfigInvalidException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to delete email address");
+ MethodNotAllowedException, OrmException, IOException, ConfigInvalidException,
+ PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
return apply(rsrc.getUser(), rsrc.getEmail());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index 1268ef2..5276e8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -29,14 +29,15 @@
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.AccountResource.Capability;
import com.google.gerrit.server.git.QueueProvider;
+import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.PluginPermission;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -77,11 +78,10 @@
}
Map<String, Object> have = new LinkedHashMap<>();
- for (GlobalPermission p : testGlobalPermissions(perm)) {
+ for (GlobalOrPluginPermission p : perm.test(permissionsToTest())) {
have.put(p.permissionName(), true);
}
addRanges(have, rsrc);
- addPluginCapabilities(have, rsrc);
addPriority(have, rsrc);
return OutputFormat.JSON
@@ -89,20 +89,23 @@
.toJsonTree(have, new TypeToken<Map<String, Object>>() {}.getType());
}
- private Set<GlobalPermission> testGlobalPermissions(PermissionBackend.WithUser perm)
- throws PermissionBackendException {
- EnumSet<GlobalPermission> toTest;
- if (query != null) {
- toTest = EnumSet.noneOf(GlobalPermission.class);
- for (GlobalPermission p : GlobalPermission.values()) {
+ private Set<GlobalOrPluginPermission> permissionsToTest() {
+ Set<GlobalOrPluginPermission> toTest = new HashSet<>();
+ for (GlobalPermission p : GlobalPermission.values()) {
+ if (want(p.permissionName())) {
+ toTest.add(p);
+ }
+ }
+
+ for (String pluginName : pluginCapabilities.plugins()) {
+ for (String capability : pluginCapabilities.byPlugin(pluginName).keySet()) {
+ PluginPermission p = new PluginPermission(pluginName, capability);
if (want(p.permissionName())) {
toTest.add(p);
}
}
- } else {
- toTest = EnumSet.allOf(GlobalPermission.class);
}
- return perm.test(toTest);
+ return toTest;
}
private boolean want(String name) {
@@ -118,18 +121,6 @@
}
}
- private void addPluginCapabilities(Map<String, Object> have, AccountResource rsrc) {
- CapabilityControl cc = rsrc.getUser().getCapabilities();
- for (String pluginName : pluginCapabilities.plugins()) {
- for (String capability : pluginCapabilities.byPlugin(pluginName).keySet()) {
- String name = String.format("%s-%s", pluginName, capability);
- if (want(name) && cc.canPerform(name)) {
- have.put(name, true);
- }
- }
- }
- }
-
private void addPriority(Map<String, Object> have, AccountResource rsrc) {
QueueProvider.QueueType queue = rsrc.getUser().getCapabilities().getQueueType();
if (queue != QueueProvider.QueueType.INTERACTIVE
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
index e385020..bb207f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
@@ -24,6 +24,9 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.UserConfigSections;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -35,22 +38,27 @@
@Singleton
public class GetEditPreferences implements RestReadView<AccountResource> {
private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
private final AllUsersName allUsersName;
private final GitRepositoryManager gitMgr;
@Inject
GetEditPreferences(
- Provider<CurrentUser> self, AllUsersName allUsersName, GitRepositoryManager gitMgr) {
+ Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
+ AllUsersName allUsersName,
+ GitRepositoryManager gitMgr) {
this.self = self;
+ this.permissionBackend = permissionBackend;
this.allUsersName = allUsersName;
this.gitMgr = gitMgr;
}
@Override
public EditPreferencesInfo apply(AccountResource rsrc)
- throws AuthException, IOException, ConfigInvalidException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("requires Modify Account capability");
+ throws AuthException, IOException, ConfigInvalidException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
return readFromGit(rsrc.getUser().getAccountId(), gitMgr, allUsersName, null);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
index 77cdbd4..3ebf864 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetPreferences.java
@@ -19,6 +19,9 @@
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -26,18 +29,22 @@
@Singleton
public class GetPreferences implements RestReadView<AccountResource> {
private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
private final AccountCache accountCache;
@Inject
- GetPreferences(Provider<CurrentUser> self, AccountCache accountCache) {
+ GetPreferences(
+ Provider<CurrentUser> self, PermissionBackend permissionBackend, AccountCache accountCache) {
this.self = self;
+ this.permissionBackend = permissionBackend;
this.accountCache = accountCache;
}
@Override
- public GeneralPreferencesInfo apply(AccountResource rsrc) throws AuthException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("requires Modify Account capability");
+ public GeneralPreferencesInfo apply(AccountResource rsrc)
+ throws AuthException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
Account.Id id = rsrc.getUser().getAccountId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
index 980d880..9f5b9d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
@@ -22,6 +22,9 @@
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -35,20 +38,25 @@
public class GetSshKeys implements RestReadView<AccountResource> {
private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
@Inject
- GetSshKeys(Provider<CurrentUser> self, VersionedAuthorizedKeys.Accessor authorizedKeys) {
+ GetSshKeys(
+ Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
+ VersionedAuthorizedKeys.Accessor authorizedKeys) {
this.self = self;
+ this.permissionBackend = permissionBackend;
this.authorizedKeys = authorizedKeys;
}
@Override
public List<SshKeyInfo> apply(AccountResource rsrc)
throws AuthException, OrmException, RepositoryNotFoundException, IOException,
- ConfigInvalidException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to get SSH keys");
+ ConfigInvalidException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
return apply(rsrc.getUser());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java
index 6943dca..ecc6b8c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java
@@ -19,6 +19,9 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.Index.Input;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -29,18 +32,22 @@
public static class Input {}
private final AccountCache accountCache;
+ private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> self;
@Inject
- Index(AccountCache accountCache, Provider<CurrentUser> self) {
+ Index(
+ AccountCache accountCache, PermissionBackend permissionBackend, Provider<CurrentUser> self) {
this.accountCache = accountCache;
+ this.permissionBackend = permissionBackend;
this.self = self;
}
@Override
- public Response<?> apply(AccountResource rsrc, Input input) throws IOException, AuthException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to index account");
+ public Response<?> apply(AccountResource rsrc, Input input)
+ throws IOException, AuthException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
// evicting the account from the cache, reindexes the account
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
index 443a549..7a2868e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
@@ -27,6 +27,9 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.PutName.Input;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -42,6 +45,7 @@
private final Provider<CurrentUser> self;
private final Realm realm;
+ private final PermissionBackend permissionBackend;
private final Provider<ReviewDb> dbProvider;
private final AccountCache byIdCache;
@@ -49,10 +53,12 @@
PutName(
Provider<CurrentUser> self,
Realm realm,
+ PermissionBackend permissionBackend,
Provider<ReviewDb> dbProvider,
AccountCache byIdCache) {
this.self = self;
this.realm = realm;
+ this.permissionBackend = permissionBackend;
this.dbProvider = dbProvider;
this.byIdCache = byIdCache;
}
@@ -60,9 +66,9 @@
@Override
public Response<String> apply(AccountResource rsrc, Input input)
throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
- IOException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to change name");
+ IOException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
return apply(rsrc.getUser(), input);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
index fb87e1e..4941cc8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
@@ -23,6 +23,9 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.PutPreferred.Input;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -37,20 +40,27 @@
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
+ private final PermissionBackend permissionBackend;
private final AccountCache byIdCache;
@Inject
- PutPreferred(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider, AccountCache byIdCache) {
+ PutPreferred(
+ Provider<CurrentUser> self,
+ Provider<ReviewDb> dbProvider,
+ PermissionBackend permissionBackend,
+ AccountCache byIdCache) {
this.self = self;
this.dbProvider = dbProvider;
+ this.permissionBackend = permissionBackend;
this.byIdCache = byIdCache;
}
@Override
public Response<String> apply(AccountResource.Email rsrc, Input input)
- throws AuthException, ResourceNotFoundException, OrmException, IOException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to set preferred email address");
+ throws AuthException, ResourceNotFoundException, OrmException, IOException,
+ PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
return apply(rsrc.getUser(), rsrc.getEmail());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java
index ff541fd..73a720b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java
@@ -25,6 +25,9 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.PutStatus.Input;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -46,20 +49,27 @@
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
+ private final PermissionBackend permissionBackend;
private final AccountCache byIdCache;
@Inject
- PutStatus(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider, AccountCache byIdCache) {
+ PutStatus(
+ Provider<CurrentUser> self,
+ Provider<ReviewDb> dbProvider,
+ PermissionBackend permissionBackend,
+ AccountCache byIdCache) {
this.self = self;
this.dbProvider = dbProvider;
+ this.permissionBackend = permissionBackend;
this.byIdCache = byIdCache;
}
@Override
public Response<String> apply(AccountResource rsrc, Input input)
- throws AuthException, ResourceNotFoundException, OrmException, IOException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("not allowed to set status");
+ throws AuthException, ResourceNotFoundException, OrmException, IOException,
+ PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
return apply(rsrc.getUser(), input);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
index ac0cc96..88e9e20 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetDiffPreferences.java
@@ -29,6 +29,9 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.UserConfigSections;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -41,6 +44,7 @@
private final Provider<CurrentUser> self;
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
private final AllUsersName allUsersName;
+ private final PermissionBackend permissionBackend;
private final GitRepositoryManager gitMgr;
@Inject
@@ -48,19 +52,21 @@
Provider<CurrentUser> self,
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
AllUsersName allUsersName,
+ PermissionBackend permissionBackend,
GitRepositoryManager gitMgr) {
this.self = self;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allUsersName = allUsersName;
+ this.permissionBackend = permissionBackend;
this.gitMgr = gitMgr;
}
@Override
public DiffPreferencesInfo apply(AccountResource rsrc, DiffPreferencesInfo in)
throws AuthException, BadRequestException, ConfigInvalidException,
- RepositoryNotFoundException, IOException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("requires Modify Account capability");
+ RepositoryNotFoundException, IOException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
if (in == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java
index ca981b8..53285db 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetEditPreferences.java
@@ -28,6 +28,9 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.UserConfigSections;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -40,6 +43,7 @@
private final Provider<CurrentUser> self;
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
+ private final PermissionBackend permissionBackend;
private final GitRepositoryManager gitMgr;
private final AllUsersName allUsersName;
@@ -47,10 +51,12 @@
SetEditPreferences(
Provider<CurrentUser> self,
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
+ PermissionBackend permissionBackend,
GitRepositoryManager gitMgr,
AllUsersName allUsersName) {
this.self = self;
this.metaDataUpdateFactory = metaDataUpdateFactory;
+ this.permissionBackend = permissionBackend;
this.gitMgr = gitMgr;
this.allUsersName = allUsersName;
}
@@ -58,9 +64,9 @@
@Override
public EditPreferencesInfo apply(AccountResource rsrc, EditPreferencesInfo in)
throws AuthException, BadRequestException, RepositoryNotFoundException, IOException,
- ConfigInvalidException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("requires Modify Account capability");
+ ConfigInvalidException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
if (in == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
index 91672f7..c033d9d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SetPreferences.java
@@ -36,6 +36,9 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.UserConfigSections;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -51,6 +54,7 @@
public class SetPreferences implements RestModifyView<AccountResource, GeneralPreferencesInfo> {
private final Provider<CurrentUser> self;
private final AccountCache cache;
+ private final PermissionBackend permissionBackend;
private final GeneralPreferencesLoader loader;
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
private final AllUsersName allUsersName;
@@ -60,6 +64,7 @@
SetPreferences(
Provider<CurrentUser> self,
AccountCache cache,
+ PermissionBackend permissionBackend,
GeneralPreferencesLoader loader,
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
AllUsersName allUsersName,
@@ -67,6 +72,7 @@
this.self = self;
this.loader = loader;
this.cache = cache;
+ this.permissionBackend = permissionBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allUsersName = allUsersName;
this.downloadSchemes = downloadSchemes;
@@ -74,9 +80,10 @@
@Override
public GeneralPreferencesInfo apply(AccountResource rsrc, GeneralPreferencesInfo i)
- throws AuthException, BadRequestException, IOException, ConfigInvalidException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new AuthException("requires Modify Account capability");
+ throws AuthException, BadRequestException, IOException, ConfigInvalidException,
+ PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
checkDownloadScheme(i.downloadScheme);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
index 6336e08..70c02a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/SshKeys.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.account;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -22,6 +23,9 @@
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -34,6 +38,7 @@
private final DynamicMap<RestView<AccountResource.SshKey>> views;
private final GetSshKeys list;
private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
@Inject
@@ -41,10 +46,12 @@
DynamicMap<RestView<AccountResource.SshKey>> views,
GetSshKeys list,
Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
VersionedAuthorizedKeys.Accessor authorizedKeys) {
this.views = views;
this.list = list;
this.self = self;
+ this.permissionBackend = permissionBackend;
this.authorizedKeys = authorizedKeys;
}
@@ -55,9 +62,15 @@
@Override
public AccountResource.SshKey parse(AccountResource rsrc, IdString id)
- throws ResourceNotFoundException, OrmException, IOException, ConfigInvalidException {
- if (self.get() != rsrc.getUser() && !self.get().getCapabilities().canModifyAccount()) {
- throw new ResourceNotFoundException();
+ throws ResourceNotFoundException, OrmException, IOException, ConfigInvalidException,
+ PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ try {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
+ } catch (AuthException e) {
+ // If lacking MODIFY_ACCOUNT claim the resource does not exist.
+ throw new ResourceNotFoundException();
+ }
}
return parse(rsrc.getUser(), id);
}
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 74e1fda..b4fef67 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
@@ -215,21 +215,21 @@
throw invalidConfig(
noteId,
String.format(
- "Expected exactly 1 %s section, found %d",
+ "Expected exactly 1 '%s' section, found %d",
EXTERNAL_ID_SECTION, externalIdKeys.size()));
}
String externalIdKeyStr = Iterables.getOnlyElement(externalIdKeys);
Key externalIdKey = Key.parse(externalIdKeyStr);
if (externalIdKey == null) {
- throw invalidConfig(noteId, String.format("Invalid external id: %s", externalIdKeyStr));
+ throw invalidConfig(noteId, String.format("External ID %s is invalid", externalIdKeyStr));
}
if (!externalIdKey.sha1().getName().equals(noteId)) {
throw invalidConfig(
noteId,
String.format(
- "SHA1 of external ID %s does not match note ID %s", externalIdKeyStr, noteId));
+ "SHA1 of external ID '%s' does not match note ID '%s'", externalIdKeyStr, noteId));
}
String email = externalIdConfig.getString(EXTERNAL_ID_SECTION, externalIdKeyStr, EMAIL_KEY);
@@ -252,7 +252,7 @@
throw invalidConfig(
noteId,
String.format(
- "Value for %s.%s.%s is missing, expected account ID",
+ "Value for '%s.%s.%s' is missing, expected account ID",
EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY));
}
@@ -263,7 +263,7 @@
throw invalidConfig(
noteId,
String.format(
- "Value %s for %s.%s.%s is invalid, expected account ID",
+ "Value %s for '%s.%s.%s' is invalid, expected account ID",
accountIdStr, EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY));
}
return accountId;
@@ -271,14 +271,14 @@
throw invalidConfig(
noteId,
String.format(
- "Value %s for %s.%s.%s is invalid, expected account ID",
+ "Value %s for '%s.%s.%s' is invalid, expected account ID",
accountIdStr, EXTERNAL_ID_SECTION, externalIdKeyStr, ACCOUNT_ID_KEY));
}
}
private static ConfigInvalidException invalidConfig(String noteId, String message) {
return new ConfigInvalidException(
- String.format("Invalid external id config for note %s: %s", noteId, message));
+ String.format("Invalid external ID config for note '%s': %s", noteId, message));
}
public static ExternalId from(AccountExternalId externalId) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
index b12e7ed..e25c36f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdReader.java
@@ -156,7 +156,7 @@
rw.getObjectReader().open(note.getData(), OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
try {
extIds.add(ExternalId.parse(note.getName(), raw));
- } catch (ConfigInvalidException e) {
+ } catch (Exception e) {
log.error(String.format("Ignoring invalid external ID note %s", note.getName()), e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
new file mode 100644
index 0000000..bc681a2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsConsistencyChecker.java
@@ -0,0 +1,150 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account.externalids;
+
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+import static java.util.stream.Collectors.joining;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.HashedPassword;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.codec.DecoderException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+@Singleton
+public class ExternalIdsConsistencyChecker {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsers;
+ private final AccountCache accountCache;
+
+ @Inject
+ ExternalIdsConsistencyChecker(
+ GitRepositoryManager repoManager, AllUsersName allUsers, AccountCache accountCache) {
+ this.repoManager = repoManager;
+ this.allUsers = allUsers;
+ this.accountCache = accountCache;
+ }
+
+ public List<ConsistencyProblemInfo> check() throws IOException {
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ return check(repo, ExternalIdReader.readRevision(repo));
+ }
+ }
+
+ public List<ConsistencyProblemInfo> check(ObjectId rev) throws IOException {
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ return check(repo, rev);
+ }
+ }
+
+ private List<ConsistencyProblemInfo> check(Repository repo, ObjectId commit) throws IOException {
+ List<ConsistencyProblemInfo> problems = new ArrayList<>();
+
+ ListMultimap<String, ExternalId.Key> emails =
+ MultimapBuilder.hashKeys().arrayListValues().build();
+
+ try (RevWalk rw = new RevWalk(repo)) {
+ NoteMap noteMap = ExternalIdReader.readNoteMap(rw, commit);
+ for (Note note : noteMap) {
+ byte[] raw =
+ rw.getObjectReader()
+ .open(note.getData(), OBJ_BLOB)
+ .getCachedBytes(ExternalIdReader.MAX_NOTE_SZ);
+ try {
+ ExternalId extId = ExternalId.parse(note.getName(), raw);
+ problems.addAll(validateExternalId(extId));
+
+ if (extId.email() != null) {
+ emails.put(extId.email(), extId.key());
+ }
+ } catch (ConfigInvalidException e) {
+ addError(String.format(e.getMessage()), problems);
+ }
+ }
+ }
+
+ emails
+ .asMap()
+ .entrySet()
+ .stream()
+ .filter(e -> e.getValue().size() > 1)
+ .forEach(
+ e ->
+ addError(
+ String.format(
+ "Email '%s' is not unique, it's used by the following external IDs: %s",
+ e.getKey(),
+ e.getValue()
+ .stream()
+ .map(k -> "'" + k.get() + "'")
+ .sorted()
+ .collect(joining(", "))),
+ problems));
+
+ return problems;
+ }
+
+ private List<ConsistencyProblemInfo> validateExternalId(ExternalId extId) {
+ List<ConsistencyProblemInfo> problems = new ArrayList<>();
+
+ if (accountCache.getIfPresent(extId.accountId()) == null) {
+ addError(
+ String.format(
+ "External ID '%s' belongs to account that doesn't exist: %s",
+ extId.key().get(), extId.accountId().get()),
+ problems);
+ }
+
+ if (extId.email() != null && !OutgoingEmailValidator.isValid(extId.email())) {
+ addError(
+ String.format(
+ "External ID '%s' has an invalid email: %s", extId.key().get(), extId.email()),
+ problems);
+ }
+
+ if (extId.password() != null && extId.isScheme(SCHEME_USERNAME)) {
+ try {
+ HashedPassword.decode(extId.password());
+ } catch (DecoderException e) {
+ addError(
+ String.format(
+ "External ID '%s' has an invalid password: %s", extId.key().get(), e.getMessage()),
+ problems);
+ }
+ }
+
+ return problems;
+ }
+
+ private static void addError(String error, List<ConsistencyProblemInfo> problems) {
+ problems.add(new ConsistencyProblemInfo(ConsistencyProblemInfo.Status.ERROR, error));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 23c6537..dc5ea4c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -71,6 +71,7 @@
import com.google.gerrit.server.account.Stars;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -234,14 +235,18 @@
@Override
public GeneralPreferencesInfo getPreferences() throws RestApiException {
- return getPreferences.apply(account);
+ try {
+ return getPreferences.apply(account);
+ } catch (PermissionBackendException e) {
+ throw new RestApiException("Cannot get preferences", e);
+ }
}
@Override
public GeneralPreferencesInfo setPreferences(GeneralPreferencesInfo in) throws RestApiException {
try {
return setPreferences.apply(account, in);
- } catch (IOException | ConfigInvalidException e) {
+ } catch (IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot set preferences", e);
}
}
@@ -259,7 +264,7 @@
public DiffPreferencesInfo setDiffPreferences(DiffPreferencesInfo in) throws RestApiException {
try {
return setDiffPreferences.apply(account, in);
- } catch (IOException | ConfigInvalidException e) {
+ } catch (IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot set diff preferences", e);
}
}
@@ -268,7 +273,7 @@
public EditPreferencesInfo getEditPreferences() throws RestApiException {
try {
return getEditPreferences.apply(account);
- } catch (IOException | ConfigInvalidException e) {
+ } catch (IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot query edit preferences", e);
}
}
@@ -277,7 +282,7 @@
public EditPreferencesInfo setEditPreferences(EditPreferencesInfo in) throws RestApiException {
try {
return setEditPreferences.apply(account, in);
- } catch (IOException | ConfigInvalidException e) {
+ } catch (IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot set edit preferences", e);
}
}
@@ -372,7 +377,11 @@
AccountResource.Email rsrc = new AccountResource.Email(account.getUser(), input.email);
try {
createEmailFactory.create(input.email).apply(rsrc, input);
- } catch (EmailException | OrmException | IOException | ConfigInvalidException e) {
+ } catch (EmailException
+ | OrmException
+ | IOException
+ | ConfigInvalidException
+ | PermissionBackendException e) {
throw new RestApiException("Cannot add email", e);
}
}
@@ -382,7 +391,7 @@
AccountResource.Email rsrc = new AccountResource.Email(account.getUser(), email);
try {
deleteEmail.apply(rsrc, null);
- } catch (OrmException | IOException | ConfigInvalidException e) {
+ } catch (OrmException | IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot delete email", e);
}
}
@@ -392,7 +401,7 @@
PutStatus.Input in = new PutStatus.Input(status);
try {
putStatus.apply(account, in);
- } catch (OrmException | IOException e) {
+ } catch (OrmException | IOException | PermissionBackendException e) {
throw new RestApiException("Cannot set status", e);
}
}
@@ -401,7 +410,7 @@
public List<SshKeyInfo> listSshKeys() throws RestApiException {
try {
return getSshKeys.apply(account);
- } catch (OrmException | IOException | ConfigInvalidException e) {
+ } catch (OrmException | IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot list SSH keys", e);
}
}
@@ -423,7 +432,7 @@
AccountResource.SshKey sshKeyRes =
sshKeys.parse(account, IdString.fromDecoded(Integer.toString(seq)));
deleteSshKey.apply(sshKeyRes, null);
- } catch (OrmException | IOException | ConfigInvalidException e) {
+ } catch (OrmException | IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot delete SSH key", e);
}
}
@@ -476,7 +485,7 @@
public void index() throws RestApiException {
try {
index.apply(account, new Index.Input());
- } catch (IOException e) {
+ } catch (IOException | PermissionBackendException e) {
throw new RestApiException("Cannot index account", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
index 498b720..bade8ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.api.accounts;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
import com.google.gerrit.extensions.api.accounts.AccountApi;
import com.google.gerrit.extensions.api.accounts.AccountInput;
@@ -32,6 +31,9 @@
import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.account.CreateAccount;
import com.google.gerrit.server.account.QueryAccounts;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -44,6 +46,7 @@
public class AccountsImpl implements Accounts {
private final AccountsCollection accounts;
private final AccountApiImpl.Factory api;
+ private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> self;
private final CreateAccount.Factory createAccount;
private final Provider<QueryAccounts> queryAccountsProvider;
@@ -52,11 +55,13 @@
AccountsImpl(
AccountsCollection accounts,
AccountApiImpl.Factory api,
+ PermissionBackend permissionBackend,
Provider<CurrentUser> self,
CreateAccount.Factory createAccount,
Provider<QueryAccounts> queryAccountsProvider) {
this.accounts = accounts;
this.api = api;
+ this.permissionBackend = permissionBackend;
this.self = self;
this.createAccount = createAccount;
this.queryAccountsProvider = queryAccountsProvider;
@@ -96,12 +101,12 @@
if (checkNotNull(in, "AccountInput").username == null) {
throw new BadRequestException("AccountInput must specify username");
}
- checkRequiresCapability(self, null, CreateAccount.class);
try {
- AccountInfo info =
- createAccount.create(in.username).apply(TopLevelResource.INSTANCE, in).value();
+ CreateAccount impl = createAccount.create(in.username);
+ permissionBackend.user(self).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
+ AccountInfo info = impl.apply(TopLevelResource.INSTANCE, in).value();
return id(info._accountId);
- } catch (OrmException | IOException | ConfigInvalidException e) {
+ } catch (OrmException | IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot create account " + in.username, e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index d534c5a..cee2403 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -572,7 +572,7 @@
public ChangeInfo check(FixInput fix) throws RestApiException {
try {
return check.apply(change, fix).value();
- } catch (OrmException e) {
+ } catch (OrmException | PermissionBackendException e) {
throw new RestApiException("Cannot check change", e);
}
}
@@ -581,7 +581,7 @@
public void index() throws RestApiException {
try {
index.apply(change, new Index.Input());
- } catch (IOException | OrmException e) {
+ } catch (IOException | OrmException | PermissionBackendException e) {
throw new RestApiException("Cannot index change", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java
index 9b6ead0..d3c5135 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/config/ServerImpl.java
@@ -15,11 +15,14 @@
package com.google.gerrit.server.api.config;
import com.google.gerrit.common.Version;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
import com.google.gerrit.extensions.api.config.Server;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.common.ServerInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.config.CheckConsistency;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.GetDiffPreferences;
import com.google.gerrit.server.config.GetPreferences;
@@ -27,6 +30,7 @@
import com.google.gerrit.server.config.SetDiffPreferences;
import com.google.gerrit.server.config.SetPreferences;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -38,6 +42,7 @@
private final GetDiffPreferences getDiffPreferences;
private final SetDiffPreferences setDiffPreferences;
private final GetServerInfo getServerInfo;
+ private final Provider<CheckConsistency> checkConsistency;
@Inject
ServerImpl(
@@ -45,12 +50,14 @@
SetPreferences setPreferences,
GetDiffPreferences getDiffPreferences,
SetDiffPreferences setDiffPreferences,
- GetServerInfo getServerInfo) {
+ GetServerInfo getServerInfo,
+ Provider<CheckConsistency> checkConsistency) {
this.getPreferences = getPreferences;
this.setPreferences = setPreferences;
this.getDiffPreferences = getDiffPreferences;
this.setDiffPreferences = setDiffPreferences;
this.getServerInfo = getServerInfo;
+ this.checkConsistency = checkConsistency;
}
@Override
@@ -104,4 +111,13 @@
throw new RestApiException("Cannot set default diff preferences", e);
}
}
+
+ @Override
+ public ConsistencyCheckInfo checkConsistency(ConsistencyCheckInput in) throws RestApiException {
+ try {
+ return checkConsistency.get().apply(new ConfigResource(), in);
+ } catch (IOException e) {
+ throw new RestApiException("Cannot check consistency", e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
index 1d725a8..6eef5e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.api.groups;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
import com.google.gerrit.extensions.api.groups.GroupApi;
import com.google.gerrit.extensions.api.groups.GroupInput;
@@ -32,6 +31,9 @@
import com.google.gerrit.server.group.GroupsCollection;
import com.google.gerrit.server.group.ListGroups;
import com.google.gerrit.server.group.QueryGroups;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectsCollection;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -49,6 +51,7 @@
private final Provider<ListGroups> listGroups;
private final Provider<QueryGroups> queryGroups;
private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
private final CreateGroup.Factory createGroup;
private final GroupApiImpl.Factory api;
@@ -60,6 +63,7 @@
Provider<ListGroups> listGroups,
Provider<QueryGroups> queryGroups,
Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
CreateGroup.Factory createGroup,
GroupApiImpl.Factory api) {
this.accounts = accounts;
@@ -68,6 +72,7 @@
this.listGroups = listGroups;
this.queryGroups = queryGroups;
this.user = user;
+ this.permissionBackend = permissionBackend;
this.createGroup = createGroup;
this.api = api;
}
@@ -89,11 +94,12 @@
if (checkNotNull(in, "GroupInput").name == null) {
throw new BadRequestException("GroupInput must specify name");
}
- checkRequiresCapability(user, null, CreateGroup.class);
try {
- GroupInfo info = createGroup.create(in.name).apply(TopLevelResource.INSTANCE, in);
+ CreateGroup impl = createGroup.create(in.name);
+ permissionBackend.user(user).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
+ GroupInfo info = impl.apply(TopLevelResource.INSTANCE, in);
return id(info.id);
- } catch (OrmException | IOException e) {
+ } catch (OrmException | IOException | PermissionBackendException e) {
throw new RestApiException("Cannot create group " + in.name, e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 025b62a..65673de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.api.projects;
-import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
-
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.api.projects.BranchApi;
@@ -39,6 +37,9 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChildProjectsCollection;
import com.google.gerrit.server.project.CommitsCollection;
import com.google.gerrit.server.project.CreateProject;
@@ -71,6 +72,7 @@
}
private final CurrentUser user;
+ private final PermissionBackend permissionBackend;
private final CreateProject.Factory createProjectFactory;
private final ProjectApiImpl.Factory projectApi;
private final ProjectsCollection projects;
@@ -97,6 +99,7 @@
@AssistedInject
ProjectApiImpl(
CurrentUser user,
+ PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
@@ -120,6 +123,7 @@
@Assisted ProjectResource project) {
this(
user,
+ permissionBackend,
createProjectFactory,
projectApi,
projects,
@@ -147,6 +151,7 @@
@AssistedInject
ProjectApiImpl(
CurrentUser user,
+ PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
@@ -170,6 +175,7 @@
@Assisted String name) {
this(
user,
+ permissionBackend,
createProjectFactory,
projectApi,
projects,
@@ -196,6 +202,7 @@
private ProjectApiImpl(
CurrentUser user,
+ PermissionBackend permissionBackend,
CreateProject.Factory createProjectFactory,
ProjectApiImpl.Factory projectApi,
ProjectsCollection projects,
@@ -219,6 +226,7 @@
CommitApiImpl.Factory commitApi,
String name) {
this.user = user;
+ this.permissionBackend = permissionBackend;
this.createProjectFactory = createProjectFactory;
this.projectApi = projectApi;
this.projects = projects;
@@ -257,10 +265,11 @@
if (in.name != null && !name.equals(in.name)) {
throw new BadRequestException("name must match input.name");
}
- checkRequiresCapability(user, null, CreateProject.class);
- createProjectFactory.create(name).apply(TopLevelResource.INSTANCE, in);
+ CreateProject impl = createProjectFactory.create(name);
+ permissionBackend.user(user).checkAny(GlobalPermission.fromAnnotation(impl.getClass()));
+ impl.apply(TopLevelResource.INSTANCE, in);
return projectApi.create(projects.parse(name));
- } catch (IOException | ConfigInvalidException e) {
+ } catch (IOException | ConfigInvalidException | PermissionBackendException e) {
throw new RestApiException("Cannot create project: " + e.getMessage(), e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index af619d7..19fdcfb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -30,14 +30,11 @@
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.UiAction;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
-import com.google.inject.util.Providers;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -48,6 +45,7 @@
private final Revisions revisions;
private final ChangeJson.Factory changeJsonFactory;
private final ChangeResource.Factory changeResourceFactory;
+ private final UiActions uiActions;
private final DynamicMap<RestView<ChangeResource>> changeViews;
private final DynamicSet<ActionVisitor> visitorSet;
@@ -56,11 +54,13 @@
Revisions revisions,
ChangeJson.Factory changeJsonFactory,
ChangeResource.Factory changeResourceFactory,
+ UiActions uiActions,
DynamicMap<RestView<ChangeResource>> changeViews,
DynamicSet<ActionVisitor> visitorSet) {
this.revisions = revisions;
this.changeJsonFactory = changeJsonFactory;
this.changeResourceFactory = changeResourceFactory;
+ this.uiActions = uiActions;
this.changeViews = changeViews;
this.visitorSet = visitorSet;
}
@@ -162,9 +162,9 @@
return out;
}
- Provider<CurrentUser> userProvider = Providers.of(ctl.getUser());
FluentIterable<UiAction.Description> descs =
- UiActions.from(changeViews, changeResourceFactory.create(ctl), userProvider);
+ uiActions.from(changeViews, changeResourceFactory.create(ctl));
+
// The followup action is a client-side only operation that does not
// have a server side handler. It must be manually registered into the
// resulting action map.
@@ -198,10 +198,10 @@
if (!rsrc.getControl().getUser().isIdentifiedUser()) {
return ImmutableMap.of();
}
+
Map<String, ActionInfo> out = new LinkedHashMap<>();
- Provider<CurrentUser> userProvider = Providers.of(rsrc.getControl().getUser());
ACTION:
- for (UiAction.Description d : UiActions.from(revisions, rsrc, userProvider)) {
+ for (UiAction.Description d : uiActions.from(revisions, rsrc)) {
ActionInfo actionInfo = new ActionInfo(d);
for (ActionVisitor visitor : visitors) {
if (!visitor.visit(d.getId(), actionInfo, changeInfo, revisionInfo)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 8ac89ae..6cb1926 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -363,9 +363,7 @@
update.setBranch(change.getDest().get());
update.setTopic(change.getTopic());
update.setPsDescription(patchSetDescription);
- if (isPrivate) {
- update.setPrivate(isPrivate);
- }
+ update.setPrivate(isPrivate);
boolean draft = status == Change.Status.DRAFT;
List<String> newGroups = groups;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index c86714a..4724ea1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -118,6 +118,7 @@
import com.google.gerrit.server.query.QueryResult;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
+import com.google.gerrit.server.query.change.PluginDefinedAttributesFactory;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -213,6 +214,7 @@
private boolean lazyLoad = true;
private AccountLoader accountLoader;
private FixInput fix;
+ private PluginDefinedAttributesFactory pluginDefinedAttributesFactory;
@Inject
ChangeJson(
@@ -276,6 +278,10 @@
return this;
}
+ public void setPluginDefinedAttributesFactory(PluginDefinedAttributesFactory pluginsFactory) {
+ this.pluginDefinedAttributesFactory = pluginsFactory;
+ }
+
public ChangeInfo format(ChangeResource rsrc) throws OrmException {
return format(changeDataFactory.create(db.get(), rsrc.getControl()));
}
@@ -520,6 +526,8 @@
out.labels = labelsFor(perm, ctl, cd, has(LABELS), has(DETAILED_LABELS));
out.submitted = getSubmittedOn(cd);
+ out.plugins =
+ pluginDefinedAttributesFactory != null ? pluginDefinedAttributesFactory.create(cd) : null;
if (out.labels != null && has(DETAILED_LABELS)) {
// If limited to specific patch sets but not the current patch set, don't
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
index 3b67930..5f6923e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
@@ -17,21 +17,29 @@
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
public class Check
implements RestReadView<ChangeResource>, RestModifyView<ChangeResource, FixInput> {
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> user;
private final ChangeJson.Factory jsonFactory;
@Inject
- Check(ChangeJson.Factory json) {
+ Check(PermissionBackend permissionBackend, Provider<CurrentUser> user, ChangeJson.Factory json) {
+ this.permissionBackend = permissionBackend;
+ this.user = user;
this.jsonFactory = json;
}
@@ -42,12 +50,10 @@
@Override
public Response<ChangeInfo> apply(ChangeResource rsrc, FixInput input)
- throws RestApiException, OrmException {
+ throws RestApiException, OrmException, PermissionBackendException {
ChangeControl ctl = rsrc.getControl();
- if (!ctl.isOwner()
- && !ctl.getProjectControl().isOwner()
- && !ctl.getUser().getCapabilities().canMaintainServer()) {
- throw new AuthException("Cannot fix change");
+ if (!ctl.isOwner() && !ctl.getProjectControl().isOwner()) {
+ permissionBackend.user(user).check(GlobalPermission.MAINTAIN_SERVER);
}
return Response.withMustRevalidate(newChangeJson().fix(input).format(rsrc));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
index 9257445..ab92281 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
@@ -18,8 +18,12 @@
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.Index.Input;
import com.google.gerrit.server.index.change.ChangeIndexer;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -32,20 +36,28 @@
public static class Input {}
private final Provider<ReviewDb> db;
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> user;
private final ChangeIndexer indexer;
@Inject
- Index(Provider<ReviewDb> db, ChangeIndexer indexer) {
+ Index(
+ Provider<ReviewDb> db,
+ PermissionBackend permissionBackend,
+ Provider<CurrentUser> user,
+ ChangeIndexer indexer) {
this.db = db;
+ this.permissionBackend = permissionBackend;
+ this.user = user;
this.indexer = indexer;
}
@Override
public Response<?> apply(ChangeResource rsrc, Input input)
- throws IOException, AuthException, OrmException {
+ throws IOException, AuthException, OrmException, PermissionBackendException {
ChangeControl ctl = rsrc.getControl();
- if (!ctl.isOwner() && !ctl.getUser().getCapabilities().canMaintainServer()) {
- throw new AuthException("Only change owner or server maintainer can reindex");
+ if (!ctl.isOwner()) {
+ permissionBackend.user(user).check(GlobalPermission.MAINTAIN_SERVER);
}
indexer.index(db.get(), rsrc.getChange());
return Response.none();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CheckConsistency.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CheckConsistency.java
new file mode 100644
index 0000000..f424995
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CheckConsistency.java
@@ -0,0 +1,67 @@
+// 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.config;
+
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.CheckAccountExternalIdsResultInfo;
+import com.google.gerrit.extensions.api.config.ConsistencyCheckInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.externalids.ExternalIdsConsistencyChecker;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+
+@Singleton
+public class CheckConsistency implements RestModifyView<ConfigResource, ConsistencyCheckInput> {
+ private final Provider<IdentifiedUser> userProvider;
+ private final ExternalIdsConsistencyChecker externalIdsConsistencyChecker;
+
+ @Inject
+ CheckConsistency(
+ Provider<IdentifiedUser> currentUser,
+ ExternalIdsConsistencyChecker externalIdsConsistencyChecker) {
+ this.userProvider = currentUser;
+ this.externalIdsConsistencyChecker = externalIdsConsistencyChecker;
+ }
+
+ @Override
+ public ConsistencyCheckInfo apply(ConfigResource resource, ConsistencyCheckInput input)
+ throws RestApiException, IOException {
+ IdentifiedUser user = userProvider.get();
+ if (!user.isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+ if (!user.getCapabilities().canAccessDatabase()) {
+ throw new AuthException("not allowed to run consistency checks");
+ }
+
+ if (input == null || input.checkAccountExternalIds == null) {
+ throw new BadRequestException("input required");
+ }
+
+ ConsistencyCheckInfo consistencyCheckInfo = new ConsistencyCheckInfo();
+ if (input.checkAccountExternalIds != null) {
+ consistencyCheckInfo.checkAccountExternalIdsResult =
+ new CheckAccountExternalIdsResultInfo(externalIdsConsistencyChecker.check());
+ }
+
+ return consistencyCheckInfo;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java
index 5e19091..366dae1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java
@@ -23,6 +23,9 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.FlushCache.Input;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -34,17 +37,20 @@
public static final String WEB_SESSIONS = "web_sessions";
+ private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> self;
@Inject
- public FlushCache(Provider<CurrentUser> self) {
+ public FlushCache(PermissionBackend permissionBackend, Provider<CurrentUser> self) {
+ this.permissionBackend = permissionBackend;
this.self = self;
}
@Override
- public Response<String> apply(CacheResource rsrc, Input input) throws AuthException {
- if (WEB_SESSIONS.equals(rsrc.getName()) && !self.get().getCapabilities().canMaintainServer()) {
- throw new AuthException(String.format("only site maintainers can flush %s", WEB_SESSIONS));
+ public Response<String> apply(CacheResource rsrc, Input input)
+ throws AuthException, PermissionBackendException {
+ if (WEB_SESSIONS.equals(rsrc.getName())) {
+ permissionBackend.user(self).check(GlobalPermission.MAINTAIN_SERVER);
}
rsrc.getCache().invalidateAll();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index c9f3e10..9754a3a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -107,6 +107,7 @@
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.events.EventsMetrics;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.AbandonOp;
import com.google.gerrit.server.git.ChangeMessageModifier;
import com.google.gerrit.server.git.EmailMerge;
@@ -166,6 +167,7 @@
import com.google.gerrit.server.project.SectionSortCache;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.query.change.ChangeQueryProcessor;
import com.google.gerrit.server.query.change.ConflictsCacheImpl;
import com.google.gerrit.server.ssh.SshAddressesModule;
import com.google.gerrit.server.tools.ToolsCatalog;
@@ -292,6 +294,7 @@
bind(AccountControl.Factory.class);
install(new AuditModule());
+ bind(UiActions.class);
install(new com.google.gerrit.server.access.Module());
install(new com.google.gerrit.server.account.Module());
install(new com.google.gerrit.server.api.Module());
@@ -378,6 +381,8 @@
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class);
DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeHasOperandFactory.class);
+ DynamicMap.mapOf(binder(), ChangeQueryProcessor.ChangeAttributeFactory.class);
+
install(new GitwebConfig.LegacyModule(cfg));
bind(AnonymousUser.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTasks.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTasks.java
index 7e9bd71..be2edfd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTasks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTasks.java
@@ -19,11 +19,13 @@
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.TaskInfoFactory;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.ProjectTask;
import com.google.gerrit.server.git.WorkQueue.Task;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.IdGenerator;
@@ -41,30 +43,40 @@
@Singleton
public class ListTasks implements RestReadView<ConfigResource> {
+ private final PermissionBackend permissionBackend;
private final WorkQueue workQueue;
private final ProjectCache projectCache;
- private final Provider<IdentifiedUser> self;
+ private final Provider<CurrentUser> self;
@Inject
- public ListTasks(WorkQueue workQueue, ProjectCache projectCache, Provider<IdentifiedUser> self) {
+ public ListTasks(
+ PermissionBackend permissionBackend,
+ WorkQueue workQueue,
+ ProjectCache projectCache,
+ Provider<CurrentUser> self) {
+ this.permissionBackend = permissionBackend;
this.workQueue = workQueue;
this.projectCache = projectCache;
this.self = self;
}
@Override
- public List<TaskInfo> apply(ConfigResource resource) throws AuthException {
+ public List<TaskInfo> apply(ConfigResource resource)
+ throws AuthException, PermissionBackendException {
CurrentUser user = self.get();
if (!user.isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
List<TaskInfo> allTasks = getTasks();
- if (user.getCapabilities().canViewQueue()) {
+ try {
+ permissionBackend.user(user).check(GlobalPermission.VIEW_QUEUE);
return allTasks;
+ } catch (AuthException e) {
+ // Fall through to filter tasks.
}
- Map<String, Boolean> visibilityCache = new HashMap<>();
+ Map<String, Boolean> visibilityCache = new HashMap<>();
List<TaskInfo> visibleTasks = new ArrayList<>();
for (TaskInfo task : allTasks) {
if (task.projectName != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
index a05058e..612fea2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -36,6 +36,7 @@
child(CONFIG_KIND, "top-menus").to(TopMenuCollection.class);
get(CONFIG_KIND, "version").to(GetVersion.class);
get(CONFIG_KIND, "info").to(GetServerInfo.class);
+ post(CONFIG_KIND, "check").to(CheckConsistency.class);
get(CONFIG_KIND, "preferences").to(GetPreferences.class);
put(CONFIG_KIND, "preferences").to(SetPreferences.class);
get(CONFIG_KIND, "preferences.diff").to(GetDiffPreferences.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java
index 3cfa2b9..d08f0a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PostCaches.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.config.PostCaches.Input;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
@@ -66,7 +67,8 @@
@Override
public Response<String> apply(ConfigResource rsrc, Input input)
- throws AuthException, BadRequestException, UnprocessableEntityException {
+ throws AuthException, BadRequestException, UnprocessableEntityException,
+ PermissionBackendException {
if (input == null || input.operation == null) {
throw new BadRequestException("operation must be specified");
}
@@ -90,7 +92,7 @@
}
}
- private void flushAll() throws AuthException {
+ private void flushAll() throws AuthException, PermissionBackendException {
for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
CacheResource cacheResource =
new CacheResource(e.getPluginName(), e.getExportName(), e.getProvider());
@@ -101,7 +103,8 @@
}
}
- private void flush(List<String> cacheNames) throws UnprocessableEntityException, AuthException {
+ private void flush(List<String> cacheNames)
+ throws UnprocessableEntityException, AuthException, PermissionBackendException {
List<CacheResource> cacheResources = new ArrayList<>(cacheNames.size());
for (String n : cacheNames) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/TasksCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/TasksCollection.java
index b239856..b33b1c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/TasksCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/TasksCollection.java
@@ -21,10 +21,12 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.ProjectTask;
import com.google.gerrit.server.git.WorkQueue.Task;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
@@ -36,7 +38,8 @@
private final DynamicMap<RestView<TaskResource>> views;
private final ListTasks list;
private final WorkQueue workQueue;
- private final Provider<IdentifiedUser> self;
+ private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
@Inject
@@ -44,12 +47,14 @@
DynamicMap<RestView<TaskResource>> views,
ListTasks list,
WorkQueue workQueue,
- Provider<IdentifiedUser> self,
+ Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
ProjectCache projectCache) {
this.views = views;
this.list = list;
this.workQueue = workQueue;
this.self = self;
+ this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
}
@@ -60,30 +65,37 @@
@Override
public TaskResource parse(ConfigResource parent, IdString id)
- throws ResourceNotFoundException, AuthException {
+ throws ResourceNotFoundException, AuthException, PermissionBackendException {
CurrentUser user = self.get();
if (!user.isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
+ int taskId;
try {
- int taskId = (int) Long.parseLong(id.get(), 16);
- Task<?> task = workQueue.getTask(taskId);
- if (task != null) {
- if (self.get().getCapabilities().canViewQueue()) {
- return new TaskResource(task);
- } else if (task instanceof ProjectTask) {
- ProjectTask<?> projectTask = ((ProjectTask<?>) task);
- ProjectState e = projectCache.get(projectTask.getProjectNameKey());
- if (e != null && e.controlFor(user).isVisible()) {
- return new TaskResource(task);
- }
- }
- }
- throw new ResourceNotFoundException(id);
+ taskId = (int) Long.parseLong(id.get(), 16);
} catch (NumberFormatException e) {
throw new ResourceNotFoundException(id);
}
+
+ Task<?> task = workQueue.getTask(taskId);
+ if (task != null) {
+ try {
+ permissionBackend.user(user).check(GlobalPermission.VIEW_QUEUE);
+ return new TaskResource(task);
+ } catch (AuthException e) {
+ // Fall through and try filtering.
+ }
+
+ if (task instanceof ProjectTask) {
+ ProjectTask<?> projectTask = ((ProjectTask<?>) task);
+ ProjectState e = projectCache.get(projectTask.getProjectNameKey());
+ if (e != null && e.controlFor(user).isVisible()) {
+ return new TaskResource(task);
+ }
+ }
+ }
+ throw new ResourceNotFoundException(id);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
index 1a8a788..0467c92 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/data/ChangeAttribute.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.data;
+import com.google.gerrit.extensions.common.PluginDefinedInfo;
import com.google.gerrit.reviewdb.client.Change;
import java.util.List;
@@ -43,4 +44,5 @@
public List<DependencyAttribute> neededBy;
public List<SubmitRecordAttribute> submitRecords;
public List<AccountAttribute> allReviewers;
+ public List<PluginDefinedInfo> plugins;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
index 85ee4f9..bd5d6a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -16,20 +16,27 @@
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.CapabilityUtils;
+import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import java.util.Objects;
+import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+@Singleton
public class UiActions {
private static final Logger log = LoggerFactory.getLogger(UiActions.class);
@@ -37,57 +44,70 @@
return UiAction.Description::isEnabled;
}
- public static <R extends RestResource> FluentIterable<UiAction.Description> from(
- RestCollection<?, R> collection, R resource, Provider<CurrentUser> userProvider) {
- return from(collection.views(), resource, userProvider);
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
+
+ @Inject
+ UiActions(PermissionBackend permissionBackend, Provider<CurrentUser> userProvider) {
+ this.permissionBackend = permissionBackend;
+ this.userProvider = userProvider;
}
- public static <R extends RestResource> FluentIterable<UiAction.Description> from(
- DynamicMap<RestView<R>> views, R resource, Provider<CurrentUser> userProvider) {
+ public <R extends RestResource> FluentIterable<UiAction.Description> from(
+ RestCollection<?, R> collection, R resource) {
+ return from(collection.views(), resource);
+ }
+
+ public <R extends RestResource> FluentIterable<UiAction.Description> from(
+ DynamicMap<RestView<R>> views, R resource) {
return FluentIterable.from(views)
- .transform(
- (DynamicMap.Entry<RestView<R>> e) -> {
- int d = e.getExportName().indexOf('.');
- if (d < 0) {
- return null;
- }
-
- RestView<R> view;
- try {
- view = e.getProvider().get();
- } catch (RuntimeException err) {
- log.error(
- String.format(
- "error creating view %s.%s", e.getPluginName(), e.getExportName()),
- err);
- return null;
- }
-
- if (!(view instanceof UiAction)) {
- return null;
- }
-
- try {
- CapabilityUtils.checkRequiresCapability(
- userProvider, e.getPluginName(), view.getClass());
- } catch (AuthException exc) {
- return null;
- }
-
- UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
- if (dsc == null || !dsc.isVisible()) {
- return null;
- }
-
- String name = e.getExportName().substring(d + 1);
- PrivateInternals_UiActionDescription.setMethod(
- dsc, e.getExportName().substring(0, d));
- PrivateInternals_UiActionDescription.setId(
- dsc, "gerrit".equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name);
- return dsc;
- })
+ .transform((e) -> describe(e, resource))
.filter(Objects::nonNull);
}
- private UiActions() {}
+ @Nullable
+ private <R extends RestResource> UiAction.Description describe(
+ DynamicMap.Entry<RestView<R>> e, R resource) {
+ int d = e.getExportName().indexOf('.');
+ if (d < 0) {
+ return null;
+ }
+
+ RestView<R> view;
+ try {
+ view = e.getProvider().get();
+ } catch (RuntimeException err) {
+ log.error(
+ String.format("error creating view %s.%s", e.getPluginName(), e.getExportName()), err);
+ return null;
+ }
+
+ if (!(view instanceof UiAction)) {
+ return null;
+ }
+
+ try {
+ Set<GlobalOrPluginPermission> need =
+ GlobalPermission.fromAnnotation(e.getPluginName(), view.getClass());
+ if (!need.isEmpty() && permissionBackend.user(userProvider).test(need).isEmpty()) {
+ // A permission is required, but test returned no candidates.
+ return null;
+ }
+ } catch (PermissionBackendException err) {
+ log.error(
+ String.format("exception testing view %s.%s", e.getPluginName(), e.getExportName()), err);
+ return null;
+ }
+
+ UiAction.Description dsc = ((UiAction<R>) view).getDescription(resource);
+ if (dsc == null || !dsc.isVisible()) {
+ return null;
+ }
+
+ String name = e.getExportName().substring(d + 1);
+ PrivateInternals_UiActionDescription.setMethod(dsc, e.getExportName().substring(0, d));
+ PrivateInternals_UiActionDescription.setId(
+ dsc, "gerrit".equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name);
+ return dsc;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
index a368190..5c3cdf2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.value.AutoValue;
+import java.util.function.IntConsumer;
import org.eclipse.jgit.lib.Config;
/**
@@ -30,29 +31,61 @@
private static final int DEFAULT_MAX_TERMS = 1024;
public static IndexConfig createDefault() {
- return create(0, 0, DEFAULT_MAX_TERMS);
+ return builder().build();
}
- public static IndexConfig fromConfig(Config cfg) {
- return create(
- cfg.getInt("index", null, "maxLimit", 0),
- cfg.getInt("index", null, "maxPages", 0),
- cfg.getInt("index", null, "maxTerms", 0));
+ public static Builder fromConfig(Config cfg) {
+ Builder b = builder();
+ setIfPresent(cfg, "maxLimit", b::maxLimit);
+ setIfPresent(cfg, "maxPages", b::maxPages);
+ setIfPresent(cfg, "maxTerms", b::maxTerms);
+ return b;
}
- public static IndexConfig create(int maxLimit, int maxPages, int maxTerms) {
- return new AutoValue_IndexConfig(
- checkLimit(maxLimit, "maxLimit", Integer.MAX_VALUE),
- checkLimit(maxPages, "maxPages", Integer.MAX_VALUE),
- checkLimit(maxTerms, "maxTerms", DEFAULT_MAX_TERMS));
- }
-
- private static int checkLimit(int limit, String name, int defaultValue) {
- if (limit == 0) {
- return defaultValue;
+ private static void setIfPresent(Config cfg, String name, IntConsumer setter) {
+ int n = cfg.getInt("index", null, name, 0);
+ if (n != 0) {
+ setter.accept(n);
}
+ }
+
+ public static Builder builder() {
+ return new AutoValue_IndexConfig.Builder()
+ .maxLimit(Integer.MAX_VALUE)
+ .maxPages(Integer.MAX_VALUE)
+ .maxTerms(DEFAULT_MAX_TERMS)
+ .separateChangeSubIndexes(false);
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder maxLimit(int maxLimit);
+
+ abstract int maxLimit();
+
+ public abstract Builder maxPages(int maxPages);
+
+ abstract int maxPages();
+
+ public abstract Builder maxTerms(int maxTerms);
+
+ abstract int maxTerms();
+
+ public abstract Builder separateChangeSubIndexes(boolean separate);
+
+ abstract IndexConfig autoBuild();
+
+ public IndexConfig build() {
+ IndexConfig cfg = autoBuild();
+ checkLimit(cfg.maxLimit(), "maxLimit");
+ checkLimit(cfg.maxPages(), "maxPages");
+ checkLimit(cfg.maxTerms(), "maxTerms");
+ return cfg;
+ }
+ }
+
+ private static void checkLimit(int limit, String name) {
checkArgument(limit > 0, "%s must be positive: %s", name, limit);
- return limit;
}
/**
@@ -71,4 +104,9 @@
* for performance reasons.
*/
public abstract int maxTerms();
+
+ /**
+ * @return whether different subsets of changes may be stored in different physical sub-indexes.
+ */
+ public abstract boolean separateChangeSubIndexes();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index afa0617..4ecd684 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -455,6 +455,15 @@
return draftIds;
}
+ public void flush() throws IOException {
+ if (changeRepo != null) {
+ changeRepo.flush();
+ }
+ if (allUsersRepo != null) {
+ allUsersRepo.flush();
+ }
+ }
+
@Nullable
public BatchRefUpdate execute() throws OrmException, IOException {
return execute(false);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalOrPluginPermission.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalOrPluginPermission.java
new file mode 100644
index 0000000..d2198d3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalOrPluginPermission.java
@@ -0,0 +1,24 @@
+// 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.permissions;
+
+/** A {@link GlobalPermission} or a {@link PluginPermission}. */
+public interface GlobalOrPluginPermission {
+ /** @return name used in {@code project.config} permissions. */
+ public String permissionName();
+
+ /** @return readable identifier of this permission for exception message. */
+ public String describeForException();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java
index 575a08b..4111253 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalPermission.java
@@ -14,10 +14,22 @@
package com.google.gerrit.server.permissions;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.CapabilityScope;
+import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.Locale;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public enum GlobalPermission {
+/** Global server permissions built into Gerrit. */
+public enum GlobalPermission implements GlobalOrPluginPermission {
ACCESS_DATABASE(GlobalCapability.ACCESS_DATABASE),
ADMINISTRATE_SERVER(GlobalCapability.ADMINISTRATE_SERVER),
CREATE_ACCOUNT(GlobalCapability.CREATE_ACCOUNT),
@@ -37,6 +49,63 @@
VIEW_PLUGINS(GlobalCapability.VIEW_PLUGINS),
VIEW_QUEUE(GlobalCapability.VIEW_QUEUE);
+ private static final Logger log = LoggerFactory.getLogger(GlobalPermission.class);
+ private static final ImmutableMap<String, GlobalPermission> BY_NAME;
+
+ static {
+ ImmutableMap.Builder<String, GlobalPermission> m = ImmutableMap.builder();
+ for (GlobalPermission p : values()) {
+ m.put(p.permissionName(), p);
+ }
+ BY_NAME = m.build();
+ }
+
+ @Nullable
+ public static GlobalPermission byName(String name) {
+ return BY_NAME.get(name);
+ }
+
+ /**
+ * Extracts the {@code @RequiresCapability} or {@code @RequiresAnyCapability} annotation.
+ *
+ * @param pluginName name of the declaring plugin. May be {@code null} or {@code "gerrit"} for
+ * classes originating from the core server.
+ * @param clazz target class to extract annotation from.
+ * @return empty set if no annotations were found, or a collection of permissions, any of which
+ * are suitable to enable access.
+ * @throws PermissionBackendException the annotation could not be parsed.
+ */
+ public static Set<GlobalOrPluginPermission> fromAnnotation(
+ @Nullable String pluginName, Class<?> clazz) throws PermissionBackendException {
+ RequiresCapability rc = findAnnotation(clazz, RequiresCapability.class);
+ RequiresAnyCapability rac = findAnnotation(clazz, RequiresAnyCapability.class);
+ if (rc != null && rac != null) {
+ log.error(
+ String.format(
+ "Class %s uses both @%s and @%s",
+ clazz.getName(),
+ RequiresCapability.class.getSimpleName(),
+ RequiresAnyCapability.class.getSimpleName()));
+ throw new PermissionBackendException("cannot extract permission");
+ } else if (rc != null) {
+ return Collections.singleton(
+ resolve(pluginName, rc.value(), rc.scope(), clazz, RequiresCapability.class));
+ } else if (rac != null) {
+ Set<GlobalOrPluginPermission> r = new LinkedHashSet<>();
+ for (String capability : rac.value()) {
+ r.add(resolve(pluginName, capability, rac.scope(), clazz, RequiresAnyCapability.class));
+ }
+ return Collections.unmodifiableSet(r);
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
+ public static Set<GlobalOrPluginPermission> fromAnnotation(Class<?> clazz)
+ throws PermissionBackendException {
+ return fromAnnotation(null, clazz);
+ }
+
private final String name;
GlobalPermission(String name) {
@@ -44,11 +113,54 @@
}
/** @return name used in {@code project.config} permissions. */
+ @Override
public String permissionName() {
return name;
}
+ @Override
public String describeForException() {
return toString().toLowerCase(Locale.US).replace('_', ' ');
}
+
+ private static GlobalOrPluginPermission resolve(
+ @Nullable String pluginName,
+ String capability,
+ CapabilityScope scope,
+ Class<?> clazz,
+ Class<?> annotationClass)
+ throws PermissionBackendException {
+ if (pluginName != null
+ && !"gerrit".equals(pluginName)
+ && (scope == CapabilityScope.PLUGIN || scope == CapabilityScope.CONTEXT)) {
+ return new PluginPermission(pluginName, capability);
+ }
+
+ if (scope == CapabilityScope.PLUGIN) {
+ log.error(
+ String.format(
+ "Class %s uses @%s(scope=%s), but is not within a plugin",
+ clazz.getName(), annotationClass.getSimpleName(), scope.name()));
+ throw new PermissionBackendException("cannot extract permission");
+ }
+
+ GlobalPermission perm = byName(capability);
+ if (perm == null) {
+ log.error(
+ String.format("Class %s requires unknown capability %s", clazz.getName(), capability));
+ throw new PermissionBackendException("cannot extract permission");
+ }
+ return perm;
+ }
+
+ @Nullable
+ private static <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annotation) {
+ for (; clazz != null; clazz = clazz.getSuperclass()) {
+ T t = clazz.getAnnotation(annotation);
+ if (t != null) {
+ return t;
+ }
+ }
+ return null;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
index b3a858c..83b9182 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -31,6 +31,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.Iterator;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -134,18 +135,47 @@
}
/** Verify scoped user can {@code perm}, throwing if denied. */
- public abstract void check(GlobalPermission perm)
+ public abstract void check(GlobalOrPluginPermission perm)
throws AuthException, PermissionBackendException;
- /** Filter {@code permSet} to permissions scoped user might be able to perform. */
- public abstract Set<GlobalPermission> test(Collection<GlobalPermission> permSet)
- throws PermissionBackendException;
-
- public boolean test(GlobalPermission perm) throws PermissionBackendException {
- return test(EnumSet.of(perm)).contains(perm);
+ /**
+ * Verify scoped user can perform at least one listed permission.
+ *
+ * <p>If {@code any} is empty, the method completes normally and allows the caller to continue.
+ * Since no permissions were supplied to check, its assumed no permissions are necessary to
+ * continue with the caller's operation.
+ *
+ * <p>If the user has at least one of the permissions in {@code any}, the method completes
+ * normally, possibly without checking all listed permissions.
+ *
+ * <p>If {@code any} is non-empty and the user has none, {@link AuthException} is thrown for one
+ * of the failed permissions.
+ *
+ * @param any set of permissions to check.
+ */
+ public void checkAny(Set<GlobalOrPluginPermission> any)
+ throws PermissionBackendException, AuthException {
+ for (Iterator<GlobalOrPluginPermission> itr = any.iterator(); itr.hasNext(); ) {
+ try {
+ check(itr.next());
+ return;
+ } catch (AuthException err) {
+ if (!itr.hasNext()) {
+ throw err;
+ }
+ }
+ }
}
- public boolean testOrFalse(GlobalPermission perm) {
+ /** Filter {@code permSet} to permissions scoped user might be able to perform. */
+ public abstract <T extends GlobalOrPluginPermission> Set<T> test(Collection<T> permSet)
+ throws PermissionBackendException;
+
+ public boolean test(GlobalOrPluginPermission perm) throws PermissionBackendException {
+ return test(Collections.singleton(perm)).contains(perm);
+ }
+
+ public boolean testOrFalse(GlobalOrPluginPermission perm) {
try {
return test(perm);
} catch (PermissionBackendException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PluginPermission.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PluginPermission.java
new file mode 100644
index 0000000..6d503df
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PluginPermission.java
@@ -0,0 +1,67 @@
+// 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.permissions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Objects;
+
+/** A global capability type permission used by a plugin. */
+public class PluginPermission implements GlobalOrPluginPermission {
+ private final String pluginName;
+ private final String capability;
+
+ public PluginPermission(String pluginName, String capability) {
+ this.pluginName = checkNotNull(pluginName, "pluginName");
+ this.capability = checkNotNull(capability, "capability");
+ }
+
+ public String pluginName() {
+ return pluginName;
+ }
+
+ public String capability() {
+ return capability;
+ }
+
+ @Override
+ public String permissionName() {
+ return pluginName + '-' + capability;
+ }
+
+ @Override
+ public String describeForException() {
+ return capability + " for plugin " + pluginName;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(pluginName, capability);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof PluginPermission) {
+ PluginPermission b = (PluginPermission) other;
+ return pluginName.equals(b.pluginName) && capability.equals(b.capability);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "PluginPermission[plugin=" + pluginName + ", capability=" + capability + ']';
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DelegatingClassLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DelegatingClassLoader.java
new file mode 100644
index 0000000..3908b72
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DelegatingClassLoader.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.plugins;
+
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+public class DelegatingClassLoader extends ClassLoader {
+ public ClassLoader target;
+
+ public DelegatingClassLoader(ClassLoader parent, ClassLoader target) {
+ super(parent);
+ this.target = target;
+ }
+
+ @Override
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ String path = name.replace('.', '/') + ".class";
+ InputStream resource = target.getResourceAsStream(path);
+ if (resource != null) {
+ try {
+ byte[] bytes = ByteStreams.toByteArray(resource);
+ return defineClass(name, bytes, 0, bytes.length);
+ } catch (IOException e) {
+ }
+ }
+ throw new ClassNotFoundException(name);
+ }
+
+ @Override
+ public URL getResource(String name) {
+ URL rtn = getParent().getResource(name);
+ if (rtn == null) {
+ rtn = target.getResource(name);
+ }
+ return rtn;
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ Enumeration<URL> rtn = getParent().getResources(name);
+ if (rtn == null) {
+ rtn = target.getResources(name);
+ }
+ return rtn;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String name) {
+ InputStream rtn = getParent().getResourceAsStream(name);
+ if (rtn == null) {
+ rtn = target.getResourceAsStream(name);
+ }
+ return rtn;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
index 0dcb5f8..4f83d5f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
@@ -31,7 +31,6 @@
import com.google.gerrit.server.config.ProjectConfigEntry;
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.TransferConfig;
-import com.google.inject.util.Providers;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -45,6 +44,7 @@
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
+ UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) {
ProjectState projectState = control.getProjectState();
Project p = control.getProject();
@@ -126,8 +126,7 @@
getPluginConfig(control.getProjectState(), pluginConfigEntries, cfgFactory, allProjects);
actions = new TreeMap<>();
- for (UiAction.Description d :
- UiActions.from(views, new ProjectResource(control), Providers.of(control.getUser()))) {
+ for (UiAction.Description d : uiActions.from(views, new ProjectResource(control))) {
actions.put(d.getId(), new ActionInfo(d));
}
this.theme = projectState.getTheme();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java
index a8f9efa..feaaccc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java
@@ -16,11 +16,12 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.Sets;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.FailedPermissionBackend;
-import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
@@ -65,17 +66,18 @@
}
@Override
- public void check(GlobalPermission perm) throws AuthException, PermissionBackendException {
+ public void check(GlobalOrPluginPermission perm)
+ throws AuthException, PermissionBackendException {
if (!can(perm)) {
throw new AuthException(perm.describeForException() + " not permitted");
}
}
@Override
- public Set<GlobalPermission> test(Collection<GlobalPermission> permSet)
+ public <T extends GlobalOrPluginPermission> Set<T> test(Collection<T> permSet)
throws PermissionBackendException {
- EnumSet<GlobalPermission> ok = EnumSet.noneOf(GlobalPermission.class);
- for (GlobalPermission perm : permSet) {
+ Set<T> ok = newSet(permSet);
+ for (T perm : permSet) {
if (can(perm)) {
ok.add(perm);
}
@@ -83,8 +85,18 @@
return ok;
}
- private boolean can(GlobalPermission perm) throws PermissionBackendException {
+ private boolean can(GlobalOrPluginPermission perm) throws PermissionBackendException {
return user.getCapabilities().doCanForDefaultPermissionBackend(perm);
}
}
+
+ private static <T extends GlobalOrPluginPermission> Set<T> newSet(Collection<T> permSet) {
+ if (permSet instanceof EnumSet) {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ Set<T> s = ((EnumSet) permSet).clone();
+ s.clear();
+ return s;
+ }
+ return Sets.newHashSetWithExpectedSize(permSet.size());
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index 8192e29..b1ba281 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
+import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.TransferConfig;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -33,6 +34,7 @@
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects;
+ private final UiActions uiActions;
private final DynamicMap<RestView<ProjectResource>> views;
@Inject
@@ -42,12 +44,14 @@
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
+ UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) {
this.serverEnableSignedPush = serverEnableSignedPush;
this.config = config;
this.pluginConfigEntries = pluginConfigEntries;
this.allProjects = allProjects;
this.cfgFactory = cfgFactory;
+ this.uiActions = uiActions;
this.views = views;
}
@@ -60,6 +64,7 @@
pluginConfigEntries,
cfgFactory,
allProjects,
+ uiActions,
views);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index a5b6458..09a6b86 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -30,7 +30,6 @@
import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
-import com.google.inject.util.Providers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -48,6 +47,7 @@
public class ListBranches implements RestReadView<ProjectResource> {
private final GitRepositoryManager repoManager;
private final DynamicMap<RestView<BranchResource>> branchViews;
+ private final UiActions uiActions;
private final WebLinks webLinks;
@Option(
@@ -99,9 +99,11 @@
public ListBranches(
GitRepositoryManager repoManager,
DynamicMap<RestView<BranchResource>> branchViews,
+ UiActions uiActions,
WebLinks webLinks) {
this.repoManager = repoManager;
this.branchViews = branchViews;
+ this.uiActions = uiActions;
this.webLinks = webLinks;
}
@@ -197,16 +199,15 @@
info.ref = ref.getName();
info.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
info.canDelete = !targets.contains(ref.getName()) && refControl.canDelete() ? true : null;
- for (UiAction.Description d :
- UiActions.from(
- branchViews,
- new BranchResource(refControl.getProjectControl(), info),
- Providers.of(refControl.getUser()))) {
+
+ BranchResource rsrc = new BranchResource(refControl.getProjectControl(), info);
+ for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
if (info.actions == null) {
info.actions = new TreeMap<>();
}
info.actions.put(d.getId(), new ActionInfo(d));
}
+
List<WebLinkInfo> links =
webLinks.getBranchLinks(
refControl.getProjectControl().getProject().getName(), ref.getName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 8705f3b..806c01a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -36,6 +36,7 @@
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.config.ProjectConfigEntry;
+import com.google.gerrit.server.extensions.webui.UiActions;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.TransferConfig;
@@ -64,6 +65,7 @@
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final PluginConfigFactory cfgFactory;
private final AllProjectsName allProjects;
+ private final UiActions uiActions;
private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<CurrentUser> user;
@@ -77,6 +79,7 @@
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
+ UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views,
Provider<CurrentUser> user) {
this.serverEnableSignedPush = serverEnableSignedPush;
@@ -87,6 +90,7 @@
this.pluginConfigEntries = pluginConfigEntries;
this.cfgFactory = cfgFactory;
this.allProjects = allProjects;
+ this.uiActions = uiActions;
this.views = views;
this.user = user;
}
@@ -185,6 +189,7 @@
pluginConfigEntries,
cfgFactory,
allProjects,
+ uiActions,
views);
} catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(projectName.get());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
index 6627687..2abcd58 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
@@ -16,25 +16,25 @@
/** Predicate to filter a field by matching integer value. */
public abstract class IntPredicate<T> extends OperatorPredicate<T> {
- private final int value;
+ private final int intValue;
public IntPredicate(final String name, final String value) {
super(name, value);
- this.value = Integer.parseInt(value);
+ this.intValue = Integer.parseInt(value);
}
- public IntPredicate(final String name, final int value) {
- super(name, String.valueOf(value));
- this.value = value;
+ public IntPredicate(final String name, final int intValue) {
+ super(name, String.valueOf(intValue));
+ this.intValue = intValue;
}
public int intValue() {
- return value;
+ return intValue;
}
@Override
public int hashCode() {
- return getOperator().hashCode() * 31 + value;
+ return getOperator().hashCode() * 31 + intValue;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
index 96a30ee..9413c5d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
@@ -18,10 +18,10 @@
/** Predicate to filter a field by matching value. */
public abstract class OperatorPredicate<T> extends Predicate<T> {
- private final String name;
- private final String value;
+ protected final String name;
+ protected final String value;
- protected OperatorPredicate(final String name, final String value) {
+ public OperatorPredicate(final String name, final String value) {
this.name = name;
this.value = value;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
index 0a74647..e5ed44d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
@@ -20,9 +20,9 @@
import com.google.gwtorm.server.OrmException;
public class AccountIsVisibleToPredicate extends IsVisibleToPredicate<AccountState> {
- private final AccountControl accountControl;
+ protected final AccountControl accountControl;
- AccountIsVisibleToPredicate(AccountControl accountControl) {
+ public AccountIsVisibleToPredicate(AccountControl accountControl) {
super(AccountQueryBuilder.FIELD_VISIBLETO, describe(accountControl.getUser()));
this.accountControl = accountControl;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
index b3cdd6a..05bf24bd2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
@@ -19,7 +19,7 @@
import com.google.gwtorm.server.OrmException;
public class AddedPredicate extends IntegerRangeChangePredicate {
- AddedPredicate(String value) throws QueryParseException {
+ public AddedPredicate(String value) throws QueryParseException {
super(ChangeField.ADDED, value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
index 7d51217..b9c4694 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
@@ -20,9 +20,9 @@
import java.util.Date;
public class AfterPredicate extends TimestampRangeChangePredicate {
- private final Date cut;
+ protected final Date cut;
- AfterPredicate(String value) throws QueryParseException {
+ public AfterPredicate(String value) throws QueryParseException {
super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_BEFORE, value);
cut = parse(value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
index 0cd76bb..a5f4965 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -25,9 +25,9 @@
import java.sql.Timestamp;
public class AgePredicate extends TimestampRangeChangePredicate {
- private final long cut;
+ protected final long cut;
- AgePredicate(String value) {
+ public AgePredicate(String value) {
super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_AGE, value);
long s = ConfigUtil.getTimeUnit(getValue(), 0, SECONDS);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AssigneePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AssigneePredicate.java
index 38622ed..848fd09 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AssigneePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AssigneePredicate.java
@@ -18,10 +18,10 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class AssigneePredicate extends ChangeIndexPredicate {
- private final Account.Id id;
+public class AssigneePredicate extends ChangeIndexPredicate {
+ protected final Account.Id id;
- AssigneePredicate(Account.Id id) {
+ public AssigneePredicate(Account.Id id) {
super(ChangeField.ASSIGNEE, id.toString());
this.id = id;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AuthorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AuthorPredicate.java
index dccd17e..3ee3352 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AuthorPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AuthorPredicate.java
@@ -22,7 +22,7 @@
import java.io.IOException;
public class AuthorPredicate extends ChangeIndexPredicate {
- AuthorPredicate(String value) {
+ public AuthorPredicate(String value) {
super(AUTHOR, FIELD_AUTHOR, value.toLowerCase());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
index 9e443c9..bc57f15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
@@ -20,9 +20,9 @@
import java.util.Date;
public class BeforePredicate extends TimestampRangeChangePredicate {
- private final Date cut;
+ protected final Date cut;
- BeforePredicate(String value) throws QueryParseException {
+ public BeforePredicate(String value) throws QueryParseException {
super(ChangeField.UPDATED, ChangeQueryBuilder.FIELD_BEFORE, value);
cut = parse(value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
index ad43c5f..31e3ee1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
@@ -18,10 +18,10 @@
import com.google.gerrit.server.index.FieldDef.FillArgs;
import com.google.gwtorm.server.OrmException;
-class BooleanPredicate extends ChangeIndexPredicate {
- private final FillArgs args;
+public class BooleanPredicate extends ChangeIndexPredicate {
+ protected final FillArgs args;
- BooleanPredicate(FieldDef<ChangeData, String> field, FillArgs args) {
+ public BooleanPredicate(FieldDef<ChangeData, String> field, FillArgs args) {
super(field, "1");
this.args = args;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
index 85d433a..d541d18 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
@@ -19,8 +19,8 @@
import com.google.gwtorm.server.OrmException;
/** Predicate over Change-Id strings (aka Change.Key). */
-class ChangeIdPredicate extends ChangeIndexPredicate {
- ChangeIdPredicate(String id) {
+public class ChangeIdPredicate extends ChangeIndexPredicate {
+ public ChangeIdPredicate(String id) {
super(ChangeField.ID, ChangeQueryBuilder.FIELD_CHANGE, id);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index 8db62a7..632ec04 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -24,13 +24,13 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
-class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData> {
- private final Provider<ReviewDb> db;
- private final ChangeNotes.Factory notesFactory;
- private final ChangeControl.GenericFactory changeControl;
- private final CurrentUser user;
+public class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData> {
+ protected final Provider<ReviewDb> db;
+ protected final ChangeNotes.Factory notesFactory;
+ protected final ChangeControl.GenericFactory changeControl;
+ protected final CurrentUser user;
- ChangeIsVisibleToPredicate(
+ public ChangeIsVisibleToPredicate(
Provider<ReviewDb> db,
ChangeNotes.Factory notesFactory,
ChangeControl.GenericFactory changeControlFactory,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index 91a37d5..efe44fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -17,6 +17,8 @@
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_LIMIT;
+import com.google.gerrit.extensions.common.PluginDefinedInfo;
+import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.index.IndexConfig;
@@ -32,12 +34,26 @@
import com.google.gerrit.server.query.QueryProcessor;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
-public class ChangeQueryProcessor extends QueryProcessor<ChangeData> {
+public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
+ implements PluginDefinedAttributesFactory {
+ /**
+ * Register a ChangeAttributeFactory in a config Module like this:
+ *
+ * <p>bind(ChangeAttributeFactory.class) .annotatedWith(Exports.named("export-name"))
+ * .to(YourClass.class);
+ */
+ public interface ChangeAttributeFactory {
+ PluginDefinedInfo create(ChangeData a, ChangeQueryProcessor qp, String plugin);
+ }
+
private final Provider<ReviewDb> db;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeNotes.Factory notesFactory;
+ private final DynamicMap<ChangeAttributeFactory> attributeFactories;
static {
// It is assumed that basic rewrites do not touch visibleto predicates.
@@ -55,7 +71,8 @@
ChangeIndexRewriter rewriter,
Provider<ReviewDb> db,
ChangeControl.GenericFactory changeControlFactory,
- ChangeNotes.Factory notesFactory) {
+ ChangeNotes.Factory notesFactory,
+ DynamicMap<ChangeAttributeFactory> attributeFactories) {
super(
userProvider,
metrics,
@@ -67,6 +84,7 @@
this.db = db;
this.changeControlFactory = changeControlFactory;
this.notesFactory = notesFactory;
+ this.attributeFactories = attributeFactories;
}
@Override
@@ -82,6 +100,30 @@
}
@Override
+ public List<PluginDefinedInfo> create(ChangeData cd) {
+ List<PluginDefinedInfo> plugins = new ArrayList<>(attributeFactories.plugins().size());
+ for (String plugin : attributeFactories.plugins()) {
+ for (Provider<ChangeAttributeFactory> provider :
+ attributeFactories.byPlugin(plugin).values()) {
+ PluginDefinedInfo pda = null;
+ try {
+ pda = provider.get().create(cd, this, plugin);
+ } catch (RuntimeException e) {
+ /* Eat runtime exceptions so that queries don't fail. */
+ }
+ if (pda != null) {
+ pda.name = plugin;
+ plugins.add(pda);
+ }
+ }
+ }
+ if (plugins.isEmpty()) {
+ plugins = null;
+ }
+ return plugins;
+ }
+
+ @Override
protected Predicate<ChangeData> enforceVisibility(Predicate<ChangeData> pred) {
return new AndChangeSource(
pred,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index 9c16777..562608e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -36,9 +36,9 @@
* <p>Status names are looked up by prefix case-insensitively.
*/
public final class ChangeStatusPredicate extends ChangeIndexPredicate {
- private static final TreeMap<String, Predicate<ChangeData>> PREDICATES;
- private static final Predicate<ChangeData> CLOSED;
- private static final Predicate<ChangeData> OPEN;
+ protected static final TreeMap<String, Predicate<ChangeData>> PREDICATES;
+ protected static final Predicate<ChangeData> CLOSED;
+ protected static final Predicate<ChangeData> OPEN;
static {
PREDICATES = new TreeMap<>();
@@ -84,9 +84,9 @@
return CLOSED;
}
- private final Change.Status status;
+ protected final Change.Status status;
- ChangeStatusPredicate(Change.Status status) {
+ public ChangeStatusPredicate(Change.Status status) {
super(ChangeField.STATUS, canonicalize(status));
this.status = status;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentByPredicate.java
index 668c6f2..7ad7afe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentByPredicate.java
@@ -21,10 +21,10 @@
import com.google.gwtorm.server.OrmException;
import java.util.Objects;
-class CommentByPredicate extends ChangeIndexPredicate {
- private final Account.Id id;
+public class CommentByPredicate extends ChangeIndexPredicate {
+ protected final Account.Id id;
- CommentByPredicate(Account.Id id) {
+ public CommentByPredicate(Account.Id id) {
super(ChangeField.COMMENTBY, id.toString());
this.id = id;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
index 4779a16..85efe90 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
@@ -21,10 +21,10 @@
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
-class CommentPredicate extends ChangeIndexPredicate {
- private final ChangeIndex index;
+public class CommentPredicate extends ChangeIndexPredicate {
+ protected final ChangeIndex index;
- CommentPredicate(ChangeIndex index, String value) {
+ public CommentPredicate(ChangeIndex index, String value) {
super(ChangeField.COMMENT, value);
this.index = index;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index 1188d5d..3fac217 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -22,7 +22,7 @@
import com.google.gerrit.server.index.FieldDef;
import com.google.gwtorm.server.OrmException;
-class CommitPredicate extends ChangeIndexPredicate {
+public class CommitPredicate extends ChangeIndexPredicate {
static FieldDef<ChangeData, ?> commitField(String id) {
if (id.length() == OBJECT_ID_STRING_LENGTH) {
return EXACT_COMMIT;
@@ -30,7 +30,7 @@
return COMMIT;
}
- CommitPredicate(String id) {
+ public CommitPredicate(String id) {
super(commitField(id), id);
}
@@ -45,7 +45,7 @@
return false;
}
- private boolean equals(PatchSet p, String id) {
+ protected boolean equals(PatchSet p, String id) {
boolean exact = getField() == EXACT_COMMIT;
String rev = p.getRevision() != null ? p.getRevision().get() : null;
return (exact && id.equals(rev)) || (!exact && rev != null && rev.startsWith(id));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitterPredicate.java
index cd1f3b2..797cb9d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitterPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitterPredicate.java
@@ -22,7 +22,7 @@
import java.io.IOException;
public class CommitterPredicate extends ChangeIndexPredicate {
- CommitterPredicate(String value) {
+ public CommitterPredicate(String value) {
super(COMMITTER, FIELD_COMMITTER, value.toLowerCase());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index 9b45890..4d8c6a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -45,19 +45,19 @@
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
-class ConflictsPredicate extends OrPredicate<ChangeData> {
+public class ConflictsPredicate extends OrPredicate<ChangeData> {
// UI code may depend on this string, so use caution when changing.
- private static final String TOO_MANY_FILES = "too many files to find conflicts";
+ protected static final String TOO_MANY_FILES = "too many files to find conflicts";
- private final String value;
+ protected final String value;
- ConflictsPredicate(Arguments args, String value, List<Change> changes)
+ public ConflictsPredicate(Arguments args, String value, List<Change> changes)
throws QueryParseException, OrmException {
super(predicates(args, value, changes));
this.value = value;
}
- private static List<Predicate<ChangeData>> predicates(
+ public static List<Predicate<ChangeData>> predicates(
final Arguments args, String value, List<Change> changes)
throws QueryParseException, OrmException {
int indexTerms = 0;
@@ -160,7 +160,7 @@
return changePredicates;
}
- private static List<String> listFiles(Change c, Arguments args, ChangeDataCache changeDataCache)
+ public static List<String> listFiles(Change c, Arguments args, ChangeDataCache changeDataCache)
throws OrmException {
try (Repository repo = args.repoManager.openRepository(c.getProject());
RevWalk rw = new RevWalk(repo)) {
@@ -200,17 +200,17 @@
return ChangeQueryBuilder.FIELD_CONFLICTS + ":" + value;
}
- private static class ChangeDataCache {
- private final Change change;
- private final Provider<ReviewDb> db;
- private final ChangeData.Factory changeDataFactory;
- private final ProjectCache projectCache;
+ public static class ChangeDataCache {
+ protected final Change change;
+ protected final Provider<ReviewDb> db;
+ protected final ChangeData.Factory changeDataFactory;
+ protected final ProjectCache projectCache;
- private ObjectId testAgainst;
- private ProjectState projectState;
- private Iterable<ObjectId> alreadyAccepted;
+ protected ObjectId testAgainst;
+ protected ProjectState projectState;
+ protected Iterable<ObjectId> alreadyAccepted;
- ChangeDataCache(
+ public ChangeDataCache(
Change change,
Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
@@ -221,7 +221,7 @@
this.projectCache = projectCache;
}
- ObjectId getTestAgainst() throws OrmException {
+ protected ObjectId getTestAgainst() throws OrmException {
if (testAgainst == null) {
testAgainst =
ObjectId.fromString(
@@ -230,7 +230,7 @@
return testAgainst;
}
- ProjectState getProjectState() {
+ protected ProjectState getProjectState() {
if (projectState == null) {
projectState = projectCache.get(change.getProject());
if (projectState == null) {
@@ -240,7 +240,7 @@
return projectState;
}
- Iterable<ObjectId> getAlreadyAccepted(Repository repo) throws IOException {
+ protected Iterable<ObjectId> getAlreadyAccepted(Repository repo) throws IOException {
if (alreadyAccepted == null) {
alreadyAccepted = SubmitDryRun.getAlreadyAccepted(repo);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
index 9e49269..9c46da8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
@@ -19,7 +19,7 @@
import com.google.gwtorm.server.OrmException;
public class DeletedPredicate extends IntegerRangeChangePredicate {
- DeletedPredicate(String value) throws QueryParseException {
+ public DeletedPredicate(String value) throws QueryParseException {
super(ChangeField.DELETED, value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
index ce33225..68a4b84 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
@@ -19,7 +19,7 @@
import com.google.gwtorm.server.OrmException;
public class DeltaPredicate extends IntegerRangeChangePredicate {
- DeltaPredicate(String value) throws QueryParseException {
+ public DeltaPredicate(String value) throws QueryParseException {
super(ChangeField.DELTA, value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DestinationPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DestinationPredicate.java
index 809e7a1..4e8d30d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DestinationPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DestinationPredicate.java
@@ -19,10 +19,10 @@
import com.google.gwtorm.server.OrmException;
import java.util.Set;
-class DestinationPredicate extends ChangeOperatorPredicate {
- Set<Branch.NameKey> destinations;
+public class DestinationPredicate extends ChangeOperatorPredicate {
+ protected Set<Branch.NameKey> destinations;
- DestinationPredicate(Set<Branch.NameKey> destinations, String value) {
+ public DestinationPredicate(Set<Branch.NameKey> destinations, String value) {
super(ChangeQueryBuilder.FIELD_DESTINATION, value);
this.destinations = destinations;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EditByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EditByPredicate.java
index 8be5235..3238dc9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EditByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EditByPredicate.java
@@ -18,10 +18,10 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class EditByPredicate extends ChangeIndexPredicate {
- private final Account.Id id;
+public class EditByPredicate extends ChangeIndexPredicate {
+ protected final Account.Id id;
- EditByPredicate(Account.Id id) {
+ public EditByPredicate(Account.Id id) {
super(ChangeField.EDITBY, id.toString());
this.id = id;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
index fb6c56b..66958695 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
@@ -19,8 +19,8 @@
import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
import com.google.gwtorm.server.OrmException;
-class EqualsFilePredicate extends ChangeIndexPredicate {
- static Predicate<ChangeData> create(Arguments args, String value) {
+public class EqualsFilePredicate extends ChangeIndexPredicate {
+ public static Predicate<ChangeData> create(Arguments args, String value) {
Predicate<ChangeData> eqPath = new EqualsPathPredicate(ChangeQueryBuilder.FIELD_FILE, value);
if (!args.getSchema().hasField(ChangeField.FILE_PART)) {
return eqPath;
@@ -28,11 +28,8 @@
return Predicate.or(eqPath, new EqualsFilePredicate(value));
}
- private final String value;
-
private EqualsFilePredicate(String value) {
super(ChangeField.FILE_PART, ChangeQueryBuilder.FIELD_FILE, value);
- this.value = value;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index a5814fe..1917d6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -31,17 +31,18 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
-class EqualsLabelPredicate extends ChangeIndexPredicate {
- private final ProjectCache projectCache;
- private final PermissionBackend permissionBackend;
- private final IdentifiedUser.GenericFactory userFactory;
- private final Provider<ReviewDb> dbProvider;
- private final String label;
- private final int expVal;
- private final Account.Id account;
- private final AccountGroup.UUID group;
+public class EqualsLabelPredicate extends ChangeIndexPredicate {
+ protected final ProjectCache projectCache;
+ protected final PermissionBackend permissionBackend;
+ protected final IdentifiedUser.GenericFactory userFactory;
+ protected final Provider<ReviewDb> dbProvider;
+ protected final String label;
+ protected final int expVal;
+ protected final Account.Id account;
+ protected final AccountGroup.UUID group;
- EqualsLabelPredicate(LabelPredicate.Args args, String label, int expVal, Account.Id account) {
+ public EqualsLabelPredicate(
+ LabelPredicate.Args args, String label, int expVal, Account.Id account) {
super(ChangeField.LABEL, ChangeField.formatLabel(label, expVal, account));
this.permissionBackend = args.permissionBackend;
this.projectCache = args.projectCache;
@@ -91,7 +92,7 @@
return false;
}
- private static LabelType type(LabelTypes types, String toFind) {
+ protected static LabelType type(LabelTypes types, String toFind) {
if (types.byLabel(toFind) != null) {
return types.byLabel(toFind);
}
@@ -104,7 +105,7 @@
return null;
}
- private boolean match(ChangeData cd, short value, Account.Id approver, LabelType type) {
+ protected boolean match(ChangeData cd, short value, Account.Id approver, LabelType type) {
if (value != expVal) {
return false;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsPathPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsPathPredicate.java
index 9d841f3..56ed797 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsPathPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsPathPredicate.java
@@ -19,12 +19,9 @@
import java.util.Collections;
import java.util.List;
-class EqualsPathPredicate extends ChangeIndexPredicate {
- private final String value;
-
- EqualsPathPredicate(String fieldName, String value) {
+public class EqualsPathPredicate extends ChangeIndexPredicate {
+ public EqualsPathPredicate(String fieldName, String value) {
super(ChangeField.PATH, fieldName, value);
- this.value = value;
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java
index 510910e..dc85ece 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ExactTopicPredicate.java
@@ -19,8 +19,8 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwtorm.server.OrmException;
-class ExactTopicPredicate extends ChangeIndexPredicate {
- ExactTopicPredicate(String topic) {
+public class ExactTopicPredicate extends ChangeIndexPredicate {
+ public ExactTopicPredicate(String topic) {
super(EXACT_TOPIC, topic);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
index 5651544..5f3b621 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
@@ -24,10 +24,10 @@
import com.google.gerrit.server.query.QueryParseException;
import com.google.gwtorm.server.OrmException;
-class FuzzyTopicPredicate extends ChangeIndexPredicate {
- private final ChangeIndex index;
+public class FuzzyTopicPredicate extends ChangeIndexPredicate {
+ protected final ChangeIndex index;
- FuzzyTopicPredicate(String topic, ChangeIndex index) {
+ public FuzzyTopicPredicate(String topic, ChangeIndex index) {
super(FUZZY_TOPIC, topic);
this.index = index;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/GroupPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/GroupPredicate.java
index 54e1c97..d2645dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/GroupPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/GroupPredicate.java
@@ -19,8 +19,8 @@
import com.google.gwtorm.server.OrmException;
import java.util.List;
-class GroupPredicate extends ChangeIndexPredicate {
- GroupPredicate(String group) {
+public class GroupPredicate extends ChangeIndexPredicate {
+ public GroupPredicate(String group) {
super(ChangeField.GROUP, group);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
index 244589c..e422b74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
@@ -18,10 +18,10 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class HasDraftByPredicate extends ChangeIndexPredicate {
- private final Account.Id accountId;
+public class HasDraftByPredicate extends ChangeIndexPredicate {
+ protected final Account.Id accountId;
- HasDraftByPredicate(Account.Id accountId) {
+ public HasDraftByPredicate(Account.Id accountId) {
super(ChangeField.DRAFTBY, accountId.toString());
this.accountId = accountId;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasStarsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
index eb3a137..b17fffd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasStarsPredicate.java
@@ -19,9 +19,9 @@
import com.google.gwtorm.server.OrmException;
public class HasStarsPredicate extends ChangeIndexPredicate {
- private final Account.Id accountId;
+ protected final Account.Id accountId;
- HasStarsPredicate(Account.Id accountId) {
+ public HasStarsPredicate(Account.Id accountId) {
super(ChangeField.STARBY, accountId.toString());
this.accountId = accountId;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java
index 4fd4156..a348d48 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HashtagPredicate.java
@@ -18,8 +18,8 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class HashtagPredicate extends ChangeIndexPredicate {
- HashtagPredicate(String hashtag) {
+public class HashtagPredicate extends ChangeIndexPredicate {
+ public HashtagPredicate(String hashtag) {
super(ChangeField.HASHTAG, HashtagsUtil.cleanupHashtag(hashtag));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergePredicate.java
index 50e5bd9..28fb7cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsMergePredicate.java
@@ -24,7 +24,7 @@
import org.eclipse.jgit.revwalk.RevWalk;
public class IsMergePredicate extends ChangeOperatorPredicate {
- private final Arguments args;
+ protected final Arguments args;
public IsMergePredicate(Arguments args, String value) {
super(ChangeQueryBuilder.FIELD_MERGE, value);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
index 92de09a..8b6c8e6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -25,14 +25,14 @@
import java.util.List;
import java.util.Set;
-class IsReviewedPredicate extends ChangeIndexPredicate {
- private static final Account.Id NOT_REVIEWED = new Account.Id(ChangeField.NOT_REVIEWED);
+public class IsReviewedPredicate extends ChangeIndexPredicate {
+ protected static final Account.Id NOT_REVIEWED = new Account.Id(ChangeField.NOT_REVIEWED);
- static Predicate<ChangeData> create() {
+ public static Predicate<ChangeData> create() {
return Predicate.not(new IsReviewedPredicate(NOT_REVIEWED));
}
- static Predicate<ChangeData> create(Collection<Account.Id> ids) {
+ public static Predicate<ChangeData> create(Collection<Account.Id> ids) {
List<Predicate<ChangeData>> predicates = new ArrayList<>(ids.size());
for (Account.Id id : ids) {
predicates.add(new IsReviewedPredicate(id));
@@ -40,7 +40,7 @@
return Predicate.or(predicates);
}
- private final Account.Id id;
+ protected final Account.Id id;
private IsReviewedPredicate(Account.Id id) {
super(REVIEWEDBY, Integer.toString(id.get()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
index 17a6347..e9b2899 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
@@ -19,11 +19,11 @@
import com.google.gwtorm.server.OrmException;
public class IsUnresolvedPredicate extends IntegerRangeChangePredicate {
- IsUnresolvedPredicate() throws QueryParseException {
+ public IsUnresolvedPredicate() throws QueryParseException {
this(">0");
}
- IsUnresolvedPredicate(String value) throws QueryParseException {
+ public IsUnresolvedPredicate(String value) throws QueryParseException {
super(ChangeField.UNRESOLVED_COMMENT_COUNT, value);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index dda834b..a1a5070 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -26,23 +26,23 @@
import java.util.Collections;
import java.util.List;
-class IsWatchedByPredicate extends AndPredicate<ChangeData> {
- private static String describe(CurrentUser user) {
+public class IsWatchedByPredicate extends AndPredicate<ChangeData> {
+ protected static String describe(CurrentUser user) {
if (user.isIdentifiedUser()) {
return user.getAccountId().toString();
}
return user.toString();
}
- private final CurrentUser user;
+ protected final CurrentUser user;
- IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, boolean checkIsVisible)
+ public IsWatchedByPredicate(ChangeQueryBuilder.Arguments args, boolean checkIsVisible)
throws QueryParseException {
super(filters(args, checkIsVisible));
this.user = args.getUser();
}
- private static List<Predicate<ChangeData>> filters(
+ protected static List<Predicate<ChangeData>> filters(
ChangeQueryBuilder.Arguments args, boolean checkIsVisible) throws QueryParseException {
List<Predicate<ChangeData>> r = new ArrayList<>();
ChangeQueryBuilder builder = new ChangeQueryBuilder(args);
@@ -89,7 +89,7 @@
}
}
- private static Collection<ProjectWatchKey> getWatches(ChangeQueryBuilder.Arguments args)
+ protected static Collection<ProjectWatchKey> getWatches(ChangeQueryBuilder.Arguments args)
throws QueryParseException {
CurrentUser user = args.getUser();
if (user.isIdentifiedUser()) {
@@ -98,7 +98,7 @@
return Collections.<ProjectWatchKey>emptySet();
}
- private static List<Predicate<ChangeData>> none() {
+ protected static List<Predicate<ChangeData>> none() {
Predicate<ChangeData> any = any();
return ImmutableList.of(not(any));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 3fe6a6f..bd342d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -33,19 +33,19 @@
import java.util.Set;
public class LabelPredicate extends OrPredicate<ChangeData> {
- private static final int MAX_LABEL_VALUE = 4;
+ protected static final int MAX_LABEL_VALUE = 4;
- static class Args {
- final ProjectCache projectCache;
- final PermissionBackend permissionBackend;
- final ChangeControl.GenericFactory ccFactory;
- final IdentifiedUser.GenericFactory userFactory;
- final Provider<ReviewDb> dbProvider;
- final String value;
- final Set<Account.Id> accounts;
- final AccountGroup.UUID group;
+ protected static class Args {
+ protected final ProjectCache projectCache;
+ protected final PermissionBackend permissionBackend;
+ protected final ChangeControl.GenericFactory ccFactory;
+ protected final IdentifiedUser.GenericFactory userFactory;
+ protected final Provider<ReviewDb> dbProvider;
+ protected final String value;
+ protected final Set<Account.Id> accounts;
+ protected final AccountGroup.UUID group;
- private Args(
+ protected Args(
ProjectCache projectCache,
PermissionBackend permissionBackend,
ChangeControl.GenericFactory ccFactory,
@@ -65,21 +65,21 @@
}
}
- private static class Parsed {
- private final String label;
- private final String test;
- private final int expVal;
+ protected static class Parsed {
+ protected final String label;
+ protected final String test;
+ protected final int expVal;
- private Parsed(String label, String test, int expVal) {
+ protected Parsed(String label, String test, int expVal) {
this.label = label;
this.test = test;
this.expVal = expVal;
}
}
- private final String value;
+ protected final String value;
- LabelPredicate(
+ public LabelPredicate(
ChangeQueryBuilder.Arguments a,
String value,
Set<Account.Id> accounts,
@@ -98,7 +98,7 @@
this.value = value;
}
- private static List<Predicate<ChangeData>> predicates(Args args) {
+ protected static List<Predicate<ChangeData>> predicates(Args args) {
String v = args.value;
Parsed parsed = null;
@@ -138,14 +138,14 @@
return r;
}
- private static Predicate<ChangeData> onePredicate(Args args, String label, int expVal) {
+ protected static Predicate<ChangeData> onePredicate(Args args, String label, int expVal) {
if (expVal != 0) {
return equalsLabelPredicate(args, label, expVal);
}
return noLabelQuery(args, label);
}
- private static Predicate<ChangeData> noLabelQuery(Args args, String label) {
+ protected static Predicate<ChangeData> noLabelQuery(Args args, String label) {
List<Predicate<ChangeData>> r = Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
for (int i = 1; i <= MAX_LABEL_VALUE; i++) {
r.add(equalsLabelPredicate(args, label, i));
@@ -154,7 +154,7 @@
return not(or(r));
}
- private static Predicate<ChangeData> equalsLabelPredicate(Args args, String label, int expVal) {
+ protected static Predicate<ChangeData> equalsLabelPredicate(Args args, String label, int expVal) {
if (args.accounts == null || args.accounts.isEmpty()) {
return new EqualsLabelPredicate(args, label, expVal, null);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
index f7f98d5..7cc8b31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LegacyChangeIdPredicate.java
@@ -20,7 +20,7 @@
/** Predicate over change number (aka legacy ID or Change.Id). */
public class LegacyChangeIdPredicate extends ChangeIndexPredicate {
- private final Change.Id id;
+ protected final Change.Id id;
public LegacyChangeIdPredicate(Change.Id id) {
super(LEGACY_ID, ChangeQueryBuilder.FIELD_CHANGE, id.toString());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 9e525c2..92d1ed3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -22,10 +22,10 @@
import com.google.gwtorm.server.OrmException;
/** Predicate to match changes that contains specified text in commit messages body. */
-class MessagePredicate extends ChangeIndexPredicate {
- private final ChangeIndex index;
+public class MessagePredicate extends ChangeIndexPredicate {
+ protected final ChangeIndex index;
- MessagePredicate(ChangeIndex index, String value) {
+ public MessagePredicate(ChangeIndex index, String value) {
super(ChangeField.COMMIT_MESSAGE, value);
this.index = index;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index cd98087..0d12132 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -313,6 +313,7 @@
eventFactory.addDependencies(rw, c, d.change(), d.currentPatchSet());
}
+ c.plugins = queryProcessor.create(d);
return c;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
index dfaac08..5fd1ca0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
@@ -19,15 +19,15 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class OwnerPredicate extends ChangeIndexPredicate {
- private final Account.Id id;
+public class OwnerPredicate extends ChangeIndexPredicate {
+ protected final Account.Id id;
- OwnerPredicate(Account.Id id) {
+ public OwnerPredicate(Account.Id id) {
super(ChangeField.OWNER, id.toString());
this.id = id;
}
- Account.Id getAccountId() {
+ protected Account.Id getAccountId() {
return id;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
index f3239af..f828970 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerinPredicate.java
@@ -19,17 +19,17 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.server.OrmException;
-class OwnerinPredicate extends ChangeOperatorPredicate {
- private final IdentifiedUser.GenericFactory userFactory;
- private final AccountGroup.UUID uuid;
+public class OwnerinPredicate extends ChangeOperatorPredicate {
+ protected final IdentifiedUser.GenericFactory userFactory;
+ protected final AccountGroup.UUID uuid;
- OwnerinPredicate(IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
+ public OwnerinPredicate(IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
super(ChangeQueryBuilder.FIELD_OWNERIN, uuid.toString());
this.userFactory = userFactory;
this.uuid = uuid;
}
- AccountGroup.UUID getAccountGroupUUID() {
+ protected AccountGroup.UUID getAccountGroupUUID() {
return uuid;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index d3a3f20..64a2fa6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -28,10 +28,10 @@
import java.util.Collections;
import java.util.List;
-class ParentProjectPredicate extends OrPredicate<ChangeData> {
- private final String value;
+public class ParentProjectPredicate extends OrPredicate<ChangeData> {
+ protected final String value;
- ParentProjectPredicate(
+ public ParentProjectPredicate(
ProjectCache projectCache,
Provider<ListChildProjects> listChildProjects,
Provider<CurrentUser> self,
@@ -40,7 +40,7 @@
this.value = value;
}
- private static List<Predicate<ChangeData>> predicates(
+ protected static List<Predicate<ChangeData>> predicates(
ProjectCache projectCache,
Provider<ListChildProjects> listChildProjects,
Provider<CurrentUser> self,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PluginDefinedAttributesFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PluginDefinedAttributesFactory.java
new file mode 100644
index 0000000..a795025
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PluginDefinedAttributesFactory.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.extensions.common.PluginDefinedInfo;
+import java.util.List;
+
+public interface PluginDefinedAttributesFactory {
+ List<PluginDefinedInfo> create(ChangeData cd);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
index 644870d..ef25ddb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPredicate.java
@@ -19,12 +19,12 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class ProjectPredicate extends ChangeIndexPredicate {
- ProjectPredicate(String id) {
+public class ProjectPredicate extends ChangeIndexPredicate {
+ public ProjectPredicate(String id) {
super(ChangeField.PROJECT, id);
}
- Project.NameKey getValueKey() {
+ protected Project.NameKey getValueKey() {
return new Project.NameKey(getValue());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
index 4c06d1b..28b1302 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
@@ -18,8 +18,8 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class ProjectPrefixPredicate extends ChangeIndexPredicate {
- ProjectPrefixPredicate(String prefix) {
+public class ProjectPrefixPredicate extends ChangeIndexPredicate {
+ public ProjectPrefixPredicate(String prefix) {
super(ChangeField.PROJECTS, prefix);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 7eccf45..f0ef40d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -137,13 +137,18 @@
int cnt = queries.size();
List<QueryResult<ChangeData>> results = imp.query(qb.parse(queries));
+
boolean requireLazyLoad =
containsAnyOf(options, ImmutableSet.of(DETAILED_LABELS, LABELS))
&& !qb.getArgs().getSchema().hasField(ChangeField.STORED_SUBMIT_RECORD_LENIENT);
+
+ ChangeJson cjson = json.create(options);
+ cjson.setPluginDefinedAttributesFactory(this.imp);
List<List<ChangeInfo>> res =
- json.create(options)
+ cjson
.lazyLoad(requireLazyLoad || containsAnyOf(options, ChangeJson.REQUIRE_LAZY_LOAD))
.formatQueryResults(results);
+
for (int n = 0; n < cnt; n++) {
List<ChangeInfo> info = res.get(n);
if (results.get(n).more()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
index 491aed9..b8bece9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
@@ -18,8 +18,8 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class RefPredicate extends ChangeIndexPredicate {
- RefPredicate(String ref) {
+public class RefPredicate extends ChangeIndexPredicate {
+ public RefPredicate(String ref) {
super(ChangeField.REF, ref);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
index 5b9774c..ca21247 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
@@ -19,8 +19,8 @@
import com.google.gwtorm.server.OrmException;
import java.util.List;
-class RegexPathPredicate extends ChangeRegexPredicate {
- RegexPathPredicate(String re) {
+public class RegexPathPredicate extends ChangeRegexPredicate {
+ public RegexPathPredicate(String re) {
super(ChangeField.PATH, re);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
index 1284e88..cf78c57 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexProjectPredicate.java
@@ -21,10 +21,10 @@
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
-class RegexProjectPredicate extends ChangeRegexPredicate {
- private final RunAutomaton pattern;
+public class RegexProjectPredicate extends ChangeRegexPredicate {
+ protected final RunAutomaton pattern;
- RegexProjectPredicate(String re) {
+ public RegexProjectPredicate(String re) {
super(ChangeField.PROJECT, re);
if (re.startsWith("^")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
index 671d4cc..ac7af9b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexRefPredicate.java
@@ -20,10 +20,10 @@
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
-class RegexRefPredicate extends ChangeRegexPredicate {
- private final RunAutomaton pattern;
+public class RegexRefPredicate extends ChangeRegexPredicate {
+ protected final RunAutomaton pattern;
- RegexRefPredicate(String re) {
+ public RegexRefPredicate(String re) {
super(ChangeField.REF, re);
if (re.startsWith("^")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
index a4ba059..8a9f8cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexTopicPredicate.java
@@ -21,10 +21,10 @@
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
-class RegexTopicPredicate extends ChangeRegexPredicate {
- private final RunAutomaton pattern;
+public class RegexTopicPredicate extends ChangeRegexPredicate {
+ protected final RunAutomaton pattern;
- RegexTopicPredicate(String re) {
+ public RegexTopicPredicate(String re) {
super(EXACT_TOPIC, re);
if (re.startsWith("^")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index 5b86494..f3a8619 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -25,14 +25,14 @@
import com.google.gwtorm.server.OrmException;
import java.util.stream.Stream;
-class ReviewerPredicate extends ChangeIndexPredicate {
- static Predicate<ChangeData> forState(
+public class ReviewerPredicate extends ChangeIndexPredicate {
+ protected static Predicate<ChangeData> forState(
Arguments args, Account.Id id, ReviewerStateInternal state) {
checkArgument(state != ReviewerStateInternal.REMOVED, "can't query by removed reviewer");
return create(args, new ReviewerPredicate(state, id));
}
- static Predicate<ChangeData> reviewer(Arguments args, Account.Id id) {
+ protected static Predicate<ChangeData> reviewer(Arguments args, Account.Id id) {
Predicate<ChangeData> p;
if (args.notesMigration.readChanges()) {
// With NoteDb, Reviewer/CC are clearly distinct states, so only choose reviewer.
@@ -45,14 +45,14 @@
return create(args, p);
}
- static Predicate<ChangeData> cc(Arguments args, Account.Id id) {
+ protected static Predicate<ChangeData> cc(Arguments args, Account.Id id) {
// As noted above, CC is nebulous without NoteDb, but it certainly doesn't make sense to return
// Reviewers for cc:foo. Most likely this will just not match anything, but let the index sort
// it out.
return create(args, new ReviewerPredicate(ReviewerStateInternal.CC, id));
}
- private static Predicate<ChangeData> anyReviewerState(Account.Id id) {
+ protected static Predicate<ChangeData> anyReviewerState(Account.Id id) {
return Predicate.or(
Stream.of(ReviewerStateInternal.values())
.filter(s -> s != ReviewerStateInternal.REMOVED)
@@ -60,8 +60,8 @@
.collect(toList()));
}
- private final ReviewerStateInternal state;
- private final Account.Id id;
+ protected final ReviewerStateInternal state;
+ protected final Account.Id id;
private ReviewerPredicate(ReviewerStateInternal state, Account.Id id) {
super(ChangeField.REVIEWER, ChangeField.getReviewerFieldValue(state, id));
@@ -69,7 +69,7 @@
this.id = id;
}
- Account.Id getAccountId() {
+ protected Account.Id getAccountId() {
return id;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
index 63e7859..df28de3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerinPredicate.java
@@ -19,17 +19,17 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.server.OrmException;
-class ReviewerinPredicate extends ChangeOperatorPredicate {
- private final IdentifiedUser.GenericFactory userFactory;
- private final AccountGroup.UUID uuid;
+public class ReviewerinPredicate extends ChangeOperatorPredicate {
+ protected final IdentifiedUser.GenericFactory userFactory;
+ protected final AccountGroup.UUID uuid;
- ReviewerinPredicate(IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
+ public ReviewerinPredicate(IdentifiedUser.GenericFactory userFactory, AccountGroup.UUID uuid) {
super(ChangeQueryBuilder.FIELD_REVIEWERIN, uuid.toString());
this.userFactory = userFactory;
this.uuid = uuid;
}
- AccountGroup.UUID getAccountGroupUUID() {
+ protected AccountGroup.UUID getAccountGroupUUID() {
return uuid;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/StarPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/StarPredicate.java
index 98965bf..12d4753 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/StarPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/StarPredicate.java
@@ -20,10 +20,10 @@
import com.google.gwtorm.server.OrmException;
public class StarPredicate extends ChangeIndexPredicate {
- private final Account.Id accountId;
- private final String label;
+ protected final Account.Id accountId;
+ protected final String label;
- StarPredicate(Account.Id accountId, String label) {
+ public StarPredicate(Account.Id accountId, String label) {
super(ChangeField.STAR, StarredChangesUtil.StarField.create(accountId, label).toString());
this.accountId = accountId;
this.label = label;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
index d8d5258..5fdeb68 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmissionIdPredicate.java
@@ -18,9 +18,8 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class SubmissionIdPredicate extends ChangeIndexPredicate {
-
- SubmissionIdPredicate(String changeSet) {
+public class SubmissionIdPredicate extends ChangeIndexPredicate {
+ public SubmissionIdPredicate(String changeSet) {
super(ChangeField.SUBMISSIONID, changeSet);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
index 5b01ea2..81d64e0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
@@ -23,8 +23,8 @@
import com.google.gwtorm.server.OrmException;
import java.util.Set;
-class SubmitRecordPredicate extends ChangeIndexPredicate {
- static Predicate<ChangeData> create(
+public class SubmitRecordPredicate extends ChangeIndexPredicate {
+ public static Predicate<ChangeData> create(
String label, SubmitRecord.Label.Status status, Set<Account.Id> accounts) {
String lowerLabel = label.toLowerCase();
if (accounts == null || accounts.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmittablePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmittablePredicate.java
index 0812c6a..df78315 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmittablePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmittablePredicate.java
@@ -18,10 +18,10 @@
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gwtorm.server.OrmException;
-class SubmittablePredicate extends ChangeIndexPredicate {
- private final SubmitRecord.Status status;
+public class SubmittablePredicate extends ChangeIndexPredicate {
+ protected final SubmitRecord.Status status;
- SubmittablePredicate(SubmitRecord.Status status) {
+ public SubmittablePredicate(SubmitRecord.Status status) {
super(ChangeField.SUBMIT_RECORD, status.name());
this.status = status;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
index afaea5c..6a5f260 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
@@ -24,12 +24,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-class TrackingIdPredicate extends ChangeIndexPredicate {
+public class TrackingIdPredicate extends ChangeIndexPredicate {
private static final Logger log = LoggerFactory.getLogger(TrackingIdPredicate.class);
- private final TrackingFooters trackingFooters;
+ protected final TrackingFooters trackingFooters;
- TrackingIdPredicate(TrackingFooters trackingFooters, String trackingId) {
+ public TrackingIdPredicate(TrackingFooters trackingFooters, String trackingId) {
super(ChangeField.TR, trackingId);
this.trackingFooters = trackingFooters;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
index 8f72945..3ac9c39 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
@@ -23,10 +23,11 @@
import com.google.gwtorm.server.OrmException;
public class GroupIsVisibleToPredicate extends IsVisibleToPredicate<AccountGroup> {
- private final GroupControl.GenericFactory groupControlFactory;
- private final CurrentUser user;
+ protected final GroupControl.GenericFactory groupControlFactory;
+ protected final CurrentUser user;
- GroupIsVisibleToPredicate(GroupControl.GenericFactory groupControlFactory, CurrentUser user) {
+ public GroupIsVisibleToPredicate(
+ GroupControl.GenericFactory groupControlFactory, CurrentUser user) {
super(AccountQueryBuilder.FIELD_VISIBLETO, describe(user));
this.groupControlFactory = groupControlFactory;
this.user = user;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index caeba3a..b88a4b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -35,7 +35,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_145> C = Schema_145.class;
+ public static final Class<Schema_148> C = Schema_148.class;
public static int getBinaryVersion() {
return guessVersion(C);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_146.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_146.java
new file mode 100644
index 0000000..dd11396
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_146.java
@@ -0,0 +1,156 @@
+// 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.schema;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Make sure that for every account a user branch exists that has an initial empty commit with the
+ * registration date as commit time.
+ *
+ * <p>For accounts that don't have a user branch yet the user branch is created with an initial
+ * empty commit that has the registration date as commit time.
+ *
+ * <p>For accounts that already have a user branch the user branch is rewritten and an initial empty
+ * commit with the registration date as commit time is inserted (if such a commit doesn't exist
+ * yet).
+ */
+public class Schema_146 extends SchemaVersion {
+ private static final String CREATE_ACCOUNT_MSG = "Create Account";
+
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsersName;
+ private final PersonIdent serverIdent;
+
+ @Inject
+ Schema_146(
+ Provider<Schema_145> prior,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName,
+ @GerritPersonIdent PersonIdent serverIdent) {
+ super(prior);
+ this.repoManager = repoManager;
+ this.allUsersName = allUsersName;
+ this.serverIdent = serverIdent;
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
+ try (Repository repo = repoManager.openRepository(allUsersName);
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter oi = repo.newObjectInserter()) {
+ ObjectId emptyTree = emptyTree(oi);
+
+ for (Account account : db.accounts().all()) {
+ String refName = RefNames.refsUsers(account.getId());
+ Ref ref = repo.exactRef(refName);
+ if (ref != null) {
+ rewriteUserBranch(repo, rw, oi, emptyTree, ref, account);
+ } else {
+ AccountsUpdate.createUserBranch(repo, oi, serverIdent, serverIdent, account);
+ }
+ }
+ } catch (IOException e) {
+ throw new OrmException("Failed to rewrite user branches.", e);
+ }
+ }
+
+ private void rewriteUserBranch(
+ Repository repo, RevWalk rw, ObjectInserter oi, ObjectId emptyTree, Ref ref, Account account)
+ throws IOException {
+ ObjectId current = createInitialEmptyCommit(oi, emptyTree, account.getRegisteredOn());
+
+ rw.reset();
+ rw.sort(RevSort.TOPO);
+ rw.sort(RevSort.REVERSE, true);
+ rw.markStart(rw.parseCommit(ref.getObjectId()));
+
+ RevCommit c;
+ while ((c = rw.next()) != null) {
+ if (isInitialEmptyCommit(emptyTree, c)) {
+ return;
+ }
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setParentId(current);
+ cb.setTreeId(c.getTree());
+ cb.setAuthor(c.getAuthorIdent());
+ cb.setCommitter(c.getCommitterIdent());
+ cb.setMessage(c.getFullMessage());
+ cb.setEncoding(c.getEncoding());
+ current = oi.insert(cb);
+ }
+
+ oi.flush();
+
+ RefUpdate ru = repo.updateRef(ref.getName());
+ ru.setExpectedOldObjectId(ref.getObjectId());
+ ru.setNewObjectId(current);
+ ru.setForceUpdate(true);
+ ru.setRefLogIdent(serverIdent);
+ ru.setRefLogMessage(getClass().getSimpleName(), true);
+ Result result = ru.update();
+ if (result != Result.FORCED) {
+ throw new IOException(
+ String.format("Failed to update ref %s: %s", ref.getName(), result.name()));
+ }
+ }
+
+ private ObjectId createInitialEmptyCommit(
+ ObjectInserter oi, ObjectId emptyTree, Timestamp registrationDate) throws IOException {
+ PersonIdent ident = new PersonIdent(serverIdent, registrationDate);
+
+ CommitBuilder cb = new CommitBuilder();
+ cb.setTreeId(emptyTree);
+ cb.setCommitter(ident);
+ cb.setAuthor(ident);
+ cb.setMessage(CREATE_ACCOUNT_MSG);
+ return oi.insert(cb);
+ }
+
+ private boolean isInitialEmptyCommit(ObjectId emptyTree, RevCommit c) {
+ return c.getParentCount() == 0
+ && c.getTree().equals(emptyTree)
+ && c.getShortMessage().equals(CREATE_ACCOUNT_MSG);
+ }
+
+ private static ObjectId emptyTree(ObjectInserter oi) throws IOException {
+ return oi.insert(Constants.OBJ_TREE, new byte[] {});
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_147.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_147.java
new file mode 100644
index 0000000..8585988
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_147.java
@@ -0,0 +1,75 @@
+// 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.schema;
+
+import static java.util.stream.Collectors.toSet;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.Objects;
+import java.util.Set;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+
+/** Delete user branches for which no account exists. */
+public class Schema_147 extends SchemaVersion {
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsersName;
+ private final PersonIdent serverIdent;
+
+ @Inject
+ Schema_147(
+ Provider<Schema_146> prior,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName,
+ @GerritPersonIdent PersonIdent serverIdent) {
+ super(prior);
+ this.repoManager = repoManager;
+ this.allUsersName = allUsersName;
+ this.serverIdent = serverIdent;
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
+ try (Repository repo = repoManager.openRepository(allUsersName)) {
+ Set<Account.Id> accountIdsFromReviewDb =
+ db.accounts().all().toList().stream().map(a -> a.getId()).collect(toSet());
+ Set<Account.Id> accountIdsFromUserBranches =
+ repo.getRefDatabase()
+ .getRefs(RefNames.REFS_USERS)
+ .values()
+ .stream()
+ .map(r -> Account.Id.fromRef(r.getName()))
+ .filter(Objects::nonNull)
+ .collect(toSet());
+ accountIdsFromUserBranches.removeAll(accountIdsFromReviewDb);
+ for (Account.Id accountId : accountIdsFromUserBranches) {
+ AccountsUpdate.deleteUserBranch(repo, serverIdent, accountId);
+ }
+ } catch (IOException e) {
+ throw new OrmException("Failed to delete user branches for non-existing accounts.", e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_148.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_148.java
new file mode 100644
index 0000000..abb3bb2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_148.java
@@ -0,0 +1,99 @@
+// 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.schema;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.google.common.primitives.Ints;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIdReader;
+import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.sql.SQLException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class Schema_148 extends SchemaVersion {
+ private static final String COMMIT_MSG = "Make account IDs of external IDs human-readable";
+
+ private final GitRepositoryManager repoManager;
+ private final AllUsersName allUsersName;
+ private final PersonIdent serverUser;
+
+ @Inject
+ Schema_148(
+ Provider<Schema_147> prior,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName,
+ @GerritPersonIdent PersonIdent serverUser) {
+ super(prior);
+ this.repoManager = repoManager;
+ this.allUsersName = allUsersName;
+ this.serverUser = serverUser;
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
+ try (Repository repo = repoManager.openRepository(allUsersName);
+ RevWalk rw = new RevWalk(repo);
+ ObjectInserter ins = repo.newObjectInserter()) {
+ ObjectId rev = ExternalIdReader.readRevision(repo);
+ NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
+ boolean dirty = false;
+ for (Note note : noteMap) {
+ byte[] raw =
+ rw.getObjectReader()
+ .open(note.getData(), OBJ_BLOB)
+ .getCachedBytes(ExternalIdReader.MAX_NOTE_SZ);
+ try {
+ ExternalId extId = ExternalId.parse(note.getName(), raw);
+
+ if (needsUpdate(extId)) {
+ ExternalIdsUpdate.upsert(rw, ins, noteMap, extId);
+ dirty = true;
+ }
+ } catch (ConfigInvalidException e) {
+ ui.message(
+ String.format("Warning: Ignoring invalid external ID note %s", note.getName()));
+ }
+ }
+ if (dirty) {
+ ExternalIdsUpdate.commit(repo, rw, ins, rev, noteMap, COMMIT_MSG, serverUser, serverUser);
+ }
+ } catch (IOException e) {
+ throw new OrmException("Failed to update external IDs", e);
+ }
+ }
+
+ private static boolean needsUpdate(ExternalId extId) {
+ Config cfg = new Config();
+ cfg.setInt("externalId", extId.key().get(), "accountId", extId.accountId().get());
+ return Ints.tryParse(cfg.getString("externalId", extId.key().get(), "accountId")) == null;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/RepoContext.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/RepoContext.java
index e635da9..9faf628 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/RepoContext.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/RepoContext.java
@@ -14,12 +14,11 @@
package com.google.gerrit.server.update;
+import java.io.IOException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.transport.ReceiveCommand;
-import java.io.IOException;
-
/** Context for performing the {@link BatchUpdateOp#updateRepo} phase. */
public interface RepoContext extends Context {
/**
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
index 1724c51..cb1d97b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
@@ -58,7 +58,7 @@
indexes = new ChangeIndexCollection();
indexes.setSearchIndex(index);
queryBuilder = new FakeQueryBuilder(indexes);
- rewrite = new ChangeIndexRewriter(indexes, IndexConfig.create(0, 0, 3));
+ rewrite = new ChangeIndexRewriter(indexes, IndexConfig.builder().maxTerms(3).build());
}
@Test
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
index 45835d9..7b7893a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
@@ -16,12 +16,16 @@
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.Atomics;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.CapabilityControl;
+import com.google.gerrit.server.permissions.GlobalOrPluginPermission;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
@@ -30,14 +34,17 @@
public class AliasCommand extends BaseCommand {
private final DispatchCommandProvider root;
private final CurrentUser currentUser;
+ private final PermissionBackend permissionBackend;
private final CommandName command;
private final AtomicReference<Command> atomicCmd;
AliasCommand(
@CommandName(Commands.ROOT) DispatchCommandProvider root,
+ PermissionBackend permissionBackend,
CurrentUser currentUser,
CommandName command) {
this.root = root;
+ this.permissionBackend = permissionBackend;
this.currentUser = currentUser;
this.command = command;
this.atomicCmd = Atomics.newReference();
@@ -47,7 +54,7 @@
public void start(Environment env) throws IOException {
try {
begin(env);
- } catch (UnloggedFailure e) {
+ } catch (Failure e) {
String msg = e.getMessage();
if (!msg.endsWith("\n")) {
msg += "\n";
@@ -58,7 +65,7 @@
}
}
- private void begin(Environment env) throws UnloggedFailure, IOException {
+ private void begin(Environment env) throws IOException, Failure {
Map<String, CommandProvider> map = root.getMap();
for (String name : chain(command)) {
CommandProvider p = map.get(name);
@@ -103,17 +110,16 @@
}
}
- private void checkRequiresCapability(Command cmd) throws UnloggedFailure {
- RequiresCapability rc = cmd.getClass().getAnnotation(RequiresCapability.class);
- if (rc != null) {
- CapabilityControl ctl = currentUser.getCapabilities();
- if (!ctl.canPerform(rc.value()) && !ctl.canAdministrateServer()) {
- String msg =
- String.format(
- "fatal: %s does not have \"%s\" capability.",
- currentUser.getUserName(), rc.value());
- throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
+ private void checkRequiresCapability(Command cmd) throws Failure {
+ try {
+ Set<GlobalOrPluginPermission> check = GlobalPermission.fromAnnotation(cmd.getClass());
+ try {
+ permissionBackend.user(currentUser).checkAny(check);
+ } catch (AuthException err) {
+ throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, "fatal: " + err.getMessage());
}
+ } catch (PermissionBackendException err) {
+ throw new Failure(1, "fatal: permissions unavailable", err);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java
index 10beb40..0ef0473 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommandProvider.java
@@ -15,6 +15,7 @@
package com.google.gerrit.sshd;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.sshd.server.Command;
@@ -27,6 +28,7 @@
@CommandName(Commands.ROOT)
private DispatchCommandProvider root;
+ @Inject private PermissionBackend permissionBackend;
@Inject private CurrentUser currentUser;
public AliasCommandProvider(CommandName command) {
@@ -35,6 +37,6 @@
@Override
public Command get() {
- return new AliasCommand(root, currentUser, command);
+ return new AliasCommand(root, permissionBackend, currentUser, command);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 8f4cfad..9971b0c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -35,6 +35,7 @@
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gerrit.util.cli.EndOfOptionsHandler;
import com.google.inject.Inject;
+import com.google.inject.Injector;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
@@ -86,13 +87,15 @@
@Inject private SshScope.Context context;
- @Inject private DynamicMap<DynamicOptions.DynamicBean> dynamicBeans;
-
/** Commands declared by a plugin can be scoped by the plugin name. */
@Inject(optional = true)
@PluginName
private String pluginName;
+ @Inject private Injector injector;
+
+ @Inject private DynamicMap<DynamicOptions.DynamicBean> dynamicBeans = null;
+
/** The task, as scheduled on a worker thread. */
private final AtomicReference<Future<?>> task;
@@ -197,7 +200,7 @@
*/
protected void parseCommandLine(Object options) throws UnloggedFailure {
final CmdLineParser clp = newCmdLineParser(options);
- DynamicOptions pluginOptions = new DynamicOptions(options, dynamicBeans);
+ DynamicOptions pluginOptions = new DynamicOptions(options, injector, dynamicBeans);
pluginOptions.parseDynamicBeans(clp);
pluginOptions.setDynamicBeans();
pluginOptions.onBeanParseStart();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index 4488c71..e64ab0e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -16,6 +16,7 @@
import static java.util.stream.Collectors.toList;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -24,6 +25,9 @@
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.ChangesCollection;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectControl;
@@ -43,6 +47,7 @@
private final ReviewDb db;
private final ChangeNotes.Factory changeNotesFactory;
private final ChangeControl.GenericFactory changeControlFactory;
+ private final PermissionBackend permissionBackend;
@Inject
ChangeArgumentParser(
@@ -51,13 +56,15 @@
ChangeFinder changeFinder,
ReviewDb db,
ChangeNotes.Factory changeNotesFactory,
- ChangeControl.GenericFactory changeControlFactory) {
+ ChangeControl.GenericFactory changeControlFactory,
+ PermissionBackend permissionBackend) {
this.currentUser = currentUser;
this.changesCollection = changesCollection;
this.changeFinder = changeFinder;
this.db = db;
this.changeNotesFactory = changeNotesFactory;
this.changeControlFactory = changeControlFactory;
+ this.permissionBackend = permissionBackend;
}
public void addChange(String id, Map<Change.Id, ChangeResource> changes)
@@ -80,9 +87,13 @@
List<ChangeControl> matched =
useIndex ? changeFinder.find(id, currentUser) : changeFromNotesFactory(id, currentUser);
List<ChangeControl> toAdd = new ArrayList<>(changes.size());
- boolean canMaintainServer =
- currentUser.isIdentifiedUser()
- && currentUser.asIdentifiedUser().getCapabilities().canMaintainServer();
+ boolean canMaintainServer;
+ try {
+ permissionBackend.user(currentUser).check(GlobalPermission.MAINTAIN_SERVER);
+ canMaintainServer = true;
+ } catch (AuthException | PermissionBackendException e) {
+ canMaintainServer = false;
+ }
for (ChangeControl ctl : matched) {
if (!changes.containsKey(ctl.getId())
&& inProject(projectControl, ctl.getProject())
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
index 2f3d10f6..87e90f4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -20,8 +20,10 @@
import com.google.common.util.concurrent.Atomics;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.CapabilityUtils;
import com.google.gerrit.server.args4j.SubcommandHandler;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
@@ -41,6 +43,7 @@
}
private final CurrentUser currentUser;
+ private final PermissionBackend permissionBackend;
private final Map<String, CommandProvider> commands;
private final AtomicReference<Command> atomicCmd;
@@ -51,8 +54,12 @@
private List<String> args = new ArrayList<>();
@Inject
- DispatchCommand(CurrentUser cu, @Assisted final Map<String, CommandProvider> all) {
- currentUser = cu;
+ DispatchCommand(
+ CurrentUser user,
+ PermissionBackend permissionBackend,
+ @Assisted Map<String, CommandProvider> all) {
+ this.currentUser = user;
+ this.permissionBackend = permissionBackend;
commands = all;
atomicCmd = Atomics.newReference();
}
@@ -117,9 +124,13 @@
pluginName = ((BaseCommand) cmd).getPluginName();
}
try {
- CapabilityUtils.checkRequiresCapability(currentUser, pluginName, cmd.getClass());
+ permissionBackend
+ .user(currentUser)
+ .checkAny(GlobalPermission.fromAnnotation(pluginName, cmd.getClass()));
} catch (AuthException e) {
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, e.getMessage());
+ } catch (PermissionBackendException e) {
+ throw new UnloggedFailure(1, "fatal: permission check unavailable", e);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
index 21bfe9b..392fd29 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/FlushCaches.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.config.ListCaches;
import com.google.gerrit.server.config.ListCaches.OutputFormat;
import com.google.gerrit.server.config.PostCaches;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -81,6 +82,8 @@
}
} catch (RestApiException e) {
throw die(e.getMessage());
+ } catch (PermissionBackendException e) {
+ throw new Failure(1, "unavailable", e);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
index fc65cf3..0ee1c28 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
@@ -18,6 +18,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.Index;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.sshd.ChangeArgumentParser;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -57,7 +58,7 @@
for (ChangeResource rsrc : changes.values()) {
try {
index.apply(rsrc, new Index.Input());
- } catch (IOException | RestApiException | OrmException e) {
+ } catch (IOException | RestApiException | OrmException | PermissionBackendException e) {
ok = false;
writeError(
"error", String.format("failed to index change %s: %s", rsrc.getId(), e.getMessage()));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
index 4ebc568..3465a9c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/KillCommand.java
@@ -25,6 +25,7 @@
import com.google.gerrit.server.config.DeleteTask;
import com.google.gerrit.server.config.TaskResource;
import com.google.gerrit.server.config.TasksCollection;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -50,7 +51,7 @@
try {
TaskResource taskRsrc = tasksCollection.parse(cfgRsrc, IdString.fromDecoded(id));
deleteTask.apply(taskRsrc, null);
- } catch (AuthException | ResourceNotFoundException e) {
+ } catch (AuthException | ResourceNotFoundException | PermissionBackendException e) {
stderr.print("kill: " + id + ": No such task\n");
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
index 1192eb5..2e5bf71 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Query.java
@@ -24,7 +24,7 @@
import org.kohsuke.args4j.Option;
@CommandMetaData(name = "query", description = "Query the change database")
-class Query extends SshCommand {
+public class Query extends SshCommand {
@Inject private OutputStreamQuery processor;
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 21591dd..bfa1a81 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -42,6 +42,7 @@
import com.google.gerrit.server.account.PutHttpPassword;
import com.google.gerrit.server.account.PutName;
import com.google.gerrit.server.account.PutPreferred;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gwtorm.server.OrmException;
@@ -174,7 +175,8 @@
}
private void setAccount()
- throws OrmException, IOException, UnloggedFailure, ConfigInvalidException {
+ throws OrmException, IOException, UnloggedFailure, ConfigInvalidException,
+ PermissionBackendException {
user = genericUserFactory.create(id);
rsrc = new AccountResource(user);
try {
@@ -237,7 +239,7 @@
private void deleteSshKeys(List<String> sshKeys)
throws RestApiException, OrmException, RepositoryNotFoundException, IOException,
- ConfigInvalidException {
+ ConfigInvalidException, PermissionBackendException {
List<SshKeyInfo> infos = getSshKeys.apply(rsrc);
if (sshKeys.contains("ALL")) {
for (SshKeyInfo i : infos) {
@@ -263,7 +265,8 @@
}
private void addEmail(String email)
- throws UnloggedFailure, RestApiException, OrmException, IOException, ConfigInvalidException {
+ throws UnloggedFailure, RestApiException, OrmException, IOException, ConfigInvalidException,
+ PermissionBackendException {
EmailInput in = new EmailInput();
in.email = email;
in.noConfirmation = true;
@@ -275,7 +278,8 @@
}
private void deleteEmail(String email)
- throws RestApiException, OrmException, IOException, ConfigInvalidException {
+ throws RestApiException, OrmException, IOException, ConfigInvalidException,
+ PermissionBackendException {
if (email.equals("ALL")) {
List<EmailInfo> emails = getEmails.apply(rsrc);
for (EmailInfo e : emails) {
@@ -286,7 +290,8 @@
}
}
- private void putPreferred(String email) throws RestApiException, OrmException, IOException {
+ private void putPreferred(String email)
+ throws RestApiException, OrmException, IOException, PermissionBackendException {
for (EmailInfo e : getEmails.apply(rsrc)) {
if (e.email.equals(email)) {
putPreferred.apply(new AccountResource.Email(user, email), null);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index e16f270..1ed7db3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -23,6 +23,7 @@
import com.google.gerrit.common.Version;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.config.GetSummary;
@@ -34,6 +35,9 @@
import com.google.gerrit.server.config.ListCaches;
import com.google.gerrit.server.config.ListCaches.CacheInfo;
import com.google.gerrit.server.config.ListCaches.CacheType;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.gerrit.sshd.SshDaemon;
@@ -80,12 +84,10 @@
private boolean showThreads;
@Inject private SshDaemon daemon;
-
@Inject private ListCaches listCaches;
-
@Inject private GetSummary getSummary;
-
@Inject private CurrentUser self;
+ @Inject private PermissionBackend permissionBackend;
@Option(
name = "--width",
@@ -168,7 +170,15 @@
printDiskCaches(caches);
stdout.print('\n');
- if (self.getCapabilities().canMaintainServer()) {
+ boolean showJvm;
+ try {
+ permissionBackend.user(self).check(GlobalPermission.MAINTAIN_SERVER);
+ showJvm = true;
+ } catch (AuthException | PermissionBackendException e) {
+ // Silently ignore and do not display detailed JVM information.
+ showJvm = false;
+ }
+ if (showJvm) {
sshSummary();
SummaryInfo summary = getSummary.setGc(gc).setJvm(showJVM).apply(new ConfigResource());
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index 13db697..dfb9c9c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -27,6 +27,9 @@
import com.google.gerrit.server.config.ListTasks.TaskInfo;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.WorkQueue.Task;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -60,10 +63,9 @@
)
private boolean groupByQueue;
+ @Inject private PermissionBackend permissionBackend;
@Inject private ListTasks listTasks;
-
@Inject private IdentifiedUser currentUser;
-
@Inject private WorkQueue workQueue;
private int columns = 80;
@@ -83,7 +85,7 @@
}
@Override
- protected void run() throws UnloggedFailure {
+ protected void run() throws Failure {
maxCommandWidth = wide ? Integer.MAX_VALUE : columns - 8 - 12 - 12 - 4 - 4;
stdout.print(
String.format(
@@ -97,10 +99,12 @@
tasks = listTasks.apply(new ConfigResource());
} catch (AuthException e) {
throw die(e);
+ } catch (PermissionBackendException e) {
+ throw new Failure(1, "permission backend unavailable", e);
}
- boolean viewAll = currentUser.getCapabilities().canViewQueue();
- long now = TimeUtil.nowMs();
+ boolean viewAll = permissionBackend.user(currentUser).testOrFalse(GlobalPermission.VIEW_QUEUE);
+ long now = TimeUtil.nowMs();
if (groupByQueue) {
ListMultimap<String, TaskInfo> byQueue = byQueue(tasks);
for (String queueName : byQueue.keySet()) {
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index c96c87e..0357e6c4 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -26,6 +26,9 @@
<name>Andrew Bonventre</name>
</developer>
<developer>
+ <name>Becky Siegel</name>
+ </developer>
+ <developer>
<name>Dave Borowitz</name>
</developer>
<developer>
@@ -41,6 +44,12 @@
<name>Hugo Arès</name>
</developer>
<developer>
+ <name>Kasper Nilsson</name>
+ </developer>
+ <developer>
+ <name>Logan Hanks</name>
+ </developer>
+ <developer>
<name>Martin Fick</name>
</developer>
<developer>
diff --git a/plugins/download-commands b/plugins/download-commands
index 8357e94..6ee2462 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 8357e942dd9da82884a4e1b6e4697479153d0496
+Subproject commit 6ee246245b9200062e753d1c6943d5782cb7fee0
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
index 6678944..580bb4b 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
@@ -23,6 +23,8 @@
<link rel="import" href="../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-tooltip-behavior.html">
+<script>void(0);</script>
+
<test-fixture id="basic">
<template>
<tooltip-behavior-element></tooltip-behavior-element>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 71ccb04..2cfa5ba 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -81,8 +81,7 @@
down-arrow
vertical-offset="32"
horizontal-align="right"
- on-tap-item-cherrypick="_handleCherrypickTap"
- on-tap-item-delete="_handleDeleteTap"
+ on-tap-item="_handleOveflowItemTap"
hidden$="[[_shouldHideActions(_menuActions.*, _loading)]]"
disabled-ids="[[_disabledMenuActions]]"
items="[[_menuActions]]">More</gr-dropdown>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 2b0916d..ea519d7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -87,14 +87,13 @@
method: 'POST',
};
- /**
- * Keys for actions to appear in the overflow menu rather than the top-level
- * set of action buttons.
- */
- var MENU_ACTION_KEYS = [
- 'cherrypick',
- '/', // '/' is the key for the delete action.
- ];
+ var ActionPriority = {
+ CHANGE: 2,
+ DEFAULT: 0,
+ PRIMARY: 3,
+ REVIEW: -3,
+ REVISION: 1,
+ };
Polymer({
is: 'gr-change-actions',
@@ -158,16 +157,42 @@
_allActionValues: {
type: Array,
computed: '_computeAllActions(actions.*, revisionActions.*,' +
- 'primaryActionKeys.*, _additionalActions.*, change)',
+ 'primaryActionKeys.*, _additionalActions.*, change, ' +
+ '_actionPriorityOverrides.*)',
},
_topLevelActions: {
type: Array,
computed: '_computeTopLevelActions(_allActionValues.*, ' +
- '_hiddenActions.*)',
+ '_hiddenActions.*, _overflowActions.*)',
},
_menuActions: {
type: Array,
- computed: '_computeMenuActions(_allActionValues.*, _hiddenActions.*)',
+ computed: '_computeMenuActions(_allActionValues.*, _hiddenActions.*, ' +
+ '_overflowActions.*)',
+ },
+ _overflowActions: {
+ type: Array,
+ value: function() {
+ var value = [
+ {
+ type: ActionType.CHANGE,
+ key: ChangeActions.DELETE,
+ },
+ {
+ type: ActionType.REVISION,
+ key: RevisionActions.DELETE,
+ },
+ {
+ type: ActionType.REVISION,
+ key: RevisionActions.CHERRYPICK,
+ }
+ ];
+ return value;
+ },
+ },
+ _actionPriorityOverrides: {
+ type: Array,
+ value: function() { return []; },
},
_additionalActions: {
type: Array,
@@ -227,7 +252,8 @@
enabled: true,
label: label,
__type: type,
- __key: ADDITIONAL_ACTION_KEY_PREFIX + Math.random().toString(36),
+ __key: ADDITIONAL_ACTION_KEY_PREFIX +
+ Math.random().toString(36).substr(2),
};
this.push('_additionalActions', action);
return action.__key;
@@ -249,6 +275,42 @@
], value);
},
+ setActionOverflow: function(type, key, overflow) {
+ if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
+ throw Error('Invalid action type given: ' + type);
+ }
+ var index = this._getActionOverflowIndex(type, key);
+ var action = {
+ type: type,
+ key: key,
+ overflow: overflow,
+ };
+ if (!overflow && index !== -1) {
+ this.splice('_overflowActions', index, 1);
+ } else if (overflow) {
+ this.push('_overflowActions', action);
+ }
+ },
+
+ setActionPriority: function(type, key, priority) {
+ if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
+ throw Error('Invalid action type given: ' + type);
+ }
+ var index = this._actionPriorityOverrides.findIndex(function(action) {
+ return action.type === type && action.key === key;
+ });
+ var action = {
+ type: type,
+ key: key,
+ priority: priority,
+ };
+ if (index !== -1) {
+ this.set('_actionPriorityOverrides', index, action);
+ } else {
+ this.push('_actionPriorityOverrides', action);
+ }
+ },
+
setActionHidden: function(type, key, hidden) {
if (type !== ActionType.CHANGE && type !== ActionType.REVISION) {
throw Error('Invalid action type given: ' + type);
@@ -459,20 +521,46 @@
return;
}
var type = el.getAttribute('data-action-type');
- if (type === ActionType.REVISION) {
- this._handleRevisionAction(key);
- } else if (key === ChangeActions.REVERT) {
- this.showRevertDialog();
- } else if (key === ChangeActions.ABANDON) {
- this._showActionDialog(this.$.confirmAbandonDialog);
- } else if (key === QUICK_APPROVE_ACTION.key) {
- var action = this._allActionValues.find(function(o) {
- return o.key === key;
- });
- this._fireAction(
- this._prependSlash(key), action, true, action.payload);
- } else {
- this._fireAction(this._prependSlash(key), this.actions[key], false);
+ this._handleAction(type, key);
+ },
+
+ _handleOveflowItemTap: function(e) {
+ this._handleAction(e.detail.action.__type, e.detail.action.__key);
+ },
+
+ _handleAction: function(type, key) {
+ switch (type) {
+ case ActionType.REVISION:
+ this._handleRevisionAction(key);
+ break;
+ case ActionType.CHANGE:
+ this._handleChangeAction(key);
+ break;
+ default:
+ this._fireAction(this._prependSlash(key), this.actions[key], false);
+ }
+ },
+
+ _handleChangeAction: function(key) {
+ switch (key) {
+ case ChangeActions.REVERT:
+ this.showRevertDialog();
+ break;
+ case ChangeActions.ABANDON:
+ this._showActionDialog(this.$.confirmAbandonDialog);
+ break;
+ case QUICK_APPROVE_ACTION.key:
+ var action = this._allActionValues.find(function(o) {
+ return o.key === key;
+ });
+ this._fireAction(
+ this._prependSlash(key), action, true, action.payload);
+ break;
+ case ChangeActions.DELETE:
+ this._handleDeleteTap();
+ break;
+ default:
+ this._fireAction(this._prependSlash(key), this.actions[key], false);
}
},
@@ -481,6 +569,12 @@
case RevisionActions.REBASE:
this._showActionDialog(this.$.confirmRebase);
break;
+ case RevisionActions.DELETE:
+ this._handleDeleteConfirm();
+ break;
+ case RevisionActions.CHERRYPICK:
+ this._handleCherrypickTap();
+ break;
case RevisionActions.SUBMIT:
if (!this._canSubmitChange()) {
return;
@@ -577,11 +671,17 @@
this._fireAction('/', this.actions[ChangeActions.DELETE], false);
},
- _setLoadingOnButtonWithKey: function(key) {
+ _getActionOverflowIndex: function(type, key) {
+ return this._overflowActions.findIndex(function(action) {
+ return action.type === type && action.key === key;
+ });
+ },
+
+ _setLoadingOnButtonWithKey: function(type, key) {
this._actionLoadingMessage = this._computeLoadingLabel(key);
// If the action appears in the overflow menu.
- if (MENU_ACTION_KEYS.indexOf(key) !== -1) {
+ if (this._getActionOverflowIndex(type, key) !== -1) {
this.push('_disabledMenuActions', key === '/' ? 'delete' : key);
return function() {
this._actionLoadingMessage = null;
@@ -601,8 +701,8 @@
},
_fireAction: function(endpoint, action, revAction, opt_payload) {
- var cleanupFn = this._setLoadingOnButtonWithKey(action.__key);
-
+ var cleanupFn =
+ this._setLoadingOnButtonWithKey(action.__type, action.__key);
this._send(action.method, opt_payload, endpoint, revAction, cleanupFn)
.then(this._handleResponse.bind(this, action));
},
@@ -696,10 +796,12 @@
* @param {splices} primariesRecord
* @param {splices} additionalActionsRecord
* @param {Object} change The change object.
+ * @param {Object} priorityOverridesRecord
* @return {Array}
*/
_computeAllActions: function(changeActionsRecord, revisionActionsRecord,
- primariesRecord, additionalActionsRecord, change) {
+ primariesRecord, additionalActionsRecord, change,
+ priorityOverridesRecord) {
var revisionActionValues = this._getActionValues(revisionActionsRecord,
primariesRecord, additionalActionsRecord, ActionType.REVISION);
var changeActionValues = this._getActionValues(changeActionsRecord,
@@ -710,58 +812,69 @@
}
return revisionActionValues
.concat(changeActionValues)
- .sort(this._actionComparator);
+ .sort(this._actionComparator.bind(this));
+ },
+
+ _getActionPriority: function(action) {
+ if (action.__type && action.__key) {
+ var overrideAction = this._actionPriorityOverrides.find(function(i) {
+ return i.type === action.__type && i.key === action.__key;
+ });
+
+ if (overrideAction !== undefined) {
+ return overrideAction.priority;
+ }
+ }
+ if (action.__key === 'review') {
+ return ActionPriority.REVIEW;
+ } else if (action.__primary) {
+ return ActionPriority.PRIMARY;
+ } else if (action.__type === ActionType.CHANGE) {
+ return ActionPriority.CHANGE;
+ } else if (action.__type === ActionType.REVISION) {
+ return ActionPriority.REVISION;
+ }
+ return ActionPriority.DEFAULT;
},
/**
* Sort comparator to define the order of change actions.
*/
_actionComparator: function(actionA, actionB) {
- // The code review action always appears first.
- if (actionA.__key === 'review') {
- return -1;
- } else if (actionB.__key === 'review') {
- return 1;
+ var priorityDelta = this._getActionPriority(actionA) -
+ this._getActionPriority(actionB);
+ // Sort by the button label if same priority.
+ if (priorityDelta === 0) {
+ return actionA.label > actionB.label ? 1 : -1;
+ } else {
+ return priorityDelta;
}
-
- // Primary actions always appear last.
- if (actionA.__primary) {
- return 1;
- } else if (actionB.__primary) {
- return -1;
- }
-
- // Change actions appear before revision actions.
- if (actionA.__type === 'change' && actionB.__type === 'revision') {
- return 1;
- } else if (actionA.__type === 'revision' && actionB.__type === 'change') {
- return -1;
- }
-
- // Otherwise, sort by the button label.
- return actionA.label > actionB.label ? 1 : -1;
},
- _computeTopLevelActions: function(actionRecord, hiddenActionsRecord) {
+ _computeTopLevelActions: function(actionRecord, hiddenActionsRecord,
+ overflowActionsRecord) {
var hiddenActions = hiddenActionsRecord.base || [];
return actionRecord.base.filter(function(a) {
- return MENU_ACTION_KEYS.indexOf(a.__key) === -1 &&
- hiddenActions.indexOf(a.__key) === -1;
- });
+ var overflow = this._getActionOverflowIndex(a.__type, a.__key) !== -1;
+ return !overflow && hiddenActions.indexOf(a.__key) === -1;
+ }.bind(this));
},
- _computeMenuActions: function(actionRecord, hiddenActionsRecord) {
+ _computeMenuActions: function(actionRecord, hiddenActionsRecord,
+ overflowActionsRecord) {
var hiddenActions = hiddenActionsRecord.base || [];
- return actionRecord.base
- .filter(function(a) {
- return MENU_ACTION_KEYS.indexOf(a.__key) !== -1 &&
- hiddenActions.indexOf(a.__key) === -1;
- })
- .map(function(action) {
- var key = action.__key;
- if (key === '/') { key = 'delete'; }
- return {name: action.label, id: key, };
- });
+ return actionRecord.base.filter(function(a) {
+ var overflow = this._getActionOverflowIndex(a.__type, a.__key) !== -1;
+ return overflow && hiddenActions.indexOf(a.__key) === -1;
+ }.bind(this)).map(function(action) {
+ var key = action.__key;
+ if (key === '/') { key = 'delete'; }
+ return {
+ name: action.label,
+ id: key + '-' + action.__type,
+ action: action,
+ };
+ });
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 83a1c7e..b7159ea 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -135,7 +135,8 @@
test('hide menu action', function(done) {
flush(function() {
- var buttonEl = element.$.moreActions.$$('span[data-id="delete"]');
+ var buttonEl =
+ element.$.moreActions.$$('span[data-id="delete-revision"]');
assert.isOk(buttonEl);
assert.throws(element.setActionHidden.bind(element, 'invalid type'));
element.setActionHidden(element.ActionType.CHANGE,
@@ -145,13 +146,15 @@
element.ChangeActions.DELETE, true);
assert.lengthOf(element._hiddenActions, 1);
flush(function() {
- var buttonEl = element.$.moreActions.$$('span[data-id="delete"]');
+ var buttonEl =
+ element.$.moreActions.$$('span[data-id="delete-revision"]');
assert.isNotOk(buttonEl);
element.setActionHidden(element.ActionType.CHANGE,
element.RevisionActions.DELETE, false);
flush(function() {
- var buttonEl = element.$.moreActions.$$('span[data-id="delete"]');
+ var buttonEl =
+ element.$.moreActions.$$('span[data-id="delete-revision"]');
assert.isOk(buttonEl);
done();
});
@@ -174,7 +177,7 @@
test('delete buttons have explicit labels', function(done) {
flush(function() {
var deleteItems = element.$.moreActions.items.filter(function(item) {
- return item.id === 'delete';
+ return item.id.indexOf('delete') === 0;
});
assert.equal(deleteItems.length, 2);
assert.notEqual(deleteItems[0].name, deleteItems[1].name);
@@ -204,16 +207,15 @@
test('_actionComparator sort order', function() {
var actions = [
{label: '123', __type: 'change', __key: 'review'},
- {label: 'abc', __type: 'revision'},
+ {label: 'abc-ro', __type: 'revision'},
{label: 'abc', __type: 'change'},
{label: 'def', __type: 'change'},
- {label: 'def', __type: 'change', __primary: true},
+ {label: 'def-p', __type: 'change', __primary: true},
];
var result = actions.slice();
result.reverse();
- result.sort(element._actionComparator);
-
+ result.sort(element._actionComparator.bind(element));
assert.deepEqual(result, actions);
});
@@ -414,7 +416,8 @@
test('_setLoadingOnButtonWithKey top-level', function() {
var key = 'rebase';
- var cleanup = element._setLoadingOnButtonWithKey(key);
+ var type = 'revision';
+ var cleanup = element._setLoadingOnButtonWithKey(type, key);
assert.equal(element._actionLoadingMessage, 'Rebasing...');
var button = element.$$('[data-action-key="' + key + '"]');
@@ -432,7 +435,8 @@
test('_setLoadingOnButtonWithKey overflow menu', function() {
var key = 'cherrypick';
- var cleanup = element._setLoadingOnButtonWithKey(key);
+ var type = 'revision';
+ var cleanup = element._setLoadingOnButtonWithKey(type, key);
assert.equal(element._actionLoadingMessage, 'Cherry-Picking...');
assert.include(element._disabledMenuActions, 'cherrypick');
assert.isFunction(cleanup);
@@ -728,5 +732,27 @@
assert.equal(approveButton.getAttribute('data-label'), 'bar+2');
});
});
+
+ suite('setActionOverflow', function() {
+ test('move action from overflow', function() {
+ assert.isNotOk(element.$$('[data-action-key="cherrypick"]'));
+ assert.strictEqual(
+ element.$.moreActions.items[0].id, 'cherrypick-revision');
+ element.setActionOverflow('revision', 'cherrypick', false);
+ flushAsynchronousOperations();
+ assert.isOk(element.$$('[data-action-key="cherrypick"]'));
+ assert.notEqual(
+ element.$.moreActions.items[0].id, 'cherrypick-revision');
+ });
+
+ test('move action to overflow', function() {
+ assert.isOk(element.$$('[data-action-key="submit"]'));
+ element.setActionOverflow('revision', 'submit', true);
+ flushAsynchronousOperations();
+ assert.isNotOk(element.$$('[data-action-key="submit"]'));
+ assert.strictEqual(
+ element.$.moreActions.items[3].id, 'submit-revision');
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 834069e..8435692 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -235,7 +235,7 @@
},
_computeProjectURL: function(project) {
- return this.getBaseUrl() + '/q/status:open+project:' +
+ return this.getBaseUrl() + '/q/project:' +
this.encodeURL(project, false);
},
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index cbe5dad..09877a0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -435,7 +435,7 @@
commit-info="[[_commitInfo]]"></gr-commit-info>
<span class="latestPatchContainer">
/
- <a href$="/c/[[_change._number]]">Go to latest patch set</a>
+ <a href$="[[getBaseUrl()]]/c/[[_change._number]]">Go to latest patch set</a>
</span>
<span class="downloadContainer desktop">
/
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index 14361f4..4449131 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -77,11 +77,14 @@
<gr-button id="oldMessagesBtn" link on-tap="_handleShowAllTap">
[[_computeNumMessagesText(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]
</gr-button>
- /
- <gr-button id="incrementMessagesBtn" link
- on-tap="_handleIncrementShownMessages">
- [[_computeIncrementText(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]
- </gr-button>
+ <span
+ hidden$="[[_computeIncrementHidden(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]">
+ /
+ <gr-button id="incrementMessagesBtn" link
+ on-tap="_handleIncrementShownMessages">
+ [[_computeIncrementText(_visibleMessages, _processedMessages, _hideAutomated, _visibleMessages.length)]]
+ </gr-button>
+ </span>
</span>
<template
is="dom-repeat"
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index 0d58d96..8750e9d 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -327,5 +327,11 @@
var total = this._numRemaining(visibleMessages, messages, hideAutomated);
return total === 1 ? 'Show 1 message' : 'Show all ' + total + ' messages';
},
+
+ _computeIncrementHidden: function(visibleMessages, messages,
+ hideAutomated) {
+ var total = this._numRemaining(visibleMessages, messages, hideAutomated);
+ return total <= this._getDelta(visibleMessages, messages, hideAutomated);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index cdca365..23fc5aa 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -87,13 +87,13 @@
assert.isFalse(element.$.messageControlsContainer.hasAttribute('hidden'));
assert.equal(getMessages().length, 20);
- assert.equal(element.$.incrementMessagesBtn.innerText,
+ assert.equal(element.$.incrementMessagesBtn.innerText.trim(),
'Show 5 more');
MockInteractions.tap(element.$.incrementMessagesBtn);
flushAsynchronousOperations();
assert.equal(getMessages().length, 25);
- assert.equal(element.$.incrementMessagesBtn.innerText,
+ assert.equal(element.$.incrementMessagesBtn.innerText.trim(),
'Show 1 more');
MockInteractions.tap(element.$.incrementMessagesBtn);
flushAsynchronousOperations();
@@ -400,6 +400,18 @@
assert.equal(messageEls.length, 1);
assert.equal(messageEls[0].message.message, messages[0].message);
});
+
+ test('hide increment text if increment >= total remaining', function() {
+ // Test with stubbed return values, as _numRemaining and _getDelta have
+ // their own tests.
+ sandbox.stub(element, '_getDelta').returns(5);
+ var remainingStub = sandbox.stub(element, '_numRemaining').returns(6);
+ assert.isFalse(element._computeIncrementHidden(null, null, null));
+ remainingStub.restore();
+
+ sandbox.stub(element, '_numRemaining').returns(4);
+ assert.isTrue(element._computeIncrementHidden(null, null, null));
+ });
});
suite('gr-messages-list automate tests', function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
index 368c613..952dc67 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
@@ -17,6 +17,8 @@
// Prevent redefinition.
if (window.GrDiffBuilderImage) { return; }
+ var IMAGE_MIME_PATTERN = /^image\/(bmp|gif|jpeg|jpg|png|tiff|webp)$/;
+
function GrDiffBuilderImage(diff, comments, prefs, outputEl, baseImage,
revisionImage) {
GrDiffBuilderSideBySide.call(this, diff, comments, prefs, outputEl, []);
@@ -53,7 +55,7 @@
GrDiffBuilderImage.prototype._createImageCell =
function(image, className, section) {
var td = this._createElement('td', className);
- if (image) {
+ if (image && IMAGE_MIME_PATTERN.test(image.type)) {
var imageEl = this._createElement('img');
imageEl.onload = function() {
image._height = imageEl.naturalHeight;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
index fe57c43..5a49daa 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
@@ -17,6 +17,8 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-storage/gr-storage.html">
<dom-module id="gr-diff-preferences">
@@ -70,71 +72,79 @@
color: #888;
}
</style>
- <div class="header">
- Diff View Preferences
- </div>
- <div class="mainContainer">
- <div class="pref">
- <label for="contextSelect">Context</label>
- <select id="contextSelect" on-change="_handleContextSelectChange">
- <option value="3">3 lines</option>
- <option value="10">10 lines</option>
- <option value="25">25 lines</option>
- <option value="50">50 lines</option>
- <option value="75">75 lines</option>
- <option value="100">100 lines</option>
- <option value="-1">Whole file</option>
- </select>
+
+ <gr-overlay id="prefsOverlay" with-backdrop>
+ <div class="header">
+ Diff View Preferences
</div>
- <div class="pref">
- <label for="lineWrappingInput">Fit to screen</label>
- <input
- is="iron-input"
- type="checkbox"
- id="lineWrappingInput"
- on-tap="_handlelineWrappingTap">
+ <div class="mainContainer">
+ <div class="pref">
+ <label for="contextSelect">Context</label>
+ <select id="contextSelect" on-change="_handleContextSelectChange">
+ <option value="3">3 lines</option>
+ <option value="10">10 lines</option>
+ <option value="25">25 lines</option>
+ <option value="50">50 lines</option>
+ <option value="75">75 lines</option>
+ <option value="100">100 lines</option>
+ <option value="-1">Whole file</option>
+ </select>
+ </div>
+ <div class="pref">
+ <label for="lineWrappingInput">Fit to screen</label>
+ <input
+ is="iron-input"
+ type="checkbox"
+ id="lineWrappingInput"
+ on-tap="_handlelineWrappingTap">
+ </div>
+ <div class="pref" id="columnsPref"
+ hidden$="[[_newPrefs.line_wrapping]]">
+ <label for="columnsInput">Diff width</label>
+ <input is="iron-input" type="number" id="columnsInput"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{_newPrefs.line_length}}">
+ </div>
+ <div class="pref">
+ <label for="tabSizeInput">Tab width</label>
+ <input is="iron-input" type="number" id="tabSizeInput"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{_newPrefs.tab_size}}">
+ </div>
+ <div class="pref" hidden$="[[!_newPrefs.font_size]]">
+ <label for="fontSizeInput">Font size</label>
+ <input is="iron-input" type="number" id="fontSizeInput"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{_newPrefs.font_size}}">
+ </div>
+ <div class="pref">
+ <label for="showTabsInput">Show tabs</label>
+ <input is="iron-input" type="checkbox" id="showTabsInput"
+ on-tap="_handleShowTabsTap">
+ </div>
+ <div class="pref">
+ <label for="showTrailingWhitespaceInput">
+ Show trailing whitespace</label>
+ <input is="iron-input" type="checkbox"
+ id="showTrailingWhitespaceInput"
+ on-tap="_handleShowTrailingWhitespaceTap">
+ </div>
+ <div class="pref">
+ <label for="syntaxHighlightInput">Syntax highlighting</label>
+ <input is="iron-input" type="checkbox" id="syntaxHighlightInput"
+ on-tap="_handleSyntaxHighlightTap">
+ </div>
</div>
- <div class="pref" id="columnsPref" hidden$="[[_newPrefs.line_wrapping]]">
- <label for="columnsInput">Diff width</label>
- <input is="iron-input" type="number" id="columnsInput"
- prevent-invalid-input
- allowed-pattern="[0-9]"
- bind-value="{{_newPrefs.line_length}}">
+ <div class="actions">
+ <gr-button id="saveButton" primary on-tap="_handleSave">Save</gr-button>
+ <gr-button id="cancelButton" on-tap="_handleCancel">Cancel</gr-button>
</div>
- <div class="pref">
- <label for="tabSizeInput">Tab width</label>
- <input is="iron-input" type="number" id="tabSizeInput"
- prevent-invalid-input
- allowed-pattern="[0-9]"
- bind-value="{{_newPrefs.tab_size}}">
- </div>
- <div class="pref" hidden$="[[!_newPrefs.font_size]]">
- <label for="fontSizeInput">Font size</label>
- <input is="iron-input" type="number" id="fontSizeInput"
- prevent-invalid-input
- allowed-pattern="[0-9]"
- bind-value="{{_newPrefs.font_size}}">
- </div>
- <div class="pref">
- <label for="showTabsInput">Show tabs</label>
- <input is="iron-input" type="checkbox" id="showTabsInput"
- on-tap="_handleShowTabsTap">
- </div>
- <div class="pref">
- <label for="showTrailingWhitespaceInput">Show trailing whitespace</label>
- <input is="iron-input" type="checkbox" id="showTrailingWhitespaceInput"
- on-tap="_handleShowTrailingWhitespaceTap">
- </div>
- <div class="pref">
- <label for="syntaxHighlightInput">Syntax highlighting</label>
- <input is="iron-input" type="checkbox" id="syntaxHighlightInput"
- on-tap="_handleSyntaxHighlightTap">
- </div>
- </div>
- <div class="actions">
- <gr-button id="saveButton" primary on-tap="_handleSave">Save</gr-button>
- <gr-button id="cancelButton" on-tap="_handleCancel">Cancel</gr-button>
- </div>
+ </overlay>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ <gr-storage id="storage"></gr-storage>
</template>
<script src="gr-diff-preferences.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
index fd2a6f5..e29f8ef 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
@@ -17,18 +17,6 @@
Polymer({
is: 'gr-diff-preferences',
- /**
- * Fired when the user presses the save button.
- *
- * @event save
- */
-
- /**
- * Fired when the user presses the cancel button.
- *
- * @event cancel
- */
-
properties: {
prefs: {
type: Object,
@@ -106,14 +94,43 @@
this.set('_newPrefs.line_wrapping', Polymer.dom(e).rootTarget.checked);
},
- _handleSave: function() {
+ _handleSave: function(e) {
+ e.stopPropagation();
this.prefs = this._newPrefs;
this.localPrefs = this._newLocalPrefs;
- this.fire('save', null, {bubbles: false});
+ var el = Polymer.dom(e).rootTarget;
+ el.disabled = true;
+ this.$.storage.savePreferences(this._localPrefs);
+ this._saveDiffPreferences().then(function(response) {
+ el.disabled = false;
+ if (!response.ok) { return response; }
+
+ this.$.prefsOverlay.close();
+ }.bind(this)).catch(function(err) {
+ el.disabled = false;
+ }.bind(this));
},
- _handleCancel: function() {
- this.fire('cancel', null, {bubbles: false});
+ _handleCancel: function(e) {
+ e.stopPropagation();
+ this.$.prefsOverlay.close();
+ },
+
+ _handlePrefsTap: function(e) {
+ e.preventDefault();
+ this._openPrefs();
+ },
+
+ open: function() {
+ this.$.prefsOverlay.open().then(function() {
+ var focusStops = this.getFocusStops();
+ this.$.prefsOverlay.setFocusStops(focusStops);
+ this.resetFocus();
+ }.bind(this));
+ },
+
+ _saveDiffPreferences: function() {
+ return this.$.restAPI.saveDiffPreferences(this.prefs);
},
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
index 06f617a..b163c71 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
@@ -35,11 +35,17 @@
<script>
suite('gr-diff-preferences tests', function() {
var element;
+ var sandbox;
setup(function() {
+ sandbox = sinon.sandbox.create();
element = fixture('basic');
});
+ teardown(function() {
+ sandbox.restore();
+ });
+
test('model changes', function() {
element.prefs = {
context: 10,
@@ -92,18 +98,25 @@
savePrefs.restore();
});
- test('events', function(done) {
- var savePromise = new Promise(function(resolve) {
- element.addEventListener('save', function() { resolve(); });
- });
- var cancelPromise = new Promise(function(resolve) {
- element.addEventListener('cancel', function() { resolve(); });
- });
- Promise.all([savePromise, cancelPromise]).then(function() {
- done();
- });
+ test('save button', function() {
+ element.prefs = {
+ font_size: '11',
+ };
+ element._newPrefs = {
+ font_size: '12',
+ };
+ var saveStub = sandbox.stub(element.$.restAPI, 'saveDiffPreferences',
+ function() { return Promise.resolve(); });
+
MockInteractions.tap(element.$$('gr-button[primary]'));
+ assert.deepEqual(element.prefs, element._newPrefs);
+ assert.deepEqual(saveStub.lastCall.args[0], element._newPrefs);
+ });
+
+ test('cancel button', function() {
+ var closeStub = sandbox.stub(element.$.prefsOverlay, 'close');
MockInteractions.tap(element.$$('gr-button:not([primary])'));
+ assert.isTrue(closeStub.called);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 1204170..a3ff3d1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -21,7 +21,6 @@
<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
-<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../gr-diff/gr-diff.html">
@@ -276,21 +275,17 @@
</span>
</div>
</div>
- <gr-overlay id="prefsOverlay" with-backdrop>
- <gr-diff-preferences
- id="diffPreferences"
- prefs="{{_prefs}}"
- local-prefs="{{_localPrefs}}"
- on-save="_handlePrefsSave"
- on-cancel="_handlePrefsCancel"></gr-diff-preferences>
- </gr-overlay>
+ <gr-diff-preferences
+ id="diffPreferences"
+ prefs="{{_prefs}}"
+ local-prefs="{{_localPrefs}}"></gr-diff-preferences>
<div class="fileNav mobile">
<a class="mobileNavLink"
- href$="[[_computeNavLinkURL(_path, _fileList, -1, 1)]]"><</a>
+ href$="[[_computeNavLinkURL(_path, _fileList, -1, 1)]]"><</a>
<div class="fullFileName mobile">[[_computeFileDisplayName(_path)]]
</div>
<a class="mobileNavLink"
- href$="[[_computeNavLinkURL(_path, _fileList, 1, 1)]]">></a>
+ href$="[[_computeNavLinkURL(_path, _fileList, 1, 1)]]">></a>
</div>
<gr-diff
id="diff"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index fcbbe23..3e73d52 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -363,7 +363,7 @@
this.modifierPressed(e)) { return; }
e.preventDefault();
- this._openPrefs();
+ this.$.diffPreferences.open();
},
_navToChangeView: function() {
@@ -389,15 +389,6 @@
page.show(this._computeNavLinkURL(path, fileList, direction));
},
- _openPrefs: function() {
- this.$.prefsOverlay.open().then(function() {
- var diffPreferences = this.$.diffPreferences;
- var focusStops = diffPreferences.getFocusStops();
- this.$.prefsOverlay.setFocusStops(focusStops);
- this.$.diffPreferences.resetFocus();
- }.bind(this));
- },
-
/**
* @param {?string} path The path of the current file being shown.
* @param {Array.<string>} fileList The list of files in this change and
@@ -599,7 +590,7 @@
_handlePrefsTap: function(e) {
e.preventDefault();
- this._openPrefs();
+ this.$.diffPreferences.open();
},
_handlePrefsSave: function(e) {
@@ -617,15 +608,6 @@
}.bind(this));
},
- _saveDiffPreferences: function() {
- return this.$.restAPI.saveDiffPreferences(this._prefs);
- },
-
- _handlePrefsCancel: function(e) {
- e.stopPropagation();
- this.$.prefsOverlay.close();
- },
-
/**
* _getDiffViewMode: Get the diff view (side-by-side or unified) based on
* the current state.
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 4759086..d54f715 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -111,8 +111,8 @@
'Should navigate to /c/42/');
assert.equal(element.changeViewState.selectedFileIndex, 0);
- var showPrefsStub = sandbox.stub(element.$.prefsOverlay, 'open',
- function() { return Promise.resolve({}); });
+ var showPrefsStub = sandbox.stub(element.$.diffPreferences.$.prefsOverlay,
+ 'open', function() { return Promise.resolve({}); });
MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
assert(showPrefsStub.calledOnce);
@@ -145,22 +145,6 @@
false, 'SIDE_BY_SIDE', false));
});
- test('saving diff preferences', function() {
- var savePrefs = sandbox.stub(element, '_handlePrefsSave');
- var cancelPrefs = sandbox.stub(element, '_handlePrefsCancel');
- element.$.diffPreferences._handleSave();
- assert(savePrefs.calledOnce);
- assert(cancelPrefs.notCalled);
- });
-
- test('cancelling diff preferences', function() {
- var savePrefs = sandbox.stub(element, '_handlePrefsSave');
- var cancelPrefs = sandbox.stub(element, '_handlePrefsCancel');
- element.$.diffPreferences._handleCancel();
- assert(cancelPrefs.calledOnce);
- assert(savePrefs.notCalled);
- });
-
test('keyboard shortcuts with patch range', function() {
element._changeNum = '42';
element._patchRange = {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index f010add..c267eb0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -577,6 +577,43 @@
element.reload();
});
});
+
+ test('does not render disallowed image type', function(done) {
+ var mockDiff = {
+ meta_a: {name: 'carrot.jpg', content_type: 'image/jpeg-evil',
+ lines: 560},
+ intraline_status: 'OK',
+ change_type: 'DELETED',
+ diff_header: [
+ 'diff --git a/carrot.jpg b/carrot.jpg',
+ 'index f9c2f2c..0000000 100644',
+ '--- a/carrot.jpg',
+ '+++ /dev/null',
+ 'Binary files differ',
+ ],
+ content: [{skip: 66}],
+ binary: true,
+ };
+ mockFile1.type = 'image/jpeg-evil';
+
+ stubs.push(sandbox.stub(element, '_getDiff',
+ function() { return Promise.resolve(mockDiff); }));
+
+ element.addEventListener('render', function() {
+ // Recognizes that it should be an image diff.
+ assert.isTrue(element.isImageDiff);
+ assert.instanceOf(
+ element.$.diffBuilder._builder, GrDiffBuilderImage);
+ var leftImage = element.$.diffTable.querySelector('td.left img');
+ assert.isNotOk(leftImage);
+ done();
+ });
+
+ element.$.restAPI.getDiffPreferences().then(function(prefs) {
+ element.prefs = prefs;
+ element.reload();
+ });
+ });
});
test('_handleTap lineNum', function(done) {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
index e84357f..6e1637c 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
@@ -23,6 +23,12 @@
* @event tap-item-<id>
*/
+ /**
+ * Fired when a non-link dropdown item is tapped.
+ *
+ * @event tap-item
+ */
+
properties: {
items: Array,
topContent: Object,
@@ -93,7 +99,13 @@
_handleItemTap: function(e) {
var id = e.target.getAttribute('data-id');
+ var item = this.items.find(function(item) {
+ return item.id === id;
+ });
if (id && this.disabledIds.indexOf(id) === -1) {
+ if (item) {
+ this.dispatchEvent(new CustomEvent('tap-item', {detail: item}));
+ }
this.dispatchEvent(new CustomEvent('tap-item-' + id));
}
},
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
index 2db38b9..f8b11ba 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
@@ -74,13 +74,17 @@
});
test('non link items', function() {
- element.items = [
- {name: 'item one', id: 'foo'}, {name: 'item two', id: 'bar'}];
- var stub = sinon.stub();
- element.addEventListener('tap-item-foo', stub);
+ var item0 = {name: 'item one', id: 'foo'};
+ element.items = [item0, {name: 'item two', id: 'bar'}];
+ var fooTapped = sinon.stub();
+ var tapped = sinon.stub();
+ element.addEventListener('tap-item-foo', fooTapped);
+ element.addEventListener('tap-item', tapped);
flushAsynchronousOperations();
MockInteractions.tap(element.$$('.itemAction'));
- assert.isTrue(stub.called);
+ assert.isTrue(fooTapped.called);
+ assert.isTrue(tapped.called);
+ assert.deepEqual(tapped.lastCall.args[0].detail, item0);
});
test('disabled non link item', function() {
@@ -88,10 +92,13 @@
element.disabledIds = ['foo'];
var stub = sinon.stub();
+ var tapped = sinon.stub();
element.addEventListener('tap-item-foo', stub);
+ element.addEventListener('tap-item', tapped);
flushAsynchronousOperations();
MockInteractions.tap(element.$$('.itemAction'));
assert.isFalse(stub.called);
+ assert.isFalse(tapped.called);
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
index 72c7f6e..9bcde07 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api.js
@@ -33,6 +33,16 @@
});
};
+ GrChangeActionsInterface.prototype.setActionOverflow = function(type, key,
+ overflow) {
+ return this._el.setActionOverflow(type, key, overflow);
+ };
+
+ GrChangeActionsInterface.prototype.setActionPriority = function(type, key,
+ priority) {
+ return this._el.setActionPriority(type, key, priority);
+ };
+
GrChangeActionsInterface.prototype.setActionHidden = function(type, key,
hidden) {
return this._el.setActionHidden(type, key, hidden);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
index 20b5dcb..d964cde 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -130,8 +130,8 @@
var button = element.$$('[data-action-key="' + key + '"]');
assert.isOk(button);
assert.isFalse(button.hasAttribute('hidden'));
- changeActions.setActionHidden(changeActions.ActionType.REVISION, key,
- true);
+ changeActions.setActionHidden(
+ changeActions.ActionType.REVISION, key, true);
flush(function() {
var button = element.$$('[data-action-key="' + key + '"]');
assert.isNotOk(button);
@@ -139,5 +139,41 @@
});
});
});
+
+ test('move action button to overflow', function(done) {
+ var key = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
+ flush(function() {
+ assert.isTrue(element.$.moreActions.hidden);
+ assert.isOk(element.$$('[data-action-key="' + key + '"]'));
+ changeActions.setActionOverflow(
+ changeActions.ActionType.REVISION, key, true);
+ flush(function() {
+ assert.isNotOk(element.$$('[data-action-key="' + key + '"]'));
+ assert.isFalse(element.$.moreActions.hidden);
+ assert.strictEqual(element.$.moreActions.items[0].name, 'Bork!');
+ done();
+ });
+ });
+ });
+
+ test('change actions priority', function(done) {
+ var key1 = changeActions.add(changeActions.ActionType.REVISION, 'Bork!');
+ var key2 = changeActions.add(changeActions.ActionType.CHANGE, 'Squanch?');
+ flush(function() {
+ var buttons =
+ Polymer.dom(element.root).querySelectorAll('[data-action-key]');
+ assert.equal(buttons[0].getAttribute('data-action-key'), key1);
+ assert.equal(buttons[1].getAttribute('data-action-key'), key2);
+ changeActions.setActionPriority(
+ changeActions.ActionType.REVISION, key1, 10);
+ flush(function() {
+ buttons =
+ Polymer.dom(element.root).querySelectorAll('[data-action-key]');
+ assert.equal(buttons[0].getAttribute('data-action-key'), key2);
+ assert.equal(buttons[1].getAttribute('data-action-key'), key1);
+ done();
+ });
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index e2f63c6..95fa2a7 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -666,6 +666,58 @@
return this.send('POST', url, review, opt_errFn, opt_ctx);
},
+ getFileInChangeEdit: function(changeNum, path) {
+ return this.send('GET',
+ this.getChangeActionURL(changeNum, null,
+ '/edit/' + encodeURIComponent(path)
+ ));
+ },
+
+ rebaseChangeEdit: function(changeNum) {
+ return this.send('POST',
+ this.getChangeActionURL(changeNum, null,
+ '/edit:rebase'
+ ));
+ },
+
+ deleteChangeEdit: function(changeNum) {
+ return this.send('DELETE',
+ this.getChangeActionURL(changeNum, null,
+ '/edit'
+ ));
+ },
+
+ restoreFileInChangeEdit: function(changeNum, restore_path) {
+ return this.send('POST',
+ this.getChangeActionURL(changeNum, null, '/edit'),
+ {restore_path: restore_path}
+ );
+ },
+
+ renameFileInChangeEdit: function(changeNum, old_path, new_path) {
+ return this.send('POST',
+ this.getChangeActionURL(changeNum, null, '/edit'),
+ {old_path: old_path},
+ {new_path: new_path}
+ );
+ },
+
+ deleteFileInChangeEdit: function(changeNum, path) {
+ return this.send('DELETE',
+ this.getChangeActionURL(changeNum, null,
+ '/edit/' + encodeURIComponent(path)
+ ));
+ },
+
+ saveChangeEdit: function(changeNum, path, contents) {
+ return this.send('PUT',
+ this.getChangeActionURL(changeNum, null,
+ '/edit/' + encodeURIComponent(path)
+ ),
+ contents
+ );
+ },
+
saveChangeCommitMessageEdit: function(changeNum, message) {
var url = this.getChangeActionURL(changeNum, null, '/edit:message');
return this.send('PUT', url, {message: message});
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 31a98d9..0ff162d 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -595,5 +595,25 @@
});
});
});
+
+ test('saveChangeEdit', function(done) {
+ var change_num = '1';
+ var file_name = 'index.php';
+ var file_contents = '<?php';
+ sandbox.stub(element, 'send').returns(
+ Promise.resolve([change_num, file_name, file_contents])
+ );
+ sandbox.stub(element, 'getResponseObject')
+ .returns(Promise.resolve([change_num, file_name, file_contents]));
+ element._cache['/changes/' + change_num + '/edit/' + file_name] = {};
+ element.saveChangeEdit(change_num, file_name, file_contents).then(
+ function() {
+ assert.isTrue(element.send.calledWith('PUT',
+ '/changes/' + change_num + '/edit/' + file_name,
+ file_contents));
+ done();
+ }
+ );
+ });
});
</script>
diff --git a/polygerrit-ui/app/wct_test.sh b/polygerrit-ui/app/wct_test.sh
index e6f3e0e..e81adfb 100755
--- a/polygerrit-ui/app/wct_test.sh
+++ b/polygerrit-ui/app/wct_test.sh
@@ -36,10 +36,10 @@
'sauce': {
'disabled': true,
'browsers': [
- 'OS X 10.11/chrome',
+ 'OS X 10.12/chrome',
'Windows 10/chrome',
'Linux/firefox',
- 'OS X 10.11/safari',
+ 'OS X 10.12/safari',
'Windows 10/microsoftedge'
]
}