Merge "Add the new gerrit systemctl file to init"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 139284c..1e87c5b 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -998,7 +998,7 @@
+
If 0 the update polling is disabled.
+
-Default is 30 seconds.
+Default is 5 minutes.
[[change.allowBlame]]change.allowBlame::
+
diff --git a/Documentation/dev-note-db.txt b/Documentation/dev-note-db.txt
index 0f6873c..e84fecf 100644
--- a/Documentation/dev-note-db.txt
+++ b/Documentation/dev-note-db.txt
@@ -32,8 +32,8 @@
- Storing the rest of account data is a work in progress.
- Storing group data is a work in progress.
-To match the current configuration of `googlesource.com`, paste the following
-config snippet in your `gerrit.config`:
+To use a configuration similar to the current state of `googlesource.com`, paste
+the following config snippet in your `gerrit.config`:
----
[noteDb "changes"]
@@ -46,6 +46,9 @@
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:
----
@@ -99,6 +102,11 @@
implementation of the `rebuild-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
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 2d417c5..63dc54c 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5949,7 +5949,9 @@
|===========================
|Field Name ||Description
|`all` |optional|List of all approvals for this label as a list
-of link:#approval-info[ApprovalInfo] entities.
+of link:#approval-info[ApprovalInfo] entities. Items in this list may
+not represent actual votes cast by users; if a user votes on any label,
+a corresponding ApprovalInfo will appear in this list for all labels.
|`values` |optional|A map of all values that are allowed for this
label. The map maps the values ("`-2`", "`-1`", " `0`", "`+1`", "`+2`")
to the value descriptions.
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index fe2025c..a0e9954 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -141,7 +141,7 @@
[[check-consistency]]
=== Check Consistency
--
-'POST /config/server/check'
+'POST /config/server/check.consistency'
--
Runs consistency checks and returns detected problems.
@@ -152,7 +152,7 @@
.Request
----
- POST /config/server/check HTTP/1.0
+ POST /config/server/check.consistency HTTP/1.0
Content-Type: application/json; charset=UTF-8
{
diff --git a/WORKSPACE b/WORKSPACE
index 2e95e9a..20b8af5 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1068,8 +1068,8 @@
bower_archive(
name = "polymer",
package = "polymer/polymer",
- sha1 = "f2563ed9c8571057814b78d8f6cf275eeb953eeb",
- version = "1.7.1",
+ sha1 = "2c7dd638d55ea91242525139cba18a308b9426d5",
+ version = "1.9.1",
)
bower_archive(
@@ -1098,8 +1098,8 @@
bower_archive(
name = "web-component-tester",
package = "web-component-tester",
- sha1 = "a4a9bc7815a22d143e8f8593e37b3c2028b8c20f",
- version = "5.0.0",
+ sha1 = "4e778f8b7d784ba2a069d83d0cd146125c5c4fcb",
+ version = "5.0.1",
)
# Bower component transitive dependencies.
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 42b1014..d3e084a 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -348,7 +348,6 @@
}
server.getTestInjector().injectMembers(this);
- notesMigration.setFromEnv();
Transport.register(inProcessProtocol);
toClose = Collections.synchronizedList(new ArrayList<Repository>());
admin = accounts.admin();
@@ -528,6 +527,7 @@
server.stop();
server = null;
}
+ notesMigration.resetFromEnv();
}
protected TestRepository<?>.CommitBuilder commitBuilder() throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 0ed5d8d..f99c35a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -896,7 +896,14 @@
PushResult pr =
GitUtil.pushHead(testRepo, "refs/for/foo%base=" + rBase.getCommit().name(), false, false);
- assertThat(pr.getMessages()).contains("changes: new: 1, refs: 1, done");
+
+ if (notesMigration.fuseUpdates()) {
+ // InMemoryRepository's atomic BatchRefUpdate implementation doesn't update the progress
+ // monitor. That's fine, we just care that there was at least one new change and no errors.
+ assertThat(pr.getMessages()).contains("changes: new: 1, done");
+ } else {
+ assertThat(pr.getMessages()).contains("changes: new: 1, refs: 1, done");
+ }
assertTwoChangesWithSameRevision(r);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index ab04ba5..2a4c188 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -240,7 +240,6 @@
@Test
public void uploadPackSubsetOfBranchesVisibleWithEdit() throws Exception {
allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
- deny(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
Change c = notesFactory.createChecked(db, project, c1.getId()).getChange();
String changeId = c.getKey().get();
@@ -265,6 +264,34 @@
}
@Test
+ public void uploadPackSubsetOfBranchesVisibleWithEditForOtherUser() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
+ allow(Permission.VIEW_PRIVATE_CHANGES, REGISTERED_USERS, "refs/*");
+
+ Change c = notesFactory.createChecked(db, project, c1.getId()).getChange();
+ String changeId = c.getKey().get();
+
+ // Admin's edit is visible.
+ setApiUser(admin);
+ gApi.changes().id(changeId).edit().create();
+
+ // User's edit is visible.
+ setApiUser(user);
+ gApi.changes().id(changeId).edit().create();
+
+ assertUploadPackRefs(
+ "HEAD",
+ r1 + "1",
+ r1 + "meta",
+ r3 + "1",
+ r3 + "meta",
+ "refs/heads/master",
+ "refs/tags/master-tag",
+ "refs/users/00/1000000/edit-" + c1.getId() + "/1",
+ "refs/users/01/1000001/edit-" + c1.getId() + "/1");
+ }
+
+ @Test
public void uploadPackSubsetOfRefsVisibleWithAccessDatabase() throws Exception {
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
try {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index f51bbf5..351623a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -168,7 +168,7 @@
assertThat(i.change.largeChange).isEqualTo(500);
assertThat(i.change.replyTooltip).startsWith("Reply and score");
assertThat(i.change.replyLabel).isEqualTo("Reply\u2026");
- assertThat(i.change.updateDelay).isEqualTo(30);
+ assertThat(i.change.updateDelay).isEqualTo(300);
// download
assertThat(i.download.archives).containsExactly("tar", "tbz2", "tgz", "txz");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
index ada17b6..4a5d496 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
@@ -31,7 +31,6 @@
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.RepoContext;
-import com.google.gerrit.testutil.NoteDbMode;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.EnumSet;
@@ -48,11 +47,12 @@
@Before
public void setUp() throws Exception {
- assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.DISABLE_CHANGE_REVIEW_DB);
+ assume().that(notesMigration.disableChangeReviewDb()).isTrue();
}
@Test
public void updateChangeFailureRollsBackRefUpdate() throws Exception {
+ assume().that(notesMigration.fuseUpdates()).isTrue();
PushOneCommit.Result r = createChange();
Change.Id id = r.getChange().getId();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalOrPluginPermission.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/GlobalOrPluginPermission.java
similarity index 84%
rename from gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalOrPluginPermission.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/GlobalOrPluginPermission.java
index d2198d3..deae084 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/GlobalOrPluginPermission.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/GlobalOrPluginPermission.java
@@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.permissions;
+package com.google.gerrit.extensions.api.access;
-/** A {@link GlobalPermission} or a {@link PluginPermission}. */
+/**
+ * A {@link com.google.gerrit.server.permissions.GlobalPermission} or a {@link PluginPermission}.
+ */
public interface GlobalOrPluginPermission {
/** @return name used in {@code project.config} permissions. */
public String permissionName();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PluginPermission.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PluginPermission.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/permissions/PluginPermission.java
rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PluginPermission.java
index 6d503df..33a85cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PluginPermission.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PluginPermission.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.permissions;
+package com.google.gerrit.extensions.api.access;
import static com.google.common.base.Preconditions.checkNotNull;
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java
index c0ece8c..d2e5d49 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/AuthInfo.java
@@ -84,7 +84,8 @@
}
public final boolean isHttpPasswordSettingsEnabled() {
- return gitBasicAuthPolicy() != GitBasicAuthPolicy.LDAP;
+ return gitBasicAuthPolicy() == GitBasicAuthPolicy.HTTP
+ || gitBasicAuthPolicy() == GitBasicAuthPolicy.HTTP_LDAP;
}
public final GitBasicAuthPolicy gitBasicAuthPolicy() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
index bbc192d..a948595 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
@@ -21,6 +21,7 @@
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.MenuScreen;
import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
import java.util.HashSet;
import java.util.Set;
@@ -46,7 +47,8 @@
if (Gerrit.info().auth().isHttpPasswordSettingsEnabled()) {
linkByGerrit(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
}
- if (Gerrit.info().auth().isOAuth()) {
+ if (Gerrit.info().auth().isOAuth()
+ && Gerrit.info().auth().gitBasicAuthPolicy() == GitBasicAuthPolicy.OAUTH) {
linkByGerrit(Util.C.tabOAuthToken(), PageLinks.SETTINGS_OAUTH_TOKEN);
}
if (Gerrit.info().gerrit().editGpgKeys()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index 0c176e8..1e29be8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -1564,6 +1564,10 @@
nm = JsArray.createArray().cast();
}
+ if (om.length() == nm.length()) {
+ return;
+ }
+
if (updateAvailable == null) {
updateAvailable =
new UpdateAvailableBar() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
index 4327c07..5ff300d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectMap.java
@@ -44,9 +44,9 @@
.get(NativeMap.copyKeysIntoChildren(callback));
}
- public static void suggest(String prefix, int limit, AsyncCallback<ProjectMap> cb) {
+ public static void suggest(String match, int limit, AsyncCallback<ProjectMap> cb) {
new RestApi("/projects/")
- .addParameter("p", prefix)
+ .addParameter("m", match)
.addParameter("n", limit)
.addParameterRaw("type", "ALL")
.addParameterTrue("d") // description
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
index 87cfea1..96c70c0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
@@ -16,6 +16,7 @@
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
@@ -28,6 +29,9 @@
import com.google.gerrit.server.events.ProjectEvent;
import com.google.gerrit.server.events.RefEvent;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
@@ -54,6 +58,7 @@
/** Listeners to receive all changes as they happen. */
protected final DynamicSet<EventListener> unrestrictedListeners;
+ private final PermissionBackend permissionBackend;
protected final ProjectCache projectCache;
protected final ChangeNotes.Factory notesFactory;
@@ -64,11 +69,13 @@
public EventBroker(
DynamicSet<UserScopedEventListener> listeners,
DynamicSet<EventListener> unrestrictedListeners,
+ PermissionBackend permissionBackend,
ProjectCache projectCache,
ChangeNotes.Factory notesFactory,
Provider<ReviewDb> dbProvider) {
this.listeners = listeners;
this.unrestrictedListeners = unrestrictedListeners;
+ this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
this.notesFactory = notesFactory;
this.dbProvider = dbProvider;
@@ -137,11 +144,12 @@
}
protected boolean isVisibleTo(Project.NameKey project, CurrentUser user) {
- ProjectState pe = projectCache.get(project);
- if (pe == null) {
+ try {
+ permissionBackend.user(user).project(project).check(ProjectPermission.ACCESS);
+ return true;
+ } catch (AuthException | PermissionBackendException e) {
return false;
}
- return pe.controlFor(user).isVisible();
}
protected boolean isVisibleTo(Change change, CurrentUser user) throws OrmException {
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 545c57f..08eecd7 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,6 +14,8 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
@@ -23,11 +25,9 @@
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;
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 2bba536..01e16d7 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
@@ -22,14 +22,14 @@
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.server.CurrentUser;
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.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
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 5276e8d..f519b6c 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
@@ -19,6 +19,8 @@
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -29,11 +31,9 @@
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;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/AccountPatchReviewStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/AccountPatchReviewStore.java
index 944b2cb..b7a6e82 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/AccountPatchReviewStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/AccountPatchReviewStore.java
@@ -14,10 +14,13 @@
package com.google.gerrit.server.change;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwtorm.server.OrmException;
import java.util.Collection;
+import java.util.Optional;
/**
* Store for reviewed flags on changes.
@@ -30,6 +33,19 @@
* <p>For a multi-master setup the store must replicate the data between the masters.
*/
public interface AccountPatchReviewStore {
+
+ /** Represents patch set id with reviewed files. */
+ @AutoValue
+ abstract class PatchSetWithReviewedFiles {
+ abstract PatchSet.Id patchSetId();
+
+ abstract ImmutableSet<String> files();
+
+ public static PatchSetWithReviewedFiles create(PatchSet.Id id, ImmutableSet<String> files) {
+ return new AutoValue_AccountPatchReviewStore_PatchSetWithReviewedFiles(id, files);
+ }
+ }
+
/**
* Marks the given file in the given patch set as reviewed by the given user.
*
@@ -72,12 +88,15 @@
void clearReviewed(PatchSet.Id psId) throws OrmException;
/**
- * Returns the paths of all files in the given patch set the have been reviewed by the given user.
+ * Find the latest patch set, that is smaller or equals to the given patch set, where at least,
+ * one file has been reviewed by the given user.
*
* @param psId patch set ID
* @param accountId account ID of the user
- * @return the paths of all files in the given patch set the have been reviewed by the given user
+ * @return optionally, all files the have been reviewed by the given user that belong to the patch
+ * set that is smaller or equals to the given patch set
* @throws OrmException thrown if accessing the reviewed flags failed
*/
- Collection<String> findReviewed(PatchSet.Id psId, Account.Id accountId) throws OrmException;
+ Optional<PatchSetWithReviewedFiles> findReviewed(PatchSet.Id psId, Account.Id accountId)
+ throws OrmException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index b7bd75a7..ebc9971 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.change;
import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -34,6 +33,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.change.AccountPatchReviewStore.PatchSetWithReviewedFiles;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
@@ -45,8 +45,10 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@@ -222,31 +224,24 @@
}
Account.Id userId = user.getAccountId();
- Collection<String> r =
- accountPatchReviewStore.get().findReviewed(resource.getPatchSet().getId(), userId);
+ PatchSet patchSetId = resource.getPatchSet();
+ Optional<PatchSetWithReviewedFiles> o =
+ accountPatchReviewStore.get().findReviewed(patchSetId.getId(), userId);
- if (r.isEmpty() && 1 < resource.getPatchSet().getPatchSetId()) {
- for (PatchSet ps : reversePatchSets(resource)) {
- Collection<String> o = accountPatchReviewStore.get().findReviewed(ps.getId(), userId);
- if (!o.isEmpty()) {
- try {
- r = copy(Sets.newHashSet(o), ps.getId(), resource, userId);
- } catch (IOException | PatchListNotAvailableException e) {
- log.warn("Cannot copy patch review flags", e);
- }
- break;
- }
+ if (o.isPresent()) {
+ PatchSetWithReviewedFiles res = o.get();
+ if (res.patchSetId().equals(patchSetId.getId())) {
+ return res.files();
+ }
+
+ try {
+ return copy(res.files(), res.patchSetId(), resource, userId);
+ } catch (IOException | PatchListNotAvailableException e) {
+ log.warn("Cannot copy patch review flags", e);
}
}
- return r;
- }
-
- private List<PatchSet> reversePatchSets(RevisionResource resource) throws OrmException {
- Collection<PatchSet> patchSets = psUtil.byChange(db.get(), resource.getNotes());
- List<PatchSet> list =
- (patchSets instanceof List) ? (List<PatchSet>) patchSets : new ArrayList<>(patchSets);
- return Lists.reverse(list);
+ return Collections.emptyList();
}
private List<String> copy(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index 5c09055..7450b32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -216,7 +216,7 @@
info.replyLabel =
Optional.ofNullable(cfg.getString("change", null, "replyLabel")).orElse("Reply") + "\u2026";
info.updateDelay =
- (int) ConfigUtil.getTimeUnit(cfg, "change", null, "updateDelay", 30, TimeUnit.SECONDS);
+ (int) ConfigUtil.getTimeUnit(cfg, "change", null, "updateDelay", 300, TimeUnit.SECONDS);
info.submitWholeTopic = Submit.wholeTopicEnabled(cfg);
return info;
}
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 612fea2..7bf5ad5 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,7 +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);
+ post(CONFIG_KIND, "check.consistency").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/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
index bd5d6a4..ae15cfd 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
@@ -17,6 +17,7 @@
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestResource;
@@ -24,7 +25,6 @@
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.permissions.GlobalOrPluginPermission;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 73d5383..3aaca53 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -476,7 +476,7 @@
// If the user lacks READ permission, some references may be filtered and hidden from view.
// Check objects mentioned inside the incoming pack file are reachable from visible refs.
try {
- permissions.check(ProjectPermission.READ);
+ permissionBackend.user(user).project(project.getNameKey()).check(ProjectPermission.READ);
} catch (AuthException e) {
rp.setCheckReferencedObjectsAreReachable(receiveConfig.checkReferencedObjectsAreReachable);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index 47d416c..b58fb55 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -35,10 +36,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
@@ -62,7 +61,7 @@
private final ReviewDb reviewDb;
private final boolean showMetadata;
private String userEditPrefix;
- private Set<Change.Id> visibleChanges;
+ private Map<Change.Id, Branch.NameKey> visibleChanges;
public VisibleRefFilter(
TagCache tagCache,
@@ -221,26 +220,29 @@
visibleChanges = visibleChangesBySearch();
}
}
- return visibleChanges.contains(changeId);
+ return visibleChanges.containsKey(changeId);
}
private boolean visibleEdit(String name) {
- if (userEditPrefix != null && name.startsWith(userEditPrefix)) {
- Change.Id id = Change.Id.fromEditRefPart(name);
- if (id != null) {
- return visible(id);
- }
+ Change.Id id = Change.Id.fromEditRefPart(name);
+ // Initialize if it wasn't yet
+ if (visibleChanges == null) {
+ visible(id);
+ }
+ if (id != null) {
+ return (userEditPrefix != null && name.startsWith(userEditPrefix) && visible(id))
+ || projectCtl.controlForRef(visibleChanges.get(id)).isEditVisible();
}
return false;
}
- private Set<Change.Id> visibleChangesBySearch() {
+ private Map<Change.Id, Branch.NameKey> visibleChangesBySearch() {
Project project = projectCtl.getProject();
try {
- Set<Change.Id> visibleChanges = new HashSet<>();
+ Map<Change.Id, Branch.NameKey> visibleChanges = new HashMap<>();
for (ChangeData cd : changeCache.getChangeData(reviewDb, project.getNameKey())) {
if (projectCtl.controlForIndexedChange(cd.change()).isVisible(reviewDb, cd)) {
- visibleChanges.add(cd.getId());
+ visibleChanges.put(cd.getId(), cd.change().getDest());
}
}
return visibleChanges;
@@ -250,24 +252,24 @@
+ project.getName()
+ ", assuming no changes are visible",
e);
- return Collections.emptySet();
+ return Collections.emptyMap();
}
}
- private Set<Change.Id> visibleChangesByScan() {
+ private Map<Change.Id, Branch.NameKey> visibleChangesByScan() {
Project.NameKey project = projectCtl.getProject().getNameKey();
try {
- Set<Change.Id> visibleChanges = new HashSet<>();
+ Map<Change.Id, Branch.NameKey> visibleChanges = new HashMap<>();
for (ChangeNotes cn : changeNotesFactory.scan(db, reviewDb, project)) {
if (projectCtl.controlFor(cn).isVisible(reviewDb)) {
- visibleChanges.add(cn.getChangeId());
+ visibleChanges.put(cn.getChangeId(), cn.getChange().getDest());
}
}
return visibleChanges;
} catch (IOException | OrmException e) {
log.error(
"Cannot load changes for project " + project + ", assuming no changes are visible", e);
- return Collections.emptySet();
+ return Collections.emptyMap();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ConfigNotesMigration.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ConfigNotesMigration.java
index 6ee3dfe..cd00149 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ConfigNotesMigration.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ConfigNotesMigration.java
@@ -17,13 +17,11 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
-import com.google.common.collect.ImmutableSet;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.util.Set;
import org.eclipse.jgit.lib.Config;
/**
@@ -46,30 +44,13 @@
public static final String SECTION_NOTE_DB = "noteDb";
- // All of these names must be reflected in the allowed set in checkConfig.
private static final String DISABLE_REVIEW_DB = "disableReviewDb";
+ private static final String FUSE_UPDATES = "fuseUpdates";
private static final String PRIMARY_STORAGE = "primaryStorage";
private static final String READ = "read";
private static final String SEQUENCE = "sequence";
private static final String WRITE = "write";
- private static void checkConfig(Config cfg) {
- Set<String> keys = ImmutableSet.of(CHANGES.key());
- Set<String> allowed =
- ImmutableSet.of(
- DISABLE_REVIEW_DB.toLowerCase(),
- PRIMARY_STORAGE.toLowerCase(),
- READ.toLowerCase(),
- WRITE.toLowerCase(),
- SEQUENCE.toLowerCase());
- for (String t : cfg.getSubsections(SECTION_NOTE_DB)) {
- checkArgument(keys.contains(t.toLowerCase()), "invalid NoteDb table: %s", t);
- for (String key : cfg.getNames(SECTION_NOTE_DB, t)) {
- checkArgument(allowed.contains(key.toLowerCase()), "invalid NoteDb key: %s.%s", t, key);
- }
- }
- }
-
public static Config allEnabledConfig() {
Config cfg = new Config();
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), WRITE, true);
@@ -77,6 +58,8 @@
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), SEQUENCE, true);
cfg.setString(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.NOTE_DB.name());
cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, true);
+ // TODO(dborowitz): Set to true when FileRepository supports it.
+ cfg.setBoolean(SECTION_NOTE_DB, CHANGES.key(), FUSE_UPDATES, false);
return cfg;
}
@@ -85,11 +68,10 @@
private final boolean readChangeSequence;
private final PrimaryStorage changePrimaryStorage;
private final boolean disableChangeReviewDb;
+ private final boolean fuseUpdates;
@Inject
public ConfigNotesMigration(@GerritServerConfig Config cfg) {
- checkConfig(cfg);
-
writeChanges = cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), WRITE, false);
readChanges = cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), READ, false);
@@ -103,6 +85,7 @@
cfg.getEnum(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.REVIEW_DB);
disableChangeReviewDb =
cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, false);
+ fuseUpdates = cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), FUSE_UPDATES, false);
checkArgument(
!(disableChangeReviewDb && changePrimaryStorage != PrimaryStorage.NOTE_DB),
@@ -133,4 +116,9 @@
public boolean disableChangeReviewDb() {
return disableChangeReviewDb;
}
+
+ @Override
+ public boolean fuseUpdates() {
+ return fuseUpdates;
+ }
}
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 4ecd684..83193d5 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
@@ -22,6 +22,7 @@
import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
@@ -53,6 +54,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
@@ -516,21 +518,53 @@
if (!dryrun) {
bru.execute(or.rw, NullProgressMonitor.INSTANCE);
- boolean lockFailure = false;
- for (ReceiveCommand cmd : bru.getCommands()) {
- if (cmd.getResult() == ReceiveCommand.Result.LOCK_FAILURE) {
- lockFailure = true;
- } else if (cmd.getResult() != ReceiveCommand.Result.OK) {
- throw new IOException("Update failed: " + bru);
- }
- }
- if (lockFailure) {
- throw new LockFailureException("Update failed with one or more lock failures: " + bru);
- }
+ checkResults(bru);
}
return bru;
}
+ /**
+ * Check results of all commands in the update batch, reducing to a single exception if there was
+ * a failure.
+ *
+ * <p>Throws {@link LockFailureException} if at least one command failed with {@code
+ * LOCK_FAILURE}, and the entire transaction was aborted, i.e. any non-{@code LOCK_FAILURE}
+ * results, if there were any, failed with "transaction aborted".
+ *
+ * <p>In particular, if the underlying ref database does not {@link
+ * org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions() perform atomic transactions},
+ * then a combination of {@code LOCK_FAILURE} on one ref and {@code OK} or another result on other
+ * refs will <em>not</em> throw {@code LockFailureException}.
+ *
+ * @param bru batch update; should already have been executed.
+ * @throws LockFailureException if the transaction was aborted due to lock failure.
+ * @throws IOException if any result was not {@code OK}.
+ */
+ @VisibleForTesting
+ static void checkResults(BatchRefUpdate bru) throws LockFailureException, IOException {
+ int lockFailure = 0;
+ int aborted = 0;
+ int failure = 0;
+
+ for (ReceiveCommand cmd : bru.getCommands()) {
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ failure++;
+ }
+ if (cmd.getResult() == ReceiveCommand.Result.LOCK_FAILURE) {
+ lockFailure++;
+ } else if (cmd.getResult() == ReceiveCommand.Result.REJECTED_OTHER_REASON
+ && JGitText.get().transactionAborted.equals(cmd.getMessage())) {
+ aborted++;
+ }
+ }
+
+ if (lockFailure + aborted == bru.getCommands().size()) {
+ throw new LockFailureException("Update aborted with one or more lock failures: " + bru);
+ } else if (failure > 0) {
+ throw new IOException("Update failed: " + bru);
+ }
+ }
+
private void addCommands() throws OrmException, IOException {
if (isEmpty()) {
return;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
index 995847f..7b0d76f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -83,6 +83,19 @@
public abstract boolean disableChangeReviewDb();
/**
+ * Fuse meta ref updates in the same batch as code updates.
+ *
+ * <p>When set, each {@link com.google.gerrit.server.update.BatchUpdate} results in a single
+ * {@link org.eclipse.jgit.lib.BatchRefUpdate} to update both code and meta refs atomically.
+ * Setting this option with a repository backend that does not support atomic multi-ref
+ * transactions ({@link org.eclipse.jgit.lib.RefDatabase#performsAtomicTransactions()}) is a
+ * configuration error, and all updates will fail at runtime.
+ *
+ * <p>Has no effect if {@link #disableChangeReviewDb()} is false.
+ */
+ public abstract boolean fuseUpdates();
+
+ /**
* Whether to fail when reading any data from NoteDb.
*
* <p>Used in conjunction with {@link #readChanges()} for tests.
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 4111253..926057b 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
@@ -20,6 +20,8 @@
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.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.api.access.PluginPermission;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
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 f7c373e..156f106 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
@@ -19,6 +19,7 @@
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
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
index 3908b72..daee9c7 100644
--- 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
@@ -21,7 +21,7 @@
import java.util.Enumeration;
public class DelegatingClassLoader extends ClassLoader {
- public ClassLoader target;
+ private final ClassLoader target;
public DelegatingClassLoader(ClassLoader parent, ClassLoader target) {
super(parent);
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 feaaccc..0e62c3a 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
@@ -17,11 +17,11 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Sets;
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
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.GlobalOrPluginPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteRef.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteRef.java
index 373f561..78a78c2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteRef.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteRef.java
@@ -170,6 +170,7 @@
private void deleteMultipleRefs(Repository r)
throws OrmException, IOException, ResourceConflictException {
BatchRefUpdate batchUpdate = r.getRefDatabase().newBatchUpdate();
+ batchUpdate.setAtomic(false);
List<String> refs =
prefix == null
? refsToDelete
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index d4c5edc..916637f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -76,9 +76,6 @@
/** Access control management for a user accessing a project's data. */
public class ProjectControl {
- public static final int VISIBLE = 1 << 0;
- public static final int OWNER = 1 << 1;
-
private static final Logger log = LoggerFactory.getLogger(ProjectControl.class);
public static class GenericFactory {
@@ -97,18 +94,6 @@
}
return p.controlFor(user);
}
-
- public ProjectControl validateFor(Project.NameKey nameKey, int need, CurrentUser user)
- throws NoSuchProjectException, IOException {
- final ProjectControl c = controlFor(nameKey, user);
- if ((need & VISIBLE) == VISIBLE && c.isVisible()) {
- return c;
- }
- if ((need & OWNER) == OWNER && c.isOwner()) {
- return c;
- }
- throw new NoSuchProjectException(nameKey);
- }
}
public static class Factory {
@@ -122,26 +107,6 @@
public ProjectControl controlFor(final Project.NameKey nameKey) throws NoSuchProjectException {
return userCache.get().get(nameKey);
}
-
- public ProjectControl validateFor(final Project.NameKey nameKey) throws NoSuchProjectException {
- return validateFor(nameKey, VISIBLE);
- }
-
- public ProjectControl ownerFor(final Project.NameKey nameKey) throws NoSuchProjectException {
- return validateFor(nameKey, OWNER);
- }
-
- public ProjectControl validateFor(final Project.NameKey nameKey, final int need)
- throws NoSuchProjectException {
- final ProjectControl c = controlFor(nameKey);
- if ((need & VISIBLE) == VISIBLE && c.isVisible()) {
- return c;
- }
- if ((need & OWNER) == OWNER && c.isOwner()) {
- return c;
- }
- throw new NoSuchProjectException(nameKey);
- }
}
public interface AssistedFactory {
@@ -280,21 +245,6 @@
return getProject().getState().equals(com.google.gerrit.extensions.client.ProjectState.HIDDEN);
}
- /**
- * Returns whether the project is readable to the current user. Note that the project could still
- * be hidden.
- */
- public boolean isReadable() {
- return (user.isInternalUser() || canPerformOnAnyRef(Permission.READ));
- }
-
- /**
- * Returns whether the project is accessible to the current user, i.e. readable and not hidden.
- */
- public boolean isVisible() {
- return isReadable() && !isHidden();
- }
-
public boolean canAddRefs() {
return (canPerformOnAnyRef(Permission.CREATE) || isOwnerAnyRef());
}
@@ -312,16 +262,11 @@
return false;
}
- /** Can this user see all the refs in this projects? */
- public boolean allRefsAreVisible() {
- return allRefsAreVisible(Collections.<String>emptySet());
- }
-
public boolean allRefsAreVisible(Set<String> ignore) {
return user.isInternalUser() || canPerformOnAllRefs(Permission.READ, ignore);
}
- /** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
+ /** Is this user a project owner? */
public boolean isOwner() {
return (isDeclaredOwner() && !controlForRef("refs/*").isBlocked(Permission.OWNER))
|| user.getCapabilities().isAdmin_DoNotUse();
@@ -609,10 +554,11 @@
private boolean can(ProjectPermission perm) throws PermissionBackendException {
switch (perm) {
case ACCESS:
- return (!isHidden() && isReadable()) || isOwner();
+ return (!isHidden() && (user.isInternalUser() || canPerformOnAnyRef(Permission.READ)))
+ || isOwner();
case READ:
- return (!isHidden() && allRefsAreVisible()) || isOwner();
+ return (!isHidden() && allRefsAreVisible(Collections.emptySet())) || isOwner();
}
throw new PermissionBackendException(perm + " unsupported");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 0345e7a..812872c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -121,6 +121,11 @@
return isVisible;
}
+ /** Can this user see other users change edits? */
+ public boolean isEditVisible() {
+ return canViewPrivateChanges();
+ }
+
/** True if this reference is visible by all REGISTERED_USERS */
public boolean isVisibleByRegisteredUsers() {
List<PermissionRule> access = relevant.getPermission(Permission.READ);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
index 2dd84fc..822ed6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/H2AccountPatchReviewStore.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.schema;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicItem;
@@ -34,9 +35,8 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
+import java.util.Optional;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.eclipse.jgit.lib.Config;
@@ -130,20 +130,20 @@
private static void doCreateTable(Statement stmt) throws SQLException {
stmt.executeUpdate(
- "CREATE TABLE IF NOT EXISTS ACCOUNT_PATCH_REVIEWS ("
- + "ACCOUNT_ID INTEGER DEFAULT 0 NOT NULL, "
- + "CHANGE_ID INTEGER DEFAULT 0 NOT NULL, "
- + "PATCH_SET_ID INTEGER DEFAULT 0 NOT NULL, "
- + "FILE_NAME VARCHAR(255) DEFAULT '' NOT NULL, "
- + "CONSTRAINT PRIMARY_KEY_ACCOUNT_PATCH_REVIEWS "
- + "PRIMARY KEY (ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME)"
+ "CREATE TABLE IF NOT EXISTS account_patch_reviews ("
+ + "account_id INTEGER DEFAULT 0 NOT NULL, "
+ + "change_id INTEGER DEFAULT 0 NOT NULL, "
+ + "patch_set_id INTEGER DEFAULT 0 NOT NULL, "
+ + "file_name VARCHAR(4096) DEFAULT '' NOT NULL, "
+ + "CONSTRAINT primary_key_account_patch_reviews "
+ + "PRIMARY KEY (account_id, change_id, patch_set_id, file_name)"
+ ")");
}
public static void dropTableIfExists(String url) throws OrmException {
try (Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement()) {
- stmt.executeUpdate("DROP TABLE IF EXISTS ACCOUNT_PATCH_REVIEWS");
+ stmt.executeUpdate("DROP TABLE IF EXISTS account_patch_reviews");
} catch (SQLException e) {
throw convertError("create", e);
}
@@ -158,8 +158,8 @@
try (Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
- "INSERT INTO ACCOUNT_PATCH_REVIEWS "
- + "(ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME) VALUES "
+ "INSERT INTO account_patch_reviews "
+ + "(account_id, change_id, patch_set_id, file_name) VALUES "
+ "(?, ?, ?, ?)")) {
stmt.setInt(1, accountId.get());
stmt.setInt(2, psId.getParentKey().get());
@@ -186,8 +186,8 @@
try (Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
- "INSERT INTO ACCOUNT_PATCH_REVIEWS "
- + "(ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME) VALUES "
+ "INSERT INTO account_patch_reviews "
+ + "(account_id, change_id, patch_set_id, file_name) VALUES "
+ "(?, ?, ?, ?)")) {
for (String path : paths) {
stmt.setInt(1, accountId.get());
@@ -212,9 +212,9 @@
try (Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
- "DELETE FROM ACCOUNT_PATCH_REVIEWS "
- + "WHERE ACCOUNT_ID = ? AND CHANGE_ID + ? AND "
- + "PATCH_SET_ID = ? AND FILE_NAME = ?")) {
+ "DELETE FROM account_patch_reviews "
+ + "WHERE account_id = ? AND change_id = ? AND "
+ + "patch_set_id = ? AND file_name = ?")) {
stmt.setInt(1, accountId.get());
stmt.setInt(2, psId.getParentKey().get());
stmt.setInt(3, psId.get());
@@ -230,8 +230,8 @@
try (Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
- "DELETE FROM ACCOUNT_PATCH_REVIEWS "
- + "WHERE CHANGE_ID + ? AND PATCH_SET_ID = ?")) {
+ "DELETE FROM account_patch_reviews "
+ + "WHERE change_id = ? AND patch_set_id = ?")) {
stmt.setInt(1, psId.getParentKey().get());
stmt.setInt(2, psId.get());
stmt.executeUpdate();
@@ -241,22 +241,33 @@
}
@Override
- public Collection<String> findReviewed(PatchSet.Id psId, Account.Id accountId)
+ public Optional<PatchSetWithReviewedFiles> findReviewed(PatchSet.Id psId, Account.Id accountId)
throws OrmException {
try (Connection con = ds.getConnection();
PreparedStatement stmt =
con.prepareStatement(
- "SELECT FILE_NAME FROM ACCOUNT_PATCH_REVIEWS "
- + "WHERE ACCOUNT_ID = ? AND CHANGE_ID = ? AND PATCH_SET_ID = ?")) {
+ "SELECT patch_set_id, file_name FROM account_patch_reviews APR1 "
+ + "WHERE account_id = ? AND change_id = ? AND patch_set_id = "
+ + "(SELECT MAX(patch_set_id) FROM account_patch_reviews APR2 WHERE "
+ + "APR1.account_id = APR2.account_id "
+ + "AND APR1.change_id = APR2.change_id "
+ + "AND patch_set_id <= ?)")) {
stmt.setInt(1, accountId.get());
stmt.setInt(2, psId.getParentKey().get());
stmt.setInt(3, psId.get());
try (ResultSet rs = stmt.executeQuery()) {
- List<String> files = new ArrayList<>();
- while (rs.next()) {
- files.add(rs.getString("FILE_NAME"));
+ if (rs.next()) {
+ PatchSet.Id id = new PatchSet.Id(psId.getParentKey(), rs.getInt("PATCH_SET_ID"));
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ do {
+ builder.add(rs.getString("FILE_NAME"));
+ } while (rs.next());
+
+ return Optional.of(
+ AccountPatchReviewStore.PatchSetWithReviewedFiles.create(id, builder.build()));
}
- return files;
+
+ return Optional.empty();
}
} catch (SQLException e) {
throw convertError("select", e);
@@ -267,13 +278,13 @@
switch (getSQLStateInt(err)) {
case 23001: // UNIQUE CONSTRAINT VIOLATION
case 23505: // DUPLICATE_KEY_1
- return new OrmDuplicateKeyException("ACCOUNT_PATCH_REVIEWS", err);
+ return new OrmDuplicateKeyException("account_patch_reviews", err);
default:
if (err.getCause() == null && err.getNextException() != null) {
err.initCause(err.getNextException());
}
- return new OrmException(op + " failure on ACCOUNT_PATCH_REVIEWS", err);
+ return new OrmException(op + " failure on account_patch_reviews", err);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java
index 03a7c37..22329fd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -94,7 +94,8 @@
@Override
public void configure() {
factory(ReviewDbBatchUpdate.AssistedFactory.class);
- factory(NoteDbBatchUpdate.AssistedFactory.class);
+ factory(FusedNoteDbBatchUpdate.AssistedFactory.class);
+ factory(UnfusedNoteDbBatchUpdate.AssistedFactory.class);
}
};
}
@@ -103,22 +104,28 @@
public static class Factory {
private final NotesMigration migration;
private final ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory;
- private final NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory;
+ private final FusedNoteDbBatchUpdate.AssistedFactory fusedNoteDbBatchUpdateFactory;
+ private final UnfusedNoteDbBatchUpdate.AssistedFactory unfusedNoteDbBatchUpdateFactory;
@Inject
Factory(
NotesMigration migration,
ReviewDbBatchUpdate.AssistedFactory reviewDbBatchUpdateFactory,
- NoteDbBatchUpdate.AssistedFactory noteDbBatchUpdateFactory) {
+ FusedNoteDbBatchUpdate.AssistedFactory fusedNoteDbBatchUpdateFactory,
+ UnfusedNoteDbBatchUpdate.AssistedFactory unfusedNoteDbBatchUpdateFactory) {
this.migration = migration;
this.reviewDbBatchUpdateFactory = reviewDbBatchUpdateFactory;
- this.noteDbBatchUpdateFactory = noteDbBatchUpdateFactory;
+ this.fusedNoteDbBatchUpdateFactory = fusedNoteDbBatchUpdateFactory;
+ this.unfusedNoteDbBatchUpdateFactory = unfusedNoteDbBatchUpdateFactory;
}
public BatchUpdate create(
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when) {
if (migration.disableChangeReviewDb()) {
- return noteDbBatchUpdateFactory.create(db, project, user, when);
+ if (migration.fuseUpdates()) {
+ return fusedNoteDbBatchUpdateFactory.create(db, project, user, when);
+ }
+ return unfusedNoteDbBatchUpdateFactory.create(db, project, user, when);
}
return reviewDbBatchUpdateFactory.create(db, project, user, when);
}
@@ -138,9 +145,15 @@
// copy them into an ImmutableList so there is no chance the callee can pollute the input
// collection.
if (migration.disableChangeReviewDb()) {
- ImmutableList<NoteDbBatchUpdate> noteDbUpdates =
- (ImmutableList) ImmutableList.copyOf(updates);
- NoteDbBatchUpdate.execute(noteDbUpdates, listener, requestId, dryRun);
+ if (migration.fuseUpdates()) {
+ ImmutableList<FusedNoteDbBatchUpdate> noteDbUpdates =
+ (ImmutableList) ImmutableList.copyOf(updates);
+ FusedNoteDbBatchUpdate.execute(noteDbUpdates, listener, requestId, dryRun);
+ } else {
+ ImmutableList<UnfusedNoteDbBatchUpdate> noteDbUpdates =
+ (ImmutableList) ImmutableList.copyOf(updates);
+ UnfusedNoteDbBatchUpdate.execute(noteDbUpdates, listener, requestId, dryRun);
+ }
} else {
ImmutableList<ReviewDbBatchUpdate> reviewDbUpdates =
(ImmutableList) ImmutableList.copyOf(updates);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/FusedNoteDbBatchUpdate.java
similarity index 92%
rename from gerrit-server/src/main/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/update/FusedNoteDbBatchUpdate.java
index f7988cf..ad758484 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/FusedNoteDbBatchUpdate.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static java.util.Comparator.comparing;
import com.google.common.base.Throwables;
@@ -51,23 +52,25 @@
import java.util.TreeMap;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
/**
- * {@link BatchUpdate} implementation that only supports NoteDb.
+ * {@link BatchUpdate} implementation using only NoteDb that updates code refs and meta refs in a
+ * single {@link org.eclipse.jgit.lib.BatchRefUpdate}.
*
* <p>Used when {@code noteDb.changes.disableReviewDb=true}, at which point ReviewDb is not
* consulted during updates.
*/
-class NoteDbBatchUpdate extends BatchUpdate {
+class FusedNoteDbBatchUpdate extends BatchUpdate {
interface AssistedFactory {
- NoteDbBatchUpdate create(
+ FusedNoteDbBatchUpdate create(
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when);
}
static void execute(
- ImmutableList<NoteDbBatchUpdate> updates,
+ ImmutableList<FusedNoteDbBatchUpdate> updates,
BatchUpdateListener listener,
@Nullable RequestId requestId,
boolean dryrun)
@@ -84,11 +87,11 @@
try {
switch (order) {
case REPO_BEFORE_DB:
- for (NoteDbBatchUpdate u : updates) {
+ for (FusedNoteDbBatchUpdate u : updates) {
u.executeUpdateRepo();
}
listener.afterUpdateRepos();
- for (NoteDbBatchUpdate u : updates) {
+ for (FusedNoteDbBatchUpdate u : updates) {
handles.add(u.executeChangeOps(dryrun));
}
for (ChangesHandle h : handles) {
@@ -109,10 +112,10 @@
// TODO(dborowitz): This may still result in multiple updates to All-Users, but that's
// currently not a big deal because multi-change batches generally aren't affecting
// drafts anyway.
- for (NoteDbBatchUpdate u : updates) {
+ for (FusedNoteDbBatchUpdate u : updates) {
handles.add(u.executeChangeOps(dryrun));
}
- for (NoteDbBatchUpdate u : updates) {
+ for (FusedNoteDbBatchUpdate u : updates) {
u.executeUpdateRepo();
}
for (ChangesHandle h : handles) {
@@ -147,7 +150,7 @@
u -> u.gitRefUpdated.fire(u.project, u.batchRefUpdate, u.getAccount().orElse(null)));
if (!dryrun) {
- for (NoteDbBatchUpdate u : updates) {
+ for (FusedNoteDbBatchUpdate u : updates) {
u.executePostOps();
}
}
@@ -159,7 +162,7 @@
class ContextImpl implements Context {
@Override
public RepoView getRepoView() throws IOException {
- return NoteDbBatchUpdate.this.getRepoView();
+ return FusedNoteDbBatchUpdate.this.getRepoView();
}
@Override
@@ -268,7 +271,7 @@
private final ReviewDb db;
@Inject
- NoteDbBatchUpdate(
+ FusedNoteDbBatchUpdate(
GitRepositoryManager repoManager,
@GerritPersonIdent PersonIdent serverIdent,
ChangeNotes.Factory changeNotesFactory,
@@ -351,7 +354,7 @@
}
void execute() throws OrmException, IOException {
- NoteDbBatchUpdate.this.batchRefUpdate = manager.execute(dryrun);
+ FusedNoteDbBatchUpdate.this.batchRefUpdate = manager.execute(dryrun);
}
List<CheckedFuture<?, IOException>> startIndexFutures() {
@@ -382,16 +385,19 @@
private ChangesHandle executeChangeOps(boolean dryrun) throws Exception {
logDebug("Executing change ops");
initRepository();
+ Repository repo = repoView.getRepository();
+ checkState(
+ repo.getRefDatabase().performsAtomicTransactions(),
+ "cannot use noteDb.changes.fuseUpdates=true with a repository that does not support atomic"
+ + " batch ref updates: %s",
+ repo);
ChangesHandle handle =
new ChangesHandle(
updateManagerFactory
.create(project)
.setChangeRepo(
- repoView.getRepository(),
- repoView.getRevWalk(),
- repoView.getInserter(),
- repoView.getCommands()),
+ repo, repoView.getRevWalk(), repoView.getInserter(), repoView.getCommands()),
dryrun);
if (user.isIdentifiedUser()) {
handle.manager.setRefLogIdent(user.asIdentifiedUser().newRefLogIdent(when, tz));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/UnfusedNoteDbBatchUpdate.java
similarity index 63%
copy from gerrit-server/src/main/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/update/UnfusedNoteDbBatchUpdate.java
index f7988cf..2f7de46 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/NoteDbBatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/UnfusedNoteDbBatchUpdate.java
@@ -17,9 +17,11 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -44,13 +46,15 @@
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
+import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
@@ -60,14 +64,14 @@
* <p>Used when {@code noteDb.changes.disableReviewDb=true}, at which point ReviewDb is not
* consulted during updates.
*/
-class NoteDbBatchUpdate extends BatchUpdate {
+class UnfusedNoteDbBatchUpdate extends BatchUpdate {
interface AssistedFactory {
- NoteDbBatchUpdate create(
+ UnfusedNoteDbBatchUpdate create(
ReviewDb db, Project.NameKey project, CurrentUser user, Timestamp when);
}
static void execute(
- ImmutableList<NoteDbBatchUpdate> updates,
+ ImmutableList<UnfusedNoteDbBatchUpdate> updates,
BatchUpdateListener listener,
@Nullable RequestId requestId,
boolean dryrun)
@@ -78,64 +82,43 @@
setRequestIds(updates, requestId);
try {
- List<CheckedFuture<?, IOException>> indexFutures = new ArrayList<>();
- List<ChangesHandle> handles = new ArrayList<>(updates.size());
Order order = getOrder(updates, listener);
- try {
- switch (order) {
- case REPO_BEFORE_DB:
- for (NoteDbBatchUpdate u : updates) {
- u.executeUpdateRepo();
- }
- listener.afterUpdateRepos();
- for (NoteDbBatchUpdate u : updates) {
- handles.add(u.executeChangeOps(dryrun));
- }
- for (ChangesHandle h : handles) {
- h.execute();
- indexFutures.addAll(h.startIndexFutures());
- }
- listener.afterUpdateRefs();
- listener.afterUpdateChanges();
- break;
-
- case DB_BEFORE_REPO:
- // Call updateChange for each op before updateRepo, but defer executing the
- // NoteDbUpdateManager until after calling updateRepo. They share an inserter and
- // BatchRefUpdate, so it will all execute as a single batch. But we have to let
- // NoteDbUpdateManager actually execute the update, since it has to interleave it
- // properly with All-Users updates.
- //
- // TODO(dborowitz): This may still result in multiple updates to All-Users, but that's
- // currently not a big deal because multi-change batches generally aren't affecting
- // drafts anyway.
- for (NoteDbBatchUpdate u : updates) {
- handles.add(u.executeChangeOps(dryrun));
- }
- for (NoteDbBatchUpdate u : updates) {
- u.executeUpdateRepo();
- }
- for (ChangesHandle h : handles) {
- // TODO(dborowitz): This isn't quite good enough: in theory updateRepo may want to
- // see the results of change meta commands, but they aren't actually added to the
- // BatchUpdate until the body of execute. To fix this, execute needs to be split up
- // into a method that returns a BatchRefUpdate before execution. Not a big deal at the
- // moment, because this order is only used for deleting changes, and those updateRepo
- // implementations definitely don't need to observe the updated change meta refs.
- h.execute();
- indexFutures.addAll(h.startIndexFutures());
- }
- break;
- default:
- throw new IllegalStateException("invalid execution order: " + order);
- }
- } finally {
- for (ChangesHandle h : handles) {
- h.close();
- }
+ // TODO(dborowitz): Fuse implementations to use a single BatchRefUpdate between phases. Note
+ // that we may still need to respect the order, since op implementations may make assumptions
+ // about the order in which their methods are called.
+ switch (order) {
+ case REPO_BEFORE_DB:
+ for (UnfusedNoteDbBatchUpdate u : updates) {
+ u.executeUpdateRepo();
+ }
+ listener.afterUpdateRepos();
+ for (UnfusedNoteDbBatchUpdate u : updates) {
+ u.executeRefUpdates(dryrun);
+ }
+ listener.afterUpdateRefs();
+ for (UnfusedNoteDbBatchUpdate u : updates) {
+ u.reindexChanges(u.executeChangeOps(dryrun), dryrun);
+ }
+ listener.afterUpdateChanges();
+ break;
+ case DB_BEFORE_REPO:
+ for (UnfusedNoteDbBatchUpdate u : updates) {
+ u.reindexChanges(u.executeChangeOps(dryrun), dryrun);
+ }
+ for (UnfusedNoteDbBatchUpdate u : updates) {
+ u.executeUpdateRepo();
+ }
+ for (UnfusedNoteDbBatchUpdate u : updates) {
+ u.executeRefUpdates(dryrun);
+ }
+ break;
+ default:
+ throw new IllegalStateException("invalid execution order: " + order);
}
- ChangeIndexer.allAsList(indexFutures).get();
+ ChangeIndexer.allAsList(
+ updates.stream().flatMap(u -> u.indexFutures.stream()).collect(toList()))
+ .get();
// Fire ref update events only after all mutations are finished, since callers may assume a
// patch set ref being created means the change was created, or a branch advancing meaning
@@ -147,7 +130,7 @@
u -> u.gitRefUpdated.fire(u.project, u.batchRefUpdate, u.getAccount().orElse(null)));
if (!dryrun) {
- for (NoteDbBatchUpdate u : updates) {
+ for (UnfusedNoteDbBatchUpdate u : updates) {
u.executePostOps();
}
}
@@ -159,7 +142,7 @@
class ContextImpl implements Context {
@Override
public RepoView getRepoView() throws IOException {
- return NoteDbBatchUpdate.this.getRepoView();
+ return UnfusedNoteDbBatchUpdate.this.getRepoView();
}
@Override
@@ -267,8 +250,10 @@
private final GitReferenceUpdated gitRefUpdated;
private final ReviewDb db;
+ private List<CheckedFuture<?, IOException>> indexFutures;
+
@Inject
- NoteDbBatchUpdate(
+ UnfusedNoteDbBatchUpdate(
GitRepositoryManager repoManager,
@GerritPersonIdent PersonIdent serverIdent,
ChangeNotes.Factory changeNotesFactory,
@@ -290,6 +275,7 @@
this.indexer = indexer;
this.gitRefUpdated = gitRefUpdated;
this.db = db;
+ this.indexFutures = new ArrayList<>();
}
@Override
@@ -323,104 +309,101 @@
onSubmitValidators.validate(
project, ctx.getRevWalk().getObjectReader(), repoView.getCommands());
}
+
+ // TODO(dborowitz): Don't flush when fusing phases.
+ if (repoView != null) {
+ logDebug("Flushing inserter");
+ repoView.getInserter().flush();
+ } else {
+ logDebug("No objects to flush");
+ }
} catch (Exception e) {
Throwables.throwIfInstanceOf(e, RestApiException.class);
throw new UpdateException(e);
}
}
- private class ChangesHandle implements AutoCloseable {
- private final NoteDbUpdateManager manager;
- private final boolean dryrun;
- private final Map<Change.Id, ChangeResult> results;
-
- ChangesHandle(NoteDbUpdateManager manager, boolean dryrun) {
- this.manager = manager;
- this.dryrun = dryrun;
- results = new HashMap<>();
+ // TODO(dborowitz): Don't execute non-change ref updates separately when fusing phases.
+ private void executeRefUpdates(boolean dryrun) throws IOException, RestApiException {
+ if (getRefUpdates().isEmpty()) {
+ logDebug("No ref updates to execute");
+ return;
+ }
+ // May not be opened if the caller added ref updates but no new objects.
+ initRepository();
+ batchRefUpdate = repoView.getRepository().getRefDatabase().newBatchUpdate();
+ repoView.getCommands().addTo(batchRefUpdate);
+ logDebug("Executing batch of {} ref updates", batchRefUpdate.getCommands().size());
+ if (dryrun) {
+ return;
}
- @Override
- public void close() {
- manager.close();
+ // Force BatchRefUpdate to read newly referenced objects using a new RevWalk, rather than one
+ // that might have access to unflushed objects.
+ try (RevWalk updateRw = new RevWalk(repoView.getRepository())) {
+ batchRefUpdate.execute(updateRw, NullProgressMonitor.INSTANCE);
}
-
- void setResult(Change.Id id, ChangeResult result) {
- ChangeResult old = results.putIfAbsent(id, result);
- checkArgument(old == null, "result for change %s already set: %s", id, old);
- }
-
- void execute() throws OrmException, IOException {
- NoteDbBatchUpdate.this.batchRefUpdate = manager.execute(dryrun);
- }
-
- List<CheckedFuture<?, IOException>> startIndexFutures() {
- if (dryrun) {
- return ImmutableList.of();
+ boolean ok = true;
+ for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ ok = false;
+ break;
}
- logDebug("Reindexing {} changes", results.size());
- List<CheckedFuture<?, IOException>> indexFutures = new ArrayList<>(results.size());
- for (Map.Entry<Change.Id, ChangeResult> e : results.entrySet()) {
- Change.Id id = e.getKey();
- switch (e.getValue()) {
- case UPSERTED:
- indexFutures.add(indexer.indexAsync(project, id));
- break;
- case DELETED:
- indexFutures.add(indexer.deleteAsync(id));
- break;
- case SKIPPED:
- break;
- default:
- throw new IllegalStateException("unexpected result: " + e.getValue());
- }
- }
- return indexFutures;
+ }
+ if (!ok) {
+ throw new RestApiException("BatchRefUpdate failed: " + batchRefUpdate);
}
}
- private ChangesHandle executeChangeOps(boolean dryrun) throws Exception {
+ private Map<Change.Id, ChangeResult> executeChangeOps(boolean dryrun) throws Exception {
logDebug("Executing change ops");
+ Map<Change.Id, ChangeResult> result =
+ Maps.newLinkedHashMapWithExpectedSize(ops.keySet().size());
initRepository();
-
- ChangesHandle handle =
- new ChangesHandle(
+ Repository repo = repoView.getRepository();
+ // TODO(dborowitz): Teach NoteDbUpdateManager to allow reusing the same inserter and batch ref
+ // update as in executeUpdateRepo.
+ try (ObjectInserter ins = repo.newObjectInserter();
+ ObjectReader reader = ins.newReader();
+ RevWalk rw = new RevWalk(reader);
+ NoteDbUpdateManager updateManager =
updateManagerFactory
.create(project)
- .setChangeRepo(
- repoView.getRepository(),
- repoView.getRevWalk(),
- repoView.getInserter(),
- repoView.getCommands()),
- dryrun);
- if (user.isIdentifiedUser()) {
- handle.manager.setRefLogIdent(user.asIdentifiedUser().newRefLogIdent(when, tz));
- }
- for (Map.Entry<Change.Id, Collection<BatchUpdateOp>> e : ops.asMap().entrySet()) {
- Change.Id id = e.getKey();
- ChangeContextImpl ctx = newChangeContext(id);
- boolean dirty = false;
- logDebug("Applying {} ops for change {}", e.getValue().size(), id);
- for (BatchUpdateOp op : e.getValue()) {
- dirty |= op.updateChange(ctx);
+ .setChangeRepo(repo, rw, ins, new ChainedReceiveCommands(repo))) {
+ if (user.isIdentifiedUser()) {
+ updateManager.setRefLogIdent(user.asIdentifiedUser().newRefLogIdent(when, tz));
}
- if (!dirty) {
- logDebug("No ops reported dirty, short-circuiting");
- handle.setResult(id, ChangeResult.SKIPPED);
- continue;
+ for (Map.Entry<Change.Id, Collection<BatchUpdateOp>> e : ops.asMap().entrySet()) {
+ Change.Id id = e.getKey();
+ ChangeContextImpl ctx = newChangeContext(id);
+ boolean dirty = false;
+ logDebug("Applying {} ops for change {}", e.getValue().size(), id);
+ for (BatchUpdateOp op : e.getValue()) {
+ dirty |= op.updateChange(ctx);
+ }
+ if (!dirty) {
+ logDebug("No ops reported dirty, short-circuiting");
+ result.put(id, ChangeResult.SKIPPED);
+ continue;
+ }
+ for (ChangeUpdate u : ctx.updates.values()) {
+ updateManager.add(u);
+ }
+ if (ctx.deleted) {
+ logDebug("Change {} was deleted", id);
+ updateManager.deleteChange(id);
+ result.put(id, ChangeResult.DELETED);
+ } else {
+ result.put(id, ChangeResult.UPSERTED);
+ }
}
- for (ChangeUpdate u : ctx.updates.values()) {
- handle.manager.add(u);
- }
- if (ctx.deleted) {
- logDebug("Change {} was deleted", id);
- handle.manager.deleteChange(id);
- handle.setResult(id, ChangeResult.DELETED);
- } else {
- handle.setResult(id, ChangeResult.UPSERTED);
+
+ if (!dryrun) {
+ logDebug("Executing NoteDb updates");
+ updateManager.execute();
}
}
- return handle;
+ return result;
}
private ChangeContextImpl newChangeContext(Change.Id id) throws OrmException {
@@ -440,6 +423,28 @@
return new ChangeContextImpl(ctl);
}
+ private void reindexChanges(Map<Change.Id, ChangeResult> updateResults, boolean dryrun) {
+ if (dryrun) {
+ return;
+ }
+ logDebug("Reindexing {} changes", updateResults.size());
+ for (Map.Entry<Change.Id, ChangeResult> e : updateResults.entrySet()) {
+ Change.Id id = e.getKey();
+ switch (e.getValue()) {
+ case UPSERTED:
+ indexFutures.add(indexer.indexAsync(project, id));
+ break;
+ case DELETED:
+ indexFutures.add(indexer.deleteAsync(id));
+ break;
+ case SKIPPED:
+ break;
+ default:
+ throw new IllegalStateException("unexpected result: " + e.getValue());
+ }
+ }
+ }
+
private void executePostOps() throws Exception {
ContextImpl ctx = new ContextImpl();
for (BatchUpdateOp op : ops.values()) {
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
index 59790dc..9adff05 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
@@ -38,7 +38,17 @@
{let $ulStyle kind="css"}
list-style: none;
- padding-left: 20px;
+ padding: 0;
+ {/let}
+
+ {let $fileLiStyle kind="css"}
+ margin: 0;
+ padding: 0;
+ {/let}
+
+ {let $commentLiStyle kind="css"}
+ margin: 0;
+ padding: 0 0 0 16px;
{/let}
{let $voteStyle kind="css"}
@@ -104,14 +114,14 @@
<ul style="{$ulStyle}">
{foreach $group in $commentFiles}
- <li>
+ <li style="{$fileLiStyle}">
<p>
<a href="{$group.link}">{$group.title}:</a>
</p>
<ul style="{$ulStyle}">
{foreach $comment in $group.comments}
- <li>
+ <li style="{$commentLiStyle}">
{if $comment.isRobotComment}
<p style="{$commentHeaderStyle}">
Robot Comment from{sp}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/NoteDbUpdateManagerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/NoteDbUpdateManagerTest.java
new file mode 100644
index 0000000..12eb39d
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/NoteDbUpdateManagerTest.java
@@ -0,0 +1,119 @@
+// 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.notedb;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.server.git.LockFailureException;
+import java.io.IOException;
+import java.util.function.Consumer;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NoteDbUpdateManager}. */
+@RunWith(JUnit4.class)
+public class NoteDbUpdateManagerTest {
+ private static final Consumer<ReceiveCommand> OK = c -> c.setResult(ReceiveCommand.Result.OK);
+ private static final Consumer<ReceiveCommand> LOCK_FAILURE =
+ c -> c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
+ private static final Consumer<ReceiveCommand> REJECTED =
+ c -> c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON);
+ private static final Consumer<ReceiveCommand> ABORTED =
+ c -> {
+ c.setResult(ReceiveCommand.Result.NOT_ATTEMPTED);
+ ReceiveCommand.abort(ImmutableList.of(c));
+ checkState(
+ c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED
+ && c.getResult() != ReceiveCommand.Result.LOCK_FAILURE
+ && c.getResult() != ReceiveCommand.Result.OK,
+ "unexpected state after abort: %s",
+ c);
+ };
+
+ @Test
+ public void checkBatchRefUpdateResults() throws Exception {
+ checkResults(OK);
+ checkResults(OK, OK);
+
+ assertIoException(REJECTED);
+ assertIoException(OK, REJECTED);
+ assertIoException(LOCK_FAILURE, REJECTED);
+ assertIoException(LOCK_FAILURE, OK);
+ assertIoException(LOCK_FAILURE, REJECTED, OK);
+ assertIoException(LOCK_FAILURE, LOCK_FAILURE, REJECTED);
+ assertIoException(LOCK_FAILURE, ABORTED, REJECTED);
+ assertIoException(LOCK_FAILURE, ABORTED, OK);
+
+ assertLockFailureException(LOCK_FAILURE);
+ assertLockFailureException(LOCK_FAILURE, LOCK_FAILURE);
+ assertLockFailureException(LOCK_FAILURE, LOCK_FAILURE, ABORTED);
+ assertLockFailureException(LOCK_FAILURE, LOCK_FAILURE, ABORTED, ABORTED);
+ assertLockFailureException(ABORTED);
+ assertLockFailureException(ABORTED, ABORTED);
+ }
+
+ @SafeVarargs
+ private static void checkResults(Consumer<ReceiveCommand>... resultSetters) throws Exception {
+ NoteDbUpdateManager.checkResults(newBatchRefUpdate(resultSetters));
+ }
+
+ @SafeVarargs
+ private static void assertIoException(Consumer<ReceiveCommand>... resultSetters) {
+ try {
+ NoteDbUpdateManager.checkResults(newBatchRefUpdate(resultSetters));
+ assert_().fail("expected IOException");
+ } catch (IOException e) {
+ assertThat(e).isNotInstanceOf(LockFailureException.class);
+ }
+ }
+
+ @SafeVarargs
+ private static void assertLockFailureException(Consumer<ReceiveCommand>... resultSetters)
+ throws Exception {
+ try {
+ NoteDbUpdateManager.checkResults(newBatchRefUpdate(resultSetters));
+ assert_().fail("expected LockFailureException");
+ } catch (LockFailureException e) {
+ // Expected.
+ }
+ }
+
+ @SafeVarargs
+ private static BatchRefUpdate newBatchRefUpdate(Consumer<ReceiveCommand>... resultSetters) {
+ try (Repository repo = new InMemoryRepository(new DfsRepositoryDescription("repo"))) {
+ BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+ for (int i = 0; i < resultSetters.length; i++) {
+ ReceiveCommand cmd =
+ new ReceiveCommand(
+ ObjectId.fromString(String.format("%039x1", i)),
+ ObjectId.fromString(String.format("%039x2", i)),
+ "refs/heads/branch" + i);
+ bru.addCommand(cmd);
+ resultSetters[i].accept(cmd);
+ }
+ return bru;
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 60d6f71..ebd5b49 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -58,6 +58,7 @@
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.schema.SchemaCreator;
@@ -110,12 +111,14 @@
assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse();
}
- private void assertCanRead(ProjectControl u) {
- assertThat(u.isVisible()).named("can read").isTrue();
+ private void assertCanAccess(ProjectControl u) {
+ boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
+ assertThat(access).named("can access").isTrue();
}
- private void assertCannotRead(ProjectControl u) {
- assertThat(u.isVisible()).named("cannot read").isFalse();
+ private void assertAccessDenied(ProjectControl u) {
+ boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
+ assertThat(access).named("cannot access").isFalse();
}
private void assertCanRead(String ref, ProjectControl u) {
@@ -443,13 +446,13 @@
public void inheritDuplicateSections() throws Exception {
allow(parent, READ, ADMIN, "refs/*");
allow(local, READ, DEVS, "refs/heads/*");
- assertCanRead(user(local, "a", ADMIN));
+ assertCanAccess(user(local, "a", ADMIN));
local = new ProjectConfig(localKey);
local.load(newRepository(localKey));
local.getProject().setParentName(parentKey);
allow(local, READ, DEVS, "refs/*");
- assertCanRead(user(local, "d", DEVS));
+ assertCanAccess(user(local, "d", DEVS));
}
@Test
@@ -457,7 +460,7 @@
allow(parent, READ, REGISTERED_USERS, "refs/*");
deny(local, READ, REGISTERED_USERS, "refs/*");
- assertCannotRead(user(local));
+ assertAccessDenied(user(local));
}
@Test
@@ -466,7 +469,7 @@
deny(local, READ, REGISTERED_USERS, "refs/heads/*");
ProjectControl u = user(local);
- assertCanRead(u);
+ assertCanAccess(u);
assertCanRead("refs/master", u);
assertCanRead("refs/tags/foobar", u);
assertCanRead("refs/heads/master", u);
@@ -479,7 +482,7 @@
allow(local, READ, REGISTERED_USERS, "refs/heads/*");
ProjectControl u = user(local);
- assertCanRead(u);
+ assertCanAccess(u);
assertCannotRead("refs/foobar", u);
assertCannotRead("refs/tags/foobar", u);
assertCanRead("refs/heads/foobar", u);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/update/BatchUpdateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/update/BatchUpdateTest.java
index 892a9ff..dba3b3d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -31,6 +31,7 @@
import com.google.gerrit.testutil.InMemoryDatabase;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -45,19 +46,16 @@
public class BatchUpdateTest {
@Inject private AccountManager accountManager;
-
@Inject private IdentifiedUser.GenericFactory userFactory;
-
- @Inject private InMemoryDatabase schemaFactory;
-
+ @Inject private SchemaFactory<ReviewDb> schemaFactory;
@Inject private InMemoryRepositoryManager repoManager;
-
@Inject private SchemaCreator schemaCreator;
-
@Inject private ThreadLocalRequestContext requestContext;
-
@Inject private BatchUpdate.Factory batchUpdateFactory;
+ // Only for use in setting up/tearing down injector; other users should use schemaFactory.
+ @Inject private InMemoryDatabase inMemoryDatabase;
+
private LifecycleManager lifecycle;
private ReviewDb db;
private TestRepository<InMemoryRepository> repo;
@@ -72,8 +70,10 @@
lifecycle.add(injector);
lifecycle.start();
+ try (ReviewDb underlyingDb = inMemoryDatabase.getDatabase().open()) {
+ schemaCreator.create(underlyingDb);
+ }
db = schemaFactory.open();
- schemaCreator.create(db);
Account.Id userId = accountManager.authenticate(AuthRequest.forUser("user")).getAccountId();
user = userFactory.create(userId);
@@ -108,7 +108,7 @@
if (db != null) {
db.close();
}
- InMemoryDatabase.drop(schemaFactory);
+ InMemoryDatabase.drop(inMemoryDatabase);
}
@Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritServerTests.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritServerTests.java
index 038baac..dd42b67 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritServerTests.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/GerritServerTests.java
@@ -49,8 +49,10 @@
};
public void beforeTest() throws Exception {
- notesMigration = new TestNotesMigration().setFromEnv();
+ notesMigration = new TestNotesMigration();
}
- public void afterTest() {}
+ public void afterTest() {
+ notesMigration.resetFromEnv();
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
index 4826d9e..a90cbb9 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
@@ -19,6 +19,8 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.RepositoryCaseMismatchException;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
@@ -30,7 +32,7 @@
/** Repository manager that uses in-memory repositories. */
public class InMemoryRepositoryManager implements GitRepositoryManager {
public static InMemoryRepository newRepository(Project.NameKey name) {
- return new Repo(name);
+ return new Repo(new TestNotesMigration(), name);
}
public static class Description extends DfsRepositoryDescription {
@@ -49,11 +51,15 @@
public static class Repo extends InMemoryRepository {
private String description;
- private Repo(Project.NameKey name) {
+ private Repo(NotesMigration notesMigration, Project.NameKey name) {
super(new Description(name));
- // TODO(dborowitz): Allow atomic transactions when this is supported:
- // https://git.eclipse.org/r/#/c/61841/2/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java@313
- setPerformsAtomicTransactions(false);
+ // Normally, mimic the behavior of JGit FileRepository, the standard Gerrit repository
+ // backend, and don't support atomic ref updates. The exception is when we're testing with
+ // fused ref updates, which requires atomic ref updates to function.
+ //
+ // TODO(dborowitz): Change to match the behavior of JGit FileRepository after fixing
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=515678
+ setPerformsAtomicTransactions(notesMigration.fuseUpdates());
}
@Override
@@ -72,7 +78,18 @@
}
}
- private Map<String, Repo> repos = new HashMap<>();
+ private final NotesMigration notesMigration;
+ private final Map<String, Repo> repos;
+
+ public InMemoryRepositoryManager() {
+ this(new TestNotesMigration());
+ }
+
+ @Inject
+ InMemoryRepositoryManager(NotesMigration notesMigration) {
+ this.notesMigration = notesMigration;
+ this.repos = new HashMap<>();
+ }
@Override
public synchronized Repo openRepository(Project.NameKey name) throws RepositoryNotFoundException {
@@ -89,7 +106,7 @@
throw new RepositoryCaseMismatchException(name);
}
} catch (RepositoryNotFoundException e) {
- repo = new Repo(name);
+ repo = new Repo(notesMigration, name);
repos.put(normalize(name), repo);
}
return repo;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbMode.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbMode.java
index 552f6f1..e8446a2 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbMode.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbMode.java
@@ -35,6 +35,9 @@
/** All change tables are entirely disabled. */
DISABLE_CHANGE_REVIEW_DB(true),
+ /** All change tables are entirely disabled, and code/meta ref updates are fused. */
+ FUSED(true),
+
/**
* Run tests with NoteDb disabled, then convert ReviewDb to NoteDb and check that the results
* match.
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestNotesMigration.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestNotesMigration.java
index d4ddaf1..c05dd01 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestNotesMigration.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestNotesMigration.java
@@ -27,9 +27,12 @@
private volatile boolean writeChanges;
private volatile PrimaryStorage changePrimaryStorage = PrimaryStorage.REVIEW_DB;
private volatile boolean disableChangeReviewDb;
+ private volatile boolean fuseUpdates;
private volatile boolean failOnLoad;
- public TestNotesMigration() {}
+ public TestNotesMigration() {
+ resetFromEnv();
+ }
@Override
public boolean readChanges() {
@@ -53,6 +56,11 @@
return disableChangeReviewDb;
}
+ @Override
+ public boolean fuseUpdates() {
+ return fuseUpdates;
+ }
+
// Increase visbility from superclass, as tests may want to check whether
// NoteDb data is written in specific migration scenarios.
@Override
@@ -85,6 +93,11 @@
return this;
}
+ public TestNotesMigration setFuseUpdates(boolean fuseUpdates) {
+ this.fuseUpdates = fuseUpdates;
+ return this;
+ }
+
public TestNotesMigration setFailOnLoad(boolean failOnLoad) {
this.failOnLoad = failOnLoad;
return this;
@@ -94,31 +107,42 @@
return setReadChanges(enabled).setWriteChanges(enabled);
}
- public TestNotesMigration setFromEnv() {
+ public TestNotesMigration resetFromEnv() {
switch (NoteDbMode.get()) {
case READ_WRITE:
setWriteChanges(true);
setReadChanges(true);
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
setDisableChangeReviewDb(false);
+ setFuseUpdates(false);
break;
case WRITE:
setWriteChanges(true);
setReadChanges(false);
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
setDisableChangeReviewDb(false);
+ setFuseUpdates(false);
break;
case PRIMARY:
setWriteChanges(true);
setReadChanges(true);
setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
setDisableChangeReviewDb(false);
+ setFuseUpdates(false);
break;
case DISABLE_CHANGE_REVIEW_DB:
setWriteChanges(true);
setReadChanges(true);
setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
setDisableChangeReviewDb(true);
+ setFuseUpdates(false);
+ break;
+ case FUSED:
+ setWriteChanges(true);
+ setReadChanges(true);
+ setChangePrimaryStorage(PrimaryStorage.NOTE_DB);
+ setDisableChangeReviewDb(true);
+ setFuseUpdates(true);
break;
case CHECK:
case OFF:
@@ -127,6 +151,7 @@
setReadChanges(false);
setChangePrimaryStorage(PrimaryStorage.REVIEW_DB);
setDisableChangeReviewDb(false);
+ setFuseUpdates(false);
break;
}
return this;
@@ -137,6 +162,7 @@
setReadChanges(other.readChanges());
setChangePrimaryStorage(other.changePrimaryStorage());
setDisableChangeReviewDb(other.disableChangeReviewDb());
+ setFuseUpdates(other.fuseUpdates());
return this;
}
}
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 7b7893a..0ac7765 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,9 +16,9 @@
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.Atomics;
+import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
-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;
diff --git a/lib/elasticsearch/BUILD b/lib/elasticsearch/BUILD
index 8dc4bce..c40925e 100644
--- a/lib/elasticsearch/BUILD
+++ b/lib/elasticsearch/BUILD
@@ -26,9 +26,6 @@
],
)
-# Java REST client for Elasticsearch.
-VERSION = "0.1.7"
-
java_library(
name = "jest-common",
data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index 67cc7c0..9b96521 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -10,8 +10,8 @@
bower_archive(
name = "accessibility-developer-tools",
package = "accessibility-developer-tools",
- version = "2.11.0",
- sha1 = "792cb24b649dafb316e7e536f8ae65d0d7b52bab")
+ version = "2.12.0",
+ sha1 = "88ae82dcdeb6c658f76eff509d0ee425cae14d49")
bower_archive(
name = "async",
package = "async",
@@ -40,13 +40,13 @@
bower_archive(
name = "iron-fit-behavior",
package = "iron-fit-behavior",
- version = "1.2.5",
- sha1 = "5938815cd227843fc77ebeac480b999600a76157")
+ version = "1.2.6",
+ sha1 = "59daa8526aac59aa72b8edcbbd24d9eed555a0f5")
bower_archive(
name = "iron-flex-layout",
package = "iron-flex-layout",
- version = "1.3.1",
- sha1 = "ba696394abff5e799fc06eb11bff4720129a1b52")
+ version = "1.3.2",
+ sha1 = "b896041aad049a5e889a0165828d7b1262e32612")
bower_archive(
name = "iron-form-element-behavior",
package = "iron-form-element-behavior",
@@ -105,5 +105,5 @@
bower_archive(
name = "webcomponentsjs",
package = "webcomponentsjs",
- version = "0.7.23",
- sha1 = "3d62269e614175573b0a0f3039aab05d40f0a763")
+ version = "0.7.24",
+ sha1 = "559227f8ee9db9bfbd81989f24510cc0c1bfc65c")
diff --git a/plugins/replication b/plugins/replication
index 032d195..a6cba7b 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 032d195285b11213821ed695ecadce51238d9bb8
+Subproject commit a6cba7b3ab4aa1e1ad0f0deb95078aacaa29b37d
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 864e294..77d5781 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -68,9 +68,10 @@
that serves PolyGerrit:
```sh
-bazel build polygerrit && \
-java -jar bazel-bin/polygerrit.war daemon --polygerrit-dev \
--d ../gerrit_testsite --console-log --show-stack-trace
+bazel build polygerrit &&
+ $(bazel info output_base)/external/local_jdk/bin/java \
+ -jar bazel-bin/polygerrit.war daemon --polygerrit-dev \
+ -d ../gerrit_testsite --console-log --show-stack-trace
```
## Running Tests
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index e576a71..f784bcf 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -529,6 +529,9 @@
if (this.viewState.showReplyDialog) {
this._openReplyDialog();
+ // TODO(kaspern@): Find a better signal for when to call center.
+ this.async(function() { this.$.replyOverlay.center(); }, 100);
+ this.async(function() { this.$.replyOverlay.center(); }, 1000);
this.set('viewState.showReplyDialog', false);
}
}.bind(this));
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html
index 75b98de..4287b97 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.html
@@ -76,7 +76,7 @@
selected="[[_computeIndexOfLabelValue(change.labels, permittedLabels, label)]]"
hidden$="[[!_computeAnyPermittedLabelValues(permittedLabels, label.name)]]">
<template is="dom-repeat"
- items="[[_computeBlankItems(permittedLabels, label.name)]]"
+ items="[[_computeBlankItems(permittedLabels, label.name, 'start')]]"
as="value">
<span class="placeholder"> </span>
</template>
@@ -88,7 +88,7 @@
[[value]]</gr-button>
</template>
<template is="dom-repeat"
- items="[[_computeBlankItems(permittedLabels, label.name)]]"
+ items="[[_computeBlankItems(permittedLabels, label.name, 'end')]]"
as="value">
<span class="placeholder"> </span>
</template>
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
index f3f374a..d0f5dd1c 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
@@ -104,8 +104,8 @@
var labelValue = label.value;
var len = permittedLabels[label.name] != null ?
permittedLabels[label.name].length : 0;
- var numberBlanks = this._computeBlankItems(permittedLabels, label.name)
- .length;
+ var numberBlanks =
+ this._computeBlankItems(permittedLabels, label.name, 'start').length;
for (var i = 0; i < len; i++) {
var val = parseInt(permittedLabels[label.name][i], 10);
if (val == labelValue) {
@@ -119,10 +119,16 @@
return permittedLabels[label];
},
- _computeBlankItems: function(permittedLabels, label) {
+ _computeBlankItems: function(permittedLabels, label, side) {
+ if (!permittedLabels[label]) { return []; }
var startPosition = this._labelValues[parseInt(
permittedLabels[label][0])];
- return new Array(startPosition);
+ if (side === 'start') {
+ return new Array(startPosition);
+ }
+ var endPosition = this._labelValues[parseInt(
+ permittedLabels[label][permittedLabels[label].length - 1])];
+ return new Array(Object.keys(this._labelValues).length - endPosition - 1);
},
_computeAnyPermittedLabelValues: function(permittedLabels, label) {
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
index cbb4c36..c7f5a59 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
@@ -203,7 +203,8 @@
assert.equal(element._computeBlankItems(element.permittedLabels,
'Code-Review').length, 0);
- assert.deepEqual(element._computeBlankItems(element.permittedLabels,
+ assert.deepEqual(
+ element._computeBlankItems(element.permittedLabels,
'Verified').length, 1);
});
@@ -240,5 +241,42 @@
flushAsynchronousOperations();
assert.equal(selector.selected, 3); // Index 3, value 1
});
+
+ test('without permitted labels', function() {
+ element.permittedLabels = {
+ Verified: [
+ '-1',
+ ' 0',
+ '+1',
+ ],
+ };
+ flushAsynchronousOperations();
+ assert.isOk(element.$$('iron-selector[data-label="Verified"]'));
+ assert.isFalse(element.$$('iron-selector[data-label="Verified"]').hidden);
+ assert.isOk(element.$$('iron-selector[data-label="Code-Review"]'));
+ assert.isTrue(
+ element.$$('iron-selector[data-label="Code-Review"]').hidden);
+ });
+
+ test('asymetrical labels', function() {
+ element.permittedLabels = {
+ 'Code-Review': [
+ '-2',
+ '-1',
+ ' 0',
+ '+1',
+ '+2',
+ ],
+ Verified: [
+ ' 0',
+ '+1',
+ ],
+ };
+ flushAsynchronousOperations();
+ assert.equal(element.$$('iron-selector[data-label="Verified"]')
+ .items.length, 5);
+ assert.equal(element.$$('iron-selector[data-label="Code-Review"]')
+ .items.length, 5);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index 487074e..affa8e5 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -58,6 +58,33 @@
],
}];
+ var DOCUMENTATION_LINKS = [
+ {
+ url : '/index.html',
+ name : 'Table of Contents',
+ },
+ {
+ url : '/user-search.html',
+ name : 'Searching',
+ },
+ {
+ url : '/user-upload.html',
+ name : 'Uploading',
+ },
+ {
+ url : '/access-control.html',
+ name : 'Access Control',
+ },
+ {
+ url : '/rest-api.html',
+ name : 'REST API',
+ },
+ {
+ url : '/intro-project-owner.html',
+ name : 'Project Owner Guide',
+ }
+ ];
+
Polymer({
is: 'gr-main-header',
@@ -82,9 +109,13 @@
return DEFAULT_LINKS;
},
},
+ _docBaseUrl: {
+ type: String,
+ },
_links: {
type: Array,
- computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks)',
+ computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks, ' +
+ '_docBaseUrl)',
},
_loginURL: {
type: String,
@@ -106,6 +137,7 @@
attached: function() {
this._loadAccount();
+ this._loadConfig();
this.listen(window, 'location-change', '_handleLocationChange');
},
@@ -137,7 +169,7 @@
return '//' + window.location.host + this.getBaseUrl() + path;
},
- _computeLinks: function(defaultLinks, userLinks, adminLinks) {
+ _computeLinks: function(defaultLinks, userLinks, adminLinks, docBaseUrl) {
var links = defaultLinks.slice();
if (userLinks && userLinks.length > 0) {
links.push({
@@ -151,9 +183,33 @@
links: adminLinks,
});
}
+ var docLinks = this._getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
+ if (docLinks.length) {
+ links.push({
+ title: 'Documentation',
+ links: docLinks,
+ });
+ }
return links;
},
+ _getDocLinks: function(docBaseUrl, docLinks) {
+ if (!docBaseUrl || !docLinks) {
+ return [];
+ }
+ return docLinks.map(function(link) {
+ var url = docBaseUrl;
+ if (url && url[url.length - 1] === '/') {
+ url = url.substring(0, url.length - 1);
+ }
+ return {
+ url: url + link.url,
+ name: link.name,
+ target: '_blank',
+ };
+ });
+ },
+
_loadAccount: function() {
this.$.restAPI.getAccount().then(function(account) {
this._account = account;
@@ -162,6 +218,27 @@
}.bind(this));
},
+ _loadConfig: function() {
+ this.$.restAPI.getConfig().then(function(config) {
+ if (config && config.gerrit && config.gerrit.doc_url) {
+ this._docBaseUrl = config.gerrit.doc_url;
+ }
+ if (!this._docBaseUrl) {
+ return this._probeDocLink('/Documentation/index.html');
+ }
+ }.bind(this));
+ },
+
+ _probeDocLink: function(path) {
+ return this.$.restAPI.probePath(this.getBaseUrl() + path).then(function(ok) {
+ if (ok) {
+ this._docBaseUrl = this.getBaseUrl() + '/Documentation';
+ } else {
+ this._docBaseUrl = null;
+ }
+ }.bind(this));
+ },
+
_accountLoaded: function(account) {
if (!account) { return; }
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index eebf397..4582b4f 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -41,6 +41,7 @@
sandbox = sinon.sandbox.create();
stub('gr-rest-api-interface', {
getConfig: function() { return Promise.resolve({}); },
+ probePath: function(path) { return Promise.resolve(false); },
});
stub('gr-main-header', {
_loadAccount: function() {},
@@ -130,5 +131,31 @@
links: adminLinks,
}));
});
+
+ test('documentation links', function() {
+ var docLinks = [
+ {
+ name: 'Table of Contents',
+ url: '/index.html',
+ },
+ ];
+
+ assert.deepEqual(element._getDocLinks(null, docLinks), []);
+ assert.deepEqual(element._getDocLinks('', docLinks), []);
+ assert.deepEqual(element._getDocLinks('base', null), []);
+ assert.deepEqual(element._getDocLinks('base', []), []);
+
+ assert.deepEqual(element._getDocLinks('base', docLinks), [{
+ name: 'Table of Contents',
+ target: '_blank',
+ url: 'base/index.html',
+ }]);
+
+ assert.deepEqual(element._getDocLinks('base/', docLinks), [{
+ name: 'Table of Contents',
+ target: '_blank',
+ url: 'base/index.html',
+ }]);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
index 0a555cf..e89bf05 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -138,7 +138,9 @@
hidden$="[[link.url]]">[[link.name]]</span>
<a
class="itemAction"
- href$="[[_computeRelativeURL(link.url)]]"
+ href$="[[_computeLinkURL(link)]]"
+ rel$="[[_computeLinkRel(link)]]"
+ target$="[[link.target]]"
hidden$="[[!link.url]]">[[link.name]]</a>
</li>
</template>
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 6e1637c..5a40e6f 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
@@ -97,6 +97,17 @@
return this._computeURLHelper(host, path);
},
+ _computeLinkURL: function(link) {
+ if (link.target) {
+ return link.url;
+ }
+ return this._computeRelativeURL(link.url);
+ },
+
+ _computeLinkRel: function(link) {
+ return link.target ? 'noopener' : null;
+ },
+
_handleItemTap: function(e) {
var id = e.target.getAttribute('data-id');
var item = this.items.find(function(item) {
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 f8b11ba..5b2d0486 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
@@ -49,13 +49,29 @@
assert.isTrue(element.$.dropdown.opened);
});
- test('_computeRelativeURL', function() {
+ test('_computeURLHelper', function() {
var path = '/test';
var host = 'http://www.testsite.com';
var computedPath = element._computeURLHelper(host, path);
assert.equal(computedPath, '//http://www.testsite.com/test');
});
+ test('link URLs', function() {
+ assert.equal(
+ element._computeLinkURL({url: '/test'}),
+ '//' + window.location.host + '/test');
+ assert.equal(
+ element._computeLinkURL({url: '/test', target: '_blank'}),
+ '/test');
+ });
+
+ test('link rel', function() {
+ assert.isNull(element._computeLinkRel({url: '/test'}));
+ assert.equal(
+ element._computeLinkRel({url: '/test', target: '_blank'}),
+ 'noopener');
+ });
+
test('_getClassIfBold', function() {
var bold = true;
assert.equal(element._getClassIfBold(bold), 'bold-text');
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 95fa2a7..f340184 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
@@ -583,7 +583,7 @@
body = {reviewer: reviewerID};
break;
case 'DELETE':
- url += '/' + reviewerID;
+ url += '/' + encodeURIComponent(reviewerID);
break;
default:
throw Error('Unsupported HTTP method: ' + method);
@@ -1107,5 +1107,12 @@
return this.send('DELETE',
this.getChangeActionURL(changeNum, null, '/assignee'));
},
+
+ probePath: function(path) {
+ return fetch(new Request(path, {method: 'HEAD'}))
+ .then(function(response) {
+ return response.ok;
+ });
+ },
});
})();