Merge branch 'stable-2.14' into stable-2.15

* stable-2.14:
  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: I354180a7850bb7af28a20306e9b5db67b3a92bbd
This commit is contained in:
David Pursehouse 2018-09-16 09:29:19 +09:00
commit a6fdd1dacc
13 changed files with 196 additions and 25 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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<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);
}
@ -196,6 +214,18 @@ public class EventRecorder {
}
}
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();
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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 + "%";
}
}

View File

@ -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> accountPatchReviewStore;
private final ChangeDeleted changeDeleted;
private Change.Id id;
@ -62,10 +64,12 @@ class DeleteChangeOp implements BatchUpdateOp {
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
@ -85,6 +89,7 @@ class DeleteChangeOp implements BatchUpdateOp {
deleteChangeElementsFromDb(ctx, id);
ctx.deleteChange();
changeDeleted.fire(ctx.getChange(), ctx.getAccount(), ctx.getWhen());
return true;
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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<ChangeDeletedListener> listeners;
private final EventUtil util;
@Inject
ChangeDeleted(DynamicSet<ChangeDeletedListener> 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);
}
}
}

@ -1 +1 @@
Subproject commit 3a3d2d059e5ed4f224c6cee0ad5bd6baae835fdc
Subproject commit 75df5d54fcb0b56a799ae1dc3aa2f88cbc8e3dc3