diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt index 72a9c21170..37af1101be 100644 --- a/Documentation/cmd-stream-events.txt +++ b/Documentation/cmd-stream-events.txt @@ -90,6 +90,16 @@ reason:: Reason for abandoning the change. 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 b473b430c6..016c8468a7 100644 --- a/Documentation/config-project-config.txt +++ b/Documentation/config-project-config.txt @@ -182,9 +182,10 @@ the parent project. + 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/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/EventRecorder.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/EventRecorder.java index d2cd170148..e9e8794962 100644 --- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/EventRecorder.java +++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/EventRecorder.java @@ -26,6 +26,7 @@ import com.google.gerrit.extensions.registration.RegistrationHandle; 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; @@ -68,6 +69,8 @@ public class EventRecorder { 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 = @@ -137,6 +140,21 @@ public class EventRecorder { return events; } + private ImmutableList getChangeDeletedEvents(int expectedSize) { + String key = ChangeDeletedEvent.TYPE; + if (expectedSize == 0) { + assertThat(recordedEvents).doesNotContainKey(key); + return ImmutableList.of(); + } + assertThat(recordedEvents).containsKey(key); + ImmutableList 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); } @@ -196,6 +214,18 @@ public class EventRecorder { } } + public void assertChangeDeletedEvents(String... expected) { + ImmutableList 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/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java index 50aac28f81..6701bb262a 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java @@ -1011,6 +1011,7 @@ public class ChangeIT extends AbstractDaemonTest { 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/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeDeletedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ChangeDeletedListener.java new file mode 100644 index 0000000000..70014f3ee5 --- /dev/null +++ b/gerrit-extension-api/src/main/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/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java index 878abd2502..b30b3ece95 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 @@ -137,25 +137,7 @@ public class FormatUtil { 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/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java index e148b80363..52bd35774e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java @@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.StarredChangesUtil; +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; @@ -55,6 +56,7 @@ class DeleteChangeOp implements BatchUpdateOp { private final PatchSetUtil psUtil; private final StarredChangesUtil starredChangesUtil; private final DynamicItem accountPatchReviewStore; + private final ChangeDeleted changeDeleted; private Change.Id id; @@ -62,10 +64,12 @@ class DeleteChangeOp implements BatchUpdateOp { DeleteChangeOp( PatchSetUtil psUtil, StarredChangesUtil starredChangesUtil, - DynamicItem accountPatchReviewStore) { + DynamicItem accountPatchReviewStore, + ChangeDeleted changeDeleted) { this.psUtil = psUtil; this.starredChangesUtil = starredChangesUtil; this.accountPatchReviewStore = accountPatchReviewStore; + this.changeDeleted = changeDeleted; } @Override @@ -85,6 +89,7 @@ class DeleteChangeOp implements BatchUpdateOp { deleteChangeElementsFromDb(ctx, id); ctx.deleteChange(); + changeDeleted.fire(ctx.getChange(), ctx.getAccount(), ctx.getWhen()); return true; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index 469c664575..2e2d675c26 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java @@ -36,6 +36,7 @@ import com.google.gerrit.extensions.events.AccountIndexedListener; 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; @@ -315,6 +316,7 @@ public class GerritGlobalModule extends FactoryModule { 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/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeDeletedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ChangeDeletedEvent.java new file mode 100644 index 0000000000..63142fd149 --- /dev/null +++ b/gerrit-server/src/main/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 deleter; + + public ChangeDeletedEvent(Change change) { + super(TYPE, change); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java index cd2b464f82..5498ec8eb1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventTypes.java @@ -24,6 +24,7 @@ public class EventTypes { 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/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java index 3006b3b70b..4c948fc718 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/StreamEventsApiListener.java @@ -26,6 +26,7 @@ import com.google.gerrit.extensions.common.ChangeInfo; 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; @@ -77,6 +78,7 @@ import org.slf4j.LoggerFactory; public class StreamEventsApiListener implements AssigneeChangedListener, ChangeAbandonedListener, + ChangeDeletedListener, ChangeMergedListener, ChangeRestoredListener, WorkInProgressStateChangedListener, @@ -97,6 +99,7 @@ public class StreamEventsApiListener 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); @@ -525,4 +528,20 @@ public class StreamEventsApiListener log.error("Failed to dispatch event", e); } } + + @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); + event.deleter = accountAttributeSupplier(ev.getWho()); + + dispatcher.get().postEvent(change, event); + } catch (OrmException | PermissionBackendException e) { + log.error("Failed to dispatch event", e); + } + } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java new file mode 100644 index 0000000000..26bc2296af --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeDeleted.java @@ -0,0 +1,67 @@ +// 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.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.Account; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.sql.Timestamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +public class ChangeDeleted { + private static final Logger log = LoggerFactory.getLogger(ChangeDeleted.class); + + private final DynamicSet listeners; + private final EventUtil util; + + @Inject + ChangeDeleted(DynamicSet listeners, EventUtil util) { + this.listeners = listeners; + this.util = util; + } + + public void fire(Change change, Account 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) { + log.error("Couldn't fire event", e); + } + } + + 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/plugins/hooks b/plugins/hooks index 3a3d2d059e..75df5d54fc 160000 --- a/plugins/hooks +++ b/plugins/hooks @@ -1 +1 @@ -Subproject commit 3a3d2d059e5ed4f224c6cee0ad5bd6baae835fdc +Subproject commit 75df5d54fcb0b56a799ae1dc3aa2f88cbc8e3dc3