Merge branch 'stable-2.15'
* stable-2.15:
ChangeData#isReviewedBy: Guard against null current patch set
Revert "GetCapabilities#CheckOne: Return json content type"
FormatUtil: Correctly fix the Math#round() error flagged by error-prone
Add a change deleted event/listener
Fix broken link in documentation of receive.requireSignedPush
Change-Id: I57f595d1081f436187df4b73a37a4366cc66196b
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 72a9c21..37af110 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -90,6 +90,16 @@
eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created.
+== Change Deleted
+
+Sent when a change has been deleted.
+
+type:: "change-deleted"
+
+change:: link:json.html#change[change attribute]
+
+deleter:: link:json.html#account[account attribute]
+
=== Change Merged
Sent when a change has been merged into the git repository.
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index cc5386f..4456484 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -186,9 +186,10 @@
+
Controls whether server-side signed push validation is required on the
project. Only has an effect if signed push validation is enabled on the
-server, and link:#receive.enableSignedPush is set on the project. See
-the link:config-gerrit.html#receive.enableSignedPush[global
-configuration] for details.
+server, and link:#receive.enableSignedPush[`receive.enableSignedPush`] is
+set on the project. See the
+link:config-gerrit.html#receive.enableSignedPush[global configuration]
+for details.
+
Default is `INHERIT`, which means that this property is inherited from
the parent project.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index e28a9c4..86d906c 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1098,10 +1098,7 @@
.Response
----
HTTP/1.1 200 OK
- Content-Disposition: attachment
- Content-Type: application/json; charset=UTF-8
- )]}'
ok
----
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index 38e1b60..c8d052e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -126,25 +126,7 @@
if (size == 0) {
return Resources.C.notAvailable();
}
- int p = Math.abs(saturatedCast(delta * 100 / size));
- return p + "%";
- }
-
- /**
- * Returns the {@code int} nearest in value to {@code value}.
- *
- * @param value any {@code long} value
- * @return the same value cast to {@code int} if it is in the range of the {@code int} type,
- * {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if it is too
- * small
- */
- private static int saturatedCast(long value) {
- if (value > Integer.MAX_VALUE) {
- return Integer.MAX_VALUE;
- }
- if (value < Integer.MIN_VALUE) {
- return Integer.MIN_VALUE;
- }
- return (int) value;
+ long percentage = Math.abs(Math.round(delta * 100.0 / size));
+ return percentage + "%";
}
}
diff --git a/java/com/google/gerrit/acceptance/EventRecorder.java b/java/com/google/gerrit/acceptance/EventRecorder.java
index 5654c35..1af71b8 100644
--- a/java/com/google/gerrit/acceptance/EventRecorder.java
+++ b/java/com/google/gerrit/acceptance/EventRecorder.java
@@ -25,6 +25,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.data.RefUpdateAttribute;
+import com.google.gerrit.server.events.ChangeDeletedEvent;
import com.google.gerrit.server.events.ChangeMergedEvent;
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.RefEvent;
@@ -69,6 +70,8 @@
public void onEvent(Event e) {
if (e instanceof ReviewerDeletedEvent) {
recordedEvents.put(ReviewerDeletedEvent.TYPE, (ReviewerDeletedEvent) e);
+ } else if (e instanceof ChangeDeletedEvent) {
+ recordedEvents.put(ChangeDeletedEvent.TYPE, (ChangeDeletedEvent) e);
} else if (e instanceof RefEvent) {
RefEvent event = (RefEvent) e;
String key =
@@ -138,6 +141,21 @@
return events;
}
+ private ImmutableList<ChangeDeletedEvent> getChangeDeletedEvents(int expectedSize) {
+ String key = ChangeDeletedEvent.TYPE;
+ if (expectedSize == 0) {
+ assertThat(recordedEvents).doesNotContainKey(key);
+ return ImmutableList.of();
+ }
+ assertThat(recordedEvents).containsKey(key);
+ ImmutableList<ChangeDeletedEvent> events =
+ FluentIterable.from(recordedEvents.get(key))
+ .transform(ChangeDeletedEvent.class::cast)
+ .toList();
+ assertThat(events).hasSize(expectedSize);
+ return events;
+ }
+
public void assertNoRefUpdatedEvents(String project, String branch) throws Exception {
getRefUpdatedEvents(project, branch, 0);
}
@@ -197,6 +215,18 @@
}
}
+ public void assertChangeDeletedEvents(String... expected) {
+ ImmutableList<ChangeDeletedEvent> events = getChangeDeletedEvents(expected.length / 2);
+ int i = 0;
+ for (ChangeDeletedEvent event : events) {
+ String id = event.change.get().id;
+ assertThat(id).isEqualTo(expected[i]);
+ String reviewer = event.deleter.get().email;
+ assertThat(reviewer).isEqualTo(expected[i + 1]);
+ i += 2;
+ }
+ }
+
public void close() {
eventListenerRegistration.remove();
}
diff --git a/java/com/google/gerrit/extensions/events/ChangeDeletedListener.java b/java/com/google/gerrit/extensions/events/ChangeDeletedListener.java
new file mode 100644
index 0000000..70014f3
--- /dev/null
+++ b/java/com/google/gerrit/extensions/events/ChangeDeletedListener.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.events;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/** Notified whenever a Change is deleted. */
+@ExtensionPoint
+public interface ChangeDeletedListener {
+ interface Event extends ChangeEvent {}
+
+ void onChangeDeleted(Event event);
+}
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 0761d2e..4c043b6 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -33,6 +33,7 @@
import com.google.gerrit.extensions.events.AgreementSignupListener;
import com.google.gerrit.extensions.events.AssigneeChangedListener;
import com.google.gerrit.extensions.events.ChangeAbandonedListener;
+import com.google.gerrit.extensions.events.ChangeDeletedListener;
import com.google.gerrit.extensions.events.ChangeIndexedListener;
import com.google.gerrit.extensions.events.ChangeMergedListener;
import com.google.gerrit.extensions.events.ChangeRestoredListener;
@@ -311,6 +312,7 @@
DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
DynamicSet.setOf(binder(), AssigneeChangedListener.class);
DynamicSet.setOf(binder(), ChangeAbandonedListener.class);
+ DynamicSet.setOf(binder(), ChangeDeletedListener.class);
DynamicSet.setOf(binder(), CommentAddedListener.class);
DynamicSet.setOf(binder(), HashtagsEditedListener.class);
DynamicSet.setOf(binder(), ChangeMergedListener.class);
diff --git a/java/com/google/gerrit/server/events/ChangeDeletedEvent.java b/java/com/google/gerrit/server/events/ChangeDeletedEvent.java
new file mode 100644
index 0000000..63142fd
--- /dev/null
+++ b/java/com/google/gerrit/server/events/ChangeDeletedEvent.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2018 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.events;
+
+import com.google.common.base.Supplier;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.data.AccountAttribute;
+
+public class ChangeDeletedEvent extends ChangeEvent {
+ public static final String TYPE = "change-deleted";
+ public Supplier<AccountAttribute> deleter;
+
+ public ChangeDeletedEvent(Change change) {
+ super(TYPE, change);
+ }
+}
diff --git a/java/com/google/gerrit/server/events/EventTypes.java b/java/com/google/gerrit/server/events/EventTypes.java
index cd2b464..5498ec8 100644
--- a/java/com/google/gerrit/server/events/EventTypes.java
+++ b/java/com/google/gerrit/server/events/EventTypes.java
@@ -24,6 +24,7 @@
static {
register(AssigneeChangedEvent.TYPE, AssigneeChangedEvent.class);
register(ChangeAbandonedEvent.TYPE, ChangeAbandonedEvent.class);
+ register(ChangeDeletedEvent.TYPE, ChangeDeletedEvent.class);
register(ChangeMergedEvent.TYPE, ChangeMergedEvent.class);
register(ChangeRestoredEvent.TYPE, ChangeRestoredEvent.class);
register(CommentAddedEvent.TYPE, CommentAddedEvent.class);
diff --git a/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
index 367a38b..97a3f39 100644
--- a/java/com/google/gerrit/server/events/StreamEventsApiListener.java
+++ b/java/com/google/gerrit/server/events/StreamEventsApiListener.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.events.AssigneeChangedListener;
import com.google.gerrit.extensions.events.ChangeAbandonedListener;
+import com.google.gerrit.extensions.events.ChangeDeletedListener;
import com.google.gerrit.extensions.events.ChangeMergedListener;
import com.google.gerrit.extensions.events.ChangeRestoredListener;
import com.google.gerrit.extensions.events.CommentAddedListener;
@@ -75,6 +76,7 @@
public class StreamEventsApiListener
implements AssigneeChangedListener,
ChangeAbandonedListener,
+ ChangeDeletedListener,
ChangeMergedListener,
ChangeRestoredListener,
WorkInProgressStateChangedListener,
@@ -95,6 +97,7 @@
protected void configure() {
DynamicSet.bind(binder(), AssigneeChangedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ChangeAbandonedListener.class).to(StreamEventsApiListener.class);
+ DynamicSet.bind(binder(), ChangeDeletedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ChangeMergedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ChangeRestoredListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), CommentAddedListener.class).to(StreamEventsApiListener.class);
@@ -522,4 +525,20 @@
logger.atSevere().withCause(e).log("Failed to dispatch event");
}
}
+
+ @Override
+ public void onChangeDeleted(ChangeDeletedListener.Event ev) {
+ try {
+ ChangeNotes notes = getNotes(ev.getChange());
+ Change change = notes.getChange();
+ ChangeDeletedEvent event = new ChangeDeletedEvent(change);
+
+ event.change = changeAttributeSupplier(change, notes);
+ event.deleter = accountAttributeSupplier(ev.getWho());
+
+ dispatcher.get().postEvent(change, event);
+ } catch (OrmException | PermissionBackendException e) {
+ logger.atSevere().withCause(e).log("Failed to dispatch event");
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java b/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java
new file mode 100644
index 0000000..dd1dc07
--- /dev/null
+++ b/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2018 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.extensions.events;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.events.ChangeDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.sql.Timestamp;
+
+@Singleton
+public class ChangeDeleted {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final DynamicSet<ChangeDeletedListener> listeners;
+ private final EventUtil util;
+
+ @Inject
+ ChangeDeleted(DynamicSet<ChangeDeletedListener> listeners, EventUtil util) {
+ this.listeners = listeners;
+ this.util = util;
+ }
+
+ public void fire(Change change, AccountState deleter, Timestamp when) {
+ if (!listeners.iterator().hasNext()) {
+ return;
+ }
+ try {
+ Event event = new Event(util.changeInfo(change), util.accountInfo(deleter), when);
+ for (ChangeDeletedListener l : listeners) {
+ try {
+ l.onChangeDeleted(event);
+ } catch (Exception e) {
+ util.logEventListenerError(this, l, e);
+ }
+ }
+ } catch (OrmException e) {
+ logger.atSevere().withCause(e).log("Couldn't fire event");
+ }
+ }
+
+ private static class Event extends AbstractChangeEvent implements ChangeDeletedListener.Event {
+ Event(ChangeInfo change, AccountInfo deleter, Timestamp when) {
+ super(change, deleter, when, NotifyHandling.ALL);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 1813211..7fb0989 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -1089,14 +1089,15 @@
public boolean isReviewedBy(Account.Id accountId) throws OrmException {
Collection<String> stars = stars(accountId);
- if (stars.contains(
- StarredChangesUtil.REVIEWED_LABEL + "/" + currentPatchSet().getPatchSetId())) {
- return true;
- }
+ PatchSet ps = currentPatchSet();
+ if (ps != null) {
+ if (stars.contains(StarredChangesUtil.REVIEWED_LABEL + "/" + ps.getPatchSetId())) {
+ return true;
+ }
- if (stars.contains(
- StarredChangesUtil.UNREVIEWED_LABEL + "/" + currentPatchSet().getPatchSetId())) {
- return false;
+ if (stars.contains(StarredChangesUtil.UNREVIEWED_LABEL + "/" + ps.getPatchSetId())) {
+ return false;
+ }
}
return reviewedBy().contains(accountId);
diff --git a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
index 5c466bf..7889f6e 100644
--- a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
+++ b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
@@ -26,8 +26,8 @@
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.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
@@ -172,9 +172,9 @@
}
@Override
- public Response<String> apply(Capability resource) throws ResourceNotFoundException {
+ public BinaryResult apply(Capability resource) throws ResourceNotFoundException {
permissionBackend.checkUsesDefaultCapabilities();
- return Response.ok("ok");
+ return BinaryResult.create("ok\n");
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java b/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java
index 9658fb4..d41e504 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChangeOp.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.change.AccountPatchReviewStore;
+import com.google.gerrit.server.extensions.events.ChangeDeleted;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.BatchUpdateReviewDb;
@@ -45,6 +46,7 @@
private final PatchSetUtil psUtil;
private final StarredChangesUtil starredChangesUtil;
private final DynamicItem<AccountPatchReviewStore> accountPatchReviewStore;
+ private final ChangeDeleted changeDeleted;
private Change.Id id;
@@ -52,10 +54,12 @@
DeleteChangeOp(
PatchSetUtil psUtil,
StarredChangesUtil starredChangesUtil,
- DynamicItem<AccountPatchReviewStore> accountPatchReviewStore) {
+ DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
+ ChangeDeleted changeDeleted) {
this.psUtil = psUtil;
this.starredChangesUtil = starredChangesUtil;
this.accountPatchReviewStore = accountPatchReviewStore;
+ this.changeDeleted = changeDeleted;
}
@Override
@@ -75,6 +79,7 @@
deleteChangeElementsFromDb(ctx, id);
ctx.deleteChange();
+ changeDeleted.fire(ctx.getChange(), ctx.getAccount(), ctx.getWhen());
return true;
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index b3a5e2d..88803ec 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -1089,6 +1089,7 @@
String ref = new Change.Id(id).toRefPrefix() + "1";
eventRecorder.assertRefUpdatedEvents(projectName.get(), ref, null, commit, commit, null);
+ eventRecorder.assertChangeDeletedEvents(changeId, deleteAs.email);
} finally {
removePermission(project, "refs/*", Permission.DELETE_OWN_CHANGES);
removePermission(project, "refs/*", Permission.DELETE_CHANGES);
diff --git a/plugins/hooks b/plugins/hooks
index cc74144..7185e5c 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit cc74144db755a18c5a63764a336b93ab3d1be1fe
+Subproject commit 7185e5ce46646e952071befd9cf8f4267560b51d