Merge "PostPrivate: Use BooleanCondition for #setVisible"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 71474f5..1dc43dd 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3286,8 +3286,8 @@
 === Section noteDb
 
 NoteDb is the next generation of Gerrit storage backend, currently powering
-`googlesource.com`. It is not (yet) recommended for general use, but if you want
-to learn more, see the link:dev-note-db.html[developer documentation].
+`googlesource.com`. For more information, including how to migrate your data,
+see the link:note-db.html[documentation].
 
 [[notedb.accounts.sequenceBatchSize]]notedb.accounts.sequenceBatchSize::
 +
@@ -3302,6 +3302,26 @@
 +
 By default, 1.
 
+[[noteDb.retryMaxWait]]noteDb.retryMaxWait::
++
+Maximum time to wait between attempts to retry update operations when one
+attempt fails due to contention (aka lock failure) on the underlying ref
+storage. Operations are retried with exponential backoff, plus some random
+jitter, until the interval reaches this limit. After that, retries continue to
+occur after a fixed timeout (plus jitter), up to
+link:#noteDb.retryTimeout[`noteDb.retryTimeout`].
++
+Defaults to 5 seconds; unit suffixes are supported, and assumes milliseconds if
+not specified.
+
+[[noteDb.retryTimeout]]noteDb.retryTimeout::
++
+Total timeout for retrying update operations when one attempt fails due to
+contention (aka lock failure) on the underlying ref storage.
++
+Defaults to 20 seconds; unit suffixes are supported, and assumes milliseconds if
+not specified.
+
 [[oauth]]
 === Section oauth
 
diff --git a/Documentation/dev-note-db.txt b/Documentation/dev-note-db.txt
deleted file mode 100644
index bacbeda..0000000
--- a/Documentation/dev-note-db.txt
+++ /dev/null
@@ -1,179 +0,0 @@
-= Gerrit Code Review - NoteDb Backend
-
-NoteDb is the next generation of Gerrit storage backend, which replaces the
-traditional SQL backend for change and account metadata with storing data in the
-same repository as code changes.
-
-.Advantages
-- *Simplicity*: All data is stored in one location in the site directory, rather
-  than being split between the site directory and a possibly external database
-  server.
-- *Consistency*: Replication and backups can use a snapshot of the Git
-  repository refs, which will include both the branch and patch set refs, and
-  the change metadata that points to them.
-- *Auditability*: Rather than storing mutable rows in a database, modifications
-  to changes are stored as a sequence of Git commits, automatically preserving
-  history of the metadata. +
-  There are no strict guarantees, and meta refs may be rewritten, but the
-  default assumption is that all operations are logged.
-- *Extensibility*: Plugin developers can add new fields to metadata without the
-  core database schema having to know about them.
-- *New features*: Enables simple federation between Gerrit servers, as well as
-  offline code review and interoperation with other tools.
-
-== Current Status
-
-- Storing change metadata is fully implemented in master, and is live on the
-  servers behind `googlesource.com`. In other words, if you use
-  link:https://gerrit-review.googlesource.com/[gerrit-review], you're already
-  using NoteDb. +
-- Storing some account data, e.g. user preferences, is implemented in releases
-  back to 2.13.
-- Storing the rest of account data is a work in progress.
-- Storing group data is a work in progress.
-
-To use a configuration similar to the current state of `googlesource.com`, paste
-the following config snippet in your `gerrit.config`:
-
-----
-[noteDb "changes"]
-  write = true
-  read = true
-  primaryStorage = NOTE_DB
-  disableReviewDb = true
-----
-
-This is the configuration that will be produced if you enable experimental
-NoteDb support on a new site with `init`.
-
-`googlesource.com` additionally uses `fuseUpdates = true`, because its repo
-backend supports atomic multi-ref transactions. Native JGit doesn't, so setting
-this option on a dev server would fail.
-
-For an example NoteDb change, poke around at this one:
-----
-  git fetch https://gerrit.googlesource.com/gerrit refs/changes/70/98070/meta \
-      && git log -p FETCH_HEAD
-----
-
-== Configuration
-
-Account and group data is migrated to NoteDb automatically using the normal
-schema upgrade process during updates. The remainder of this section details the
-configuration options that control migration of the change data, which is mostly
-but not fully implemented.
-
-Change migration state is configured in `gerrit.config` with options like
-`noteDb.changes.*`. These options are undocumented outside of this file, and the
-general approach has been to add one new option for each phase of the migration.
-Assume that each config option in the following list requires all of the
-previous options, unless otherwise noted.
-
-- `noteDb.changes.write=true`: During a ReviewDb write, the state of the change
-  in NoteDb is written to the `note_db_state` field in the `Change` entity.
-  After the ReviewDb write, this state is written into NoteDb, resulting in
-  effectively double the time for write operations. NoteDb write errors are
-  dropped on the floor, and no attempt is made to read from ReviewDb or correct
-  errors (without additional configuration, below). +
-  This state allows for a rolling update in a multi-master setting, where some
-  servers can start reading from NoteDb, but older servers are still reading
-  only from ReviewDb.
-- `noteDb.changes.read=true`: Change data is written
-  to and read from NoteDb, but ReviewDb is still the source of truth. During
-  reads, first read the change from ReviewDb, and compare its `note_db_state`
-  with what is in NoteDb. If it doesn't match, immediately "auto-rebuild" the
-  change, copying data from ReviewDb to NoteDb and returning the result.
-- `noteDb.changes.primaryStorage=NOTE_DB`: New changes are written only to
-  NoteDb, but changes whose primary storage is ReviewDb are still supported.
-  Continues to read from ReviewDb first as in the previous stage, but if the
-  change is not in ReviewDb, falls back to reading from NoteDb. +
-  Migration of existing changes is described in the link:#migration[Migration]
-  section below. +
-  Due to an implementation detail, writes to Changes or related tables still
-  result in write calls to the database layer, but they are inside a transaction
-  that is always rolled back.
-- `noteDb.changes.disableReviewDb=true`: All access to Changes or related tables
-  is disabled; reads return no results, and writes are no-ops. Assumes the state
-  of all changes in NoteDb is accurate, and so is only safe once all changes are
-  NoteDb primary. Otherwise, reading changes only from NoteDb might result in
-  inaccurate results, and writing to NoteDb would compound the problem. +
-  Thus it is up to an admin of a previously-ReviewDb site to ensure
-  MigratePrimaryStorage has been run for all changes. Note that the current
-  implementation of the `migrate-to-note-db` program does not do this. +
-  In this phase, it would be possible to delete the Changes tables out from
-  under a running server with no effect.
-- `noteDb.changes.fuseUpdates=true`: Code and meta updates within a single
-  repository are fused into a single atomic `BatchRefUpdate`, so they either
-  all succeed or all fail. This mode is only possible on a backend that
-  supports atomic ref updates, which notably excludes the default file repository
-  backend.
-
-[[migration]]
-== Migration
-
-Once configuration options are set, migration to NoteDb is primarily
-accomplished by running the `migrate-to-note-db` program. Currently, this program
-bulk copies ReviewDb data into NoteDb, but leaves primary storage of these
-changes in ReviewDb, so the site is runnable with
-`noteDb.changes.{write,read}=true`, but ReviewDb is still required.
-
-Eventually, `migrate-to-note-db` will set primary storage to NoteDb for all
-changes by default, so a site will be able to stop using ReviewDb for changes
-immediately after a successful run.
-
-There is code in `PrimaryStorageMigrator.java` to migrate individual changes
-from NoteDb primary to ReviewDb primary. This code is not intended to be used
-except in the event of a critical bug in NoteDb primary changes in production.
-It will likely never be used by `migrate-to-note-db`, and in fact it's not
-recommended to run `migrate-to-note-db` until the code is stable enough that the
-reverse migration won't be necessary.
-
-=== Zero-Downtime Multi-Master Migration
-
-Single-master Gerrit sites can use `migrate-to-note-db` on an offline site to
-rebuild NoteDb, but this doesn't work in a zero-downtime environment like
-googlesource.com.
-
-Here, the migration process looks like:
-
-- Turn on `noteDb.changes.write=true` to start writing to NoteDb.
-- Run a parallel link:https://research.google.com/pubs/pub35650.html[FlumeJava]
-  pipeline to write NoteDb data for all changes, and update all `note_db_state`
-  fields. (Sorry, this implementation is entirely closed-source.)
-- Turn on `noteDb.changes.read=true` to start reading from NoteDb.
-- Turn on `noteDb.changes.primaryStorage=NOTE_DB` to start writing new changes
-  to NoteDb only.
-- Run a Flume to migrate all existing changes to NoteDb primary. (Also
-  closed-source, but basically just a wrapper around `PrimaryStorageMigrator`.)
-- Turn off access to ReviewDb changes tables.
-
-== Configuration
-
-This section describes general configuration for the NoteDb backend that is not
-directly related to the migration process. These options will continue to be
-supported after the migration is complete and the migration-related options are
-obsolete.
-
-[[noteDb.retryMaxWait]]noteDb.retryMaxWait::
-+
-Maximum time to wait between attempts to retry update operations when one
-attempt fails due to contention (aka lock failure) on the underlying ref
-storage. Operations are retried with exponential backoff, plus some random
-jitter, until the interval reaches this limit. After that, retries continue to
-occur after a fixed timeout (plus jitter), up to
-link:#noteDb.retryTimeout[`noteDb.retryTimeout`].
-+
-Only applicable when `noteDb.changes.fuseUpdates=true`.
-+
-Defaults to 5 seconds; unit suffixes are supported, and assumes milliseconds if
-not specified.
-
-[[noteDb.retryTimeout]]noteDb.retryTimeout::
-+
-Total timeout for retrying update operations when one attempt fails due to
-contention (aka lock failure) on the underlying ref storage.
-+
-Only applicable when `noteDb.changes.fuseUpdates=true`.
-+
-Defaults to 20 seconds; unit suffixes are supported, and assumes milliseconds if
-not specified.
diff --git a/Documentation/index.txt b/Documentation/index.txt
index e2df077..da211b6 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -66,6 +66,7 @@
 . link:config-reverseproxy.html[Reverse Proxy]
 . link:config-auto-site-initialization.html[Automatic Site Initialization on Startup]
 . link:pgm-index.html[Server Side Administrative Tools]
+. link:note-db.html[NoteDb]
 
 == Developer
 . Getting Started
@@ -82,7 +83,6 @@
 .. link:dev-stars.html[Starring Changes]
 . link:dev-design.html[System Design]
 . link:i18n-readme.html[i18n Support]
-. link:dev-note-db.html[NoteDb]
 
 == Maintainer
 . link:dev-release.html[Making a Gerrit Release]
diff --git a/Documentation/note-db.txt b/Documentation/note-db.txt
new file mode 100644
index 0000000..b926bba
--- /dev/null
+++ b/Documentation/note-db.txt
@@ -0,0 +1,173 @@
+= Gerrit Code Review - NoteDb Backend
+
+NoteDb is the next generation of Gerrit storage backend, which replaces the
+traditional SQL backend for change and account metadata with storing data in the
+same repository as code changes.
+
+.Advantages
+- *Simplicity*: All data is stored in one location in the site directory, rather
+  than being split between the site directory and a possibly external database
+  server.
+- *Consistency*: Replication and backups can use a snapshot of the Git
+  repository refs, which will include both the branch and patch set refs, and
+  the change metadata that points to them.
+- *Auditability*: Rather than storing mutable rows in a database, modifications
+  to changes are stored as a sequence of Git commits, automatically preserving
+  history of the metadata. +
+  There are no strict guarantees, and meta refs may be rewritten, but the
+  default assumption is that all operations are logged.
+- *Extensibility*: Plugin developers can add new fields to metadata without the
+  core database schema having to know about them.
+- *New features*: Enables simple federation between Gerrit servers, as well as
+  offline code review and interoperation with other tools.
+
+== Current Status
+
+- Storing change metadata is fully implemented in the 2.15 release. Admins may
+  use an link:#offline-migration[offline] or link:#online-migration[online] tool
+  to migrate change data from ReviewDb.
+- Storing account data is fully implemented in the 2.15 release. Account data is
+  migrated automatically during the upgrade process by running `gerrit.war
+  init`.
+- Account and change metadata on the servers behind `googlesource.com` is fully
+  migrated to NoteDb. In other words, if you use
+  link:https://gerrit-review.googlesource.com/[gerrit-review], you're already
+  using NoteDb.
+
+For an example NoteDb change, poke around at this one:
+----
+  git fetch https://gerrit.googlesource.com/gerrit refs/changes/70/98070/meta \
+      && git log -p FETCH_HEAD
+----
+
+== Future Work ("Gerrit 3.0")
+
+- Storing group data is a work in progress. Like account data, it will be
+  migrated automatically.
+- NoteDb will be the only database format supported by Gerrit 3.0. The offline
+  change data migration tool will be included in Gerrit 3.0, but online
+  migration will only be available in the 2.x line.
+
+[[migration]]
+== Migration
+
+Migrating change metadata can take a long time for large sites, so
+administrators choose whether to do the migration offline or online, depending
+on their available resources and tolerance for downtime.
+
+Only change metadata requires manual steps to migrate it from ReviewDb; account
+and group data is migrated automatically by `gerrit.war init`.
+
+[[online-migration]]
+=== Online
+
+To start the online migration, set the `noteDb.changes.autoMigrate` option in
+`gerrit.config` and restart Gerrit:
+----
+[noteDb "changes"]
+  autoMigrate = true
+----
+
+Alternatively, pass the `--migrate-to-note-db` flag to
+`gerrit.war daemon`:
+----
+  java -jar gerrit.war daemon -d /path/to/site --migrate-to-note-db
+----
+
+Both ways of starting the online migration are equivalent. Once started, it is
+safe to restart the server at any time; the migration will pick up where it left
+off. Migration progress will be reported to the Gerrit logs.
+
+*Advantages*
+
+* No downtime required.
+
+*Disadvantages*
+
+* Only available in 2.x; will not be available in Gerrit 3.0.
+* Much slower than offline; uses only a single thread, to leave resources
+  available for serving traffic.
+* Performance may be degraded, particularly of updates; data needs to be written
+  to both ReviewDb and NoteDb while the migration is in progress.
+
+[[offline-migration]]
+=== Offline
+
+To run the offline migration, run the `migrate-to-note-db` program:
+----
+  java -jar gerrit.war migrate-to-note-db /path/to/site
+----
+
+Once started, it is safe to cancel and restart the migration process, or to
+switch to the online process.
+
+*Advantages*
+
+* Much faster than online; can use all available CPUs, since no live traffic
+  needs to be served.
+* No degraded performance of live servers due to writing data to 2 locations.
+* Available in both Gerrit 2.x and 3.0.
+
+*Disadvantages*
+
+* May require substantial downtime; takes about twice as long as an
+  link:#pgm-reindex[offline reindex]. (In fact, one of the migration steps is a
+  full reindex, so it can't possibly take less time.)
+
+[[trial-migration]]
+==== Trial mode
+
+The offline migration tool also supports "trial mode", where changes are
+migrated to NoteDb and read from NoteDb at runtime, but their primary storage
+location is still ReviewDb, and data is kept in sync between the two locations.
+
+To run the offline migration in trial mode, add `--trial` to
+`migrate-to-note-db`:
+----
+  java -jar gerrit.war migrate-to-note-db --trial /path/to/site
+----
+
+There are several use cases for trial mode:
+
+* Help test early releases of the migration tool for bugs with lower risk.
+* Try out new NoteDb-only features like
+  link:rest-api-changes.txt#get-hashtags[hashtags] without running the full
+  migration.
+
+To continue with the full migration after running the trial migration, use
+either the online or offline migration steps as normal. To revert to
+ReviewDb-only, remove `noteDb.changes.read` and `noteDb.changes.write` from
+`gerrit.config` and restart Gerrit.
+
+== Configuration
+
+The migration process works by setting a configuration option in `gerrit.config`
+for each step in the process, then performing the corresponding data migration.
+In general, users should not set these options manually; this section serves
+primarily as a reference.
+
+- `noteDb.changes.write=true`: During a ReviewDb write, the state of the change
+  in NoteDb is written to the `note_db_state` field in the `Change` entity.
+  After the ReviewDb write, this state is written into NoteDb, resulting in
+  effectively double the time for write operations. NoteDb write errors are
+  dropped on the floor, and no attempt is made to read from ReviewDb or correct
+  errors (without additional configuration, below).
+- `noteDb.changes.read=true`: Change data is written
+  to and read from NoteDb, but ReviewDb is still the source of truth. During
+  reads, first read the change from ReviewDb, and compare its `note_db_state`
+  with what is in NoteDb. If it doesn't match, immediately "auto-rebuild" the
+  change, copying data from ReviewDb to NoteDb and returning the result.
+- `noteDb.changes.primaryStorage=NOTE_DB`: New changes are written only to
+  NoteDb, but changes whose primary storage is ReviewDb are still supported.
+  Continues to read from ReviewDb first as in the previous stage, but if the
+  change is not in ReviewDb, falls back to reading from NoteDb. +
+  Migration of existing changes is described in the link:#migration[Migration]
+  section above. +
+  Due to an implementation detail, writes to Changes or related tables still
+  result in write calls to the database layer, but they are inside a transaction
+  that is always rolled back.
+- `noteDb.changes.disableReviewDb=true`: All access to Changes or related tables
+  is disabled; reads return no results, and writes are no-ops. Assumes the state
+  of all changes in NoteDb is accurate, and so is only safe once all changes are
+  NoteDb primary. Otherwise, reading changes only from NoteDb might result in
+  inaccurate results, and writing to NoteDb would compound the problem. +
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
index 281e6cd..0517675 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/StandaloneNoteDbMigrationIT.java
@@ -76,7 +76,7 @@
     assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
     setUpOneChange();
 
-    migrate();
+    migrate("--trial");
     assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
 
     try (ServerContext ctx = startServer()) {
@@ -104,7 +104,7 @@
     assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
     setUpOneChange();
 
-    migrate("--trial", "false");
+    migrate();
     assertNotesMigrationState(NotesMigrationState.NOTE_DB);
 
     try (ServerContext ctx = startServer()) {
@@ -142,7 +142,7 @@
     status.save();
     assertServerStartupFails();
 
-    migrate("--trial", "false");
+    migrate();
     assertNotesMigrationState(NotesMigrationState.NOTE_DB);
 
     status = new GerritIndexStatus(sitePaths);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 72920d0..19475ec 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -170,7 +170,7 @@
 
   @Option(
     name = "--migrate-to-note-db",
-    usage = "(EXPERIMENTAL) Automatically migrate changes to NoteDb",
+    usage = "Automatically migrate changes to NoteDb",
     handler = ExplicitBooleanOptionHandler.class
   )
   private boolean migrateToNoteDb;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
index eeebddd..02e779b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
@@ -74,10 +74,9 @@
     name = "--trial",
     usage =
         "trial mode: migrate changes and turn on reading from NoteDb, but leave ReviewDb as"
-            + " the source of truth",
-    handler = ExplicitBooleanOptionHandler.class
+            + " the source of truth"
   )
-  private boolean trial = true; // TODO(dborowitz): Default to false in 3.0.
+  private boolean trial;
 
   @Option(
     name = "--sequence-gap",
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java
index c83f4e4..825cfc0 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitExperimental.java
@@ -58,10 +58,9 @@
 
   private void initNoteDb() {
     ui.message(
-        "Use experimental NoteDb for change metadata?\n"
-            + "  NoteDb is not recommended for production servers."
-            + "  Please familiarize yourself with the documentation:\n"
-            + "  https://gerrit-review.googlesource.com/Documentation/dev-note-db.html\n");
+        "Use NoteDb for change metadata?\n"
+            + "  See documentation:\n"
+            + "  https://gerrit-review.googlesource.com/Documentation/note-db.html\n");
     if (!ui.yesno(false, "Enable")) {
       return;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index 5073e4a..14aa108 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.gerrit.common.TimeUtil;
@@ -191,8 +193,8 @@
         .setLabel("Abandon")
         .setTitle("Abandon the change")
         .setVisible(
-            change.getStatus().isOpen()
-                && change.getStatus() != Change.Status.DRAFT
-                && rsrc.permissions().database(dbProvider).testOrFalse(ChangePermission.ABANDON));
+            and(
+                change.getStatus().isOpen() && change.getStatus() != Change.Status.DRAFT,
+                rsrc.permissions().database(dbProvider).testCond(ChangePermission.ABANDON)));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
index b9b05e8..bc53a7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -100,7 +102,7 @@
     return new UiAction.Description()
         .setLabel("Delete")
         .setTitle("Delete change " + rsrc.getId())
-        .setVisible(couldDeleteWhenIn(status) && perm.testOrFalse(ChangePermission.DELETE));
+        .setVisible(and(couldDeleteWhenIn(status), perm.testCond(ChangePermission.DELETE)));
   }
 
   private boolean couldDeleteWhenIn(Change.Status status) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
index 71c940b..001ef89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivate.java
@@ -14,7 +14,10 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
+
 import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -56,7 +59,7 @@
   protected Response<String> applyImpl(
       BatchUpdate.Factory updateFactory, ChangeResource rsrc, SetPrivateOp.Input input)
       throws RestApiException, UpdateException {
-    if (!canDeletePrivate(rsrc)) {
+    if (!canDeletePrivate(rsrc).value()) {
       throw new AuthException("not allowed to unmark private");
     }
 
@@ -78,9 +81,10 @@
     return Response.none();
   }
 
-  protected boolean canDeletePrivate(ChangeResource rsrc) {
+  protected BooleanCondition canDeletePrivate(ChangeResource rsrc) {
     PermissionBackend.WithUser user = permissionBackend.user(rsrc.getUser());
-    return user.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER)
-        || (rsrc.isUserOwner() && rsrc.getChange().getStatus() != Change.Status.MERGED);
+    return or(
+        rsrc.isUserOwner() && rsrc.getChange().getStatus() != Change.Status.MERGED,
+        user.testCond(GlobalPermission.ADMINISTRATE_SERVER));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java
index 9cf85d1..2de57eb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeletePrivateByPost.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeMessagesUtil;
@@ -39,6 +41,6 @@
     return new UiAction.Description()
         .setLabel("Unmark private")
         .setTitle("Unmark change as private")
-        .setVisible(rsrc.getChange().isPrivate() && canDeletePrivate(rsrc));
+        .setVisible(and(rsrc.getChange().isPrivate(), canDeletePrivate(rsrc)));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
index 5f690ba..10c30b2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 import static com.google.gerrit.server.permissions.ChangePermission.ABANDON;
 import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
 import static com.google.gerrit.server.query.change.ChangeData.asChanges;
@@ -215,11 +216,13 @@
         .setLabel("Move Change")
         .setTitle("Move change to a different branch")
         .setVisible(
-            change.getStatus().isOpen()
-                && permissionBackend
-                    .user(rsrc.getUser())
-                    .ref(change.getDest())
-                    .testOrFalse(CREATE_CHANGE)
-                && rsrc.permissions().database(dbProvider).testOrFalse(ABANDON));
+            and(
+                change.getStatus().isOpen(),
+                and(
+                    permissionBackend
+                        .user(rsrc.getUser())
+                        .ref(change.getDest())
+                        .testCond(CREATE_CHANGE),
+                    rsrc.permissions().database(dbProvider).testCond(ABANDON))));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
index 0c8f010..7ae0abe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostHashtags.java
@@ -67,6 +67,6 @@
   public UiAction.Description getDescription(ChangeResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Hashtags")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_HASHTAGS));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_HASHTAGS));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java
index a0862d6..8b1f4e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutAssignee.java
@@ -123,6 +123,6 @@
   public UiAction.Description getDescription(ChangeResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Assignee")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_ASSIGNEE));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_ASSIGNEE));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
index f2614c3..62742ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
@@ -131,6 +131,6 @@
   public UiAction.Description getDescription(RevisionResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Description")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_DESCRIPTION));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_DESCRIPTION));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 5d9f7b9..1829199 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -128,6 +128,6 @@
   public UiAction.Description getDescription(ChangeResource rsrc) {
     return new UiAction.Description()
         .setLabel("Edit Topic")
-        .setVisible(rsrc.permissions().testOrFalse(ChangePermission.EDIT_TOPIC_NAME));
+        .setVisible(rsrc.permissions().testCond(ChangePermission.EDIT_TOPIC_NAME));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index 6f74ddd..0b8fdfe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+
 import com.google.common.base.Strings;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.RestoreInput;
@@ -156,7 +158,8 @@
         .setLabel("Restore")
         .setTitle("Restore the change")
         .setVisible(
-            rsrc.getChange().getStatus() == Status.ABANDONED
-                && rsrc.permissions().database(dbProvider).testOrFalse(ChangePermission.RESTORE));
+            and(
+                rsrc.getChange().getStatus() == Status.ABANDONED,
+                rsrc.permissions().database(dbProvider).testCond(ChangePermission.RESTORE)));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 53e09d4..b126efd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
 
 import com.google.common.base.Strings;
@@ -240,8 +241,9 @@
         .setLabel("Revert")
         .setTitle("Revert the change")
         .setVisible(
-            change.getStatus() == Change.Status.MERGED
-                && permissionBackend.user(user).ref(change.getDest()).testOrFalse(CREATE_CHANGE));
+            and(
+                change.getStatus() == Change.Status.MERGED,
+                permissionBackend.user(user).ref(change.getDest()).testCond(CREATE_CHANGE)));
   }
 
   private class NotifyOp implements BatchUpdateOp {