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 eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
created. 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 === Change Merged
Sent when a change has been merged into the git repository. 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 Controls whether server-side signed push validation is required on the
project. Only has an effect if signed push validation is enabled 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 server, and link:#receive.enableSignedPush[`receive.enableSignedPush`] is
the link:config-gerrit.html#receive.enableSignedPush[global set on the project. See the
configuration] for details. link:config-gerrit.html#receive.enableSignedPush[global configuration]
for details.
+ +
Default is `INHERIT`, which means that this property is inherited from Default is `INHERIT`, which means that this property is inherited from
the parent project. 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.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.data.RefUpdateAttribute; 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.ChangeMergedEvent;
import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.RefEvent; import com.google.gerrit.server.events.RefEvent;
@ -68,6 +69,8 @@ public class EventRecorder {
public void onEvent(Event e) { public void onEvent(Event e) {
if (e instanceof ReviewerDeletedEvent) { if (e instanceof ReviewerDeletedEvent) {
recordedEvents.put(ReviewerDeletedEvent.TYPE, (ReviewerDeletedEvent) e); recordedEvents.put(ReviewerDeletedEvent.TYPE, (ReviewerDeletedEvent) e);
} else if (e instanceof ChangeDeletedEvent) {
recordedEvents.put(ChangeDeletedEvent.TYPE, (ChangeDeletedEvent) e);
} else if (e instanceof RefEvent) { } else if (e instanceof RefEvent) {
RefEvent event = (RefEvent) e; RefEvent event = (RefEvent) e;
String key = String key =
@ -137,6 +140,21 @@ public class EventRecorder {
return events; 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 { public void assertNoRefUpdatedEvents(String project, String branch) throws Exception {
getRefUpdatedEvents(project, branch, 0); 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() { public void close() {
eventListenerRegistration.remove(); eventListenerRegistration.remove();
} }

View File

@ -1011,6 +1011,7 @@ public class ChangeIT extends AbstractDaemonTest {
String ref = new Change.Id(id).toRefPrefix() + "1"; String ref = new Change.Id(id).toRefPrefix() + "1";
eventRecorder.assertRefUpdatedEvents(projectName.get(), ref, null, commit, commit, null); eventRecorder.assertRefUpdatedEvents(projectName.get(), ref, null, commit, commit, null);
eventRecorder.assertChangeDeletedEvents(changeId, deleteAs.email);
} finally { } finally {
removePermission(project, "refs/*", Permission.DELETE_OWN_CHANGES); removePermission(project, "refs/*", Permission.DELETE_OWN_CHANGES);
removePermission(project, "refs/*", Permission.DELETE_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) { if (size == 0) {
return Resources.C.notAvailable(); return Resources.C.notAvailable();
} }
int p = Math.abs(saturatedCast(delta * 100 / size)); long percentage = Math.abs(Math.round(delta * 100.0 / size));
return p + "%"; return percentage + "%";
}
/**
* 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;
} }
} }

View File

@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil; import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.StarredChangesUtil; 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.project.NoSuchChangeException;
import com.google.gerrit.server.update.BatchUpdateOp; import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.BatchUpdateReviewDb; import com.google.gerrit.server.update.BatchUpdateReviewDb;
@ -55,6 +56,7 @@ class DeleteChangeOp implements BatchUpdateOp {
private final PatchSetUtil psUtil; private final PatchSetUtil psUtil;
private final StarredChangesUtil starredChangesUtil; private final StarredChangesUtil starredChangesUtil;
private final DynamicItem<AccountPatchReviewStore> accountPatchReviewStore; private final DynamicItem<AccountPatchReviewStore> accountPatchReviewStore;
private final ChangeDeleted changeDeleted;
private Change.Id id; private Change.Id id;
@ -62,10 +64,12 @@ class DeleteChangeOp implements BatchUpdateOp {
DeleteChangeOp( DeleteChangeOp(
PatchSetUtil psUtil, PatchSetUtil psUtil,
StarredChangesUtil starredChangesUtil, StarredChangesUtil starredChangesUtil,
DynamicItem<AccountPatchReviewStore> accountPatchReviewStore) { DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
ChangeDeleted changeDeleted) {
this.psUtil = psUtil; this.psUtil = psUtil;
this.starredChangesUtil = starredChangesUtil; this.starredChangesUtil = starredChangesUtil;
this.accountPatchReviewStore = accountPatchReviewStore; this.accountPatchReviewStore = accountPatchReviewStore;
this.changeDeleted = changeDeleted;
} }
@Override @Override
@ -85,6 +89,7 @@ class DeleteChangeOp implements BatchUpdateOp {
deleteChangeElementsFromDb(ctx, id); deleteChangeElementsFromDb(ctx, id);
ctx.deleteChange(); ctx.deleteChange();
changeDeleted.fire(ctx.getChange(), ctx.getAccount(), ctx.getWhen());
return true; 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.AgreementSignupListener;
import com.google.gerrit.extensions.events.AssigneeChangedListener; import com.google.gerrit.extensions.events.AssigneeChangedListener;
import com.google.gerrit.extensions.events.ChangeAbandonedListener; 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.ChangeIndexedListener;
import com.google.gerrit.extensions.events.ChangeMergedListener; import com.google.gerrit.extensions.events.ChangeMergedListener;
import com.google.gerrit.extensions.events.ChangeRestoredListener; import com.google.gerrit.extensions.events.ChangeRestoredListener;
@ -315,6 +316,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class); DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
DynamicSet.setOf(binder(), AssigneeChangedListener.class); DynamicSet.setOf(binder(), AssigneeChangedListener.class);
DynamicSet.setOf(binder(), ChangeAbandonedListener.class); DynamicSet.setOf(binder(), ChangeAbandonedListener.class);
DynamicSet.setOf(binder(), ChangeDeletedListener.class);
DynamicSet.setOf(binder(), CommentAddedListener.class); DynamicSet.setOf(binder(), CommentAddedListener.class);
DynamicSet.setOf(binder(), HashtagsEditedListener.class); DynamicSet.setOf(binder(), HashtagsEditedListener.class);
DynamicSet.setOf(binder(), ChangeMergedListener.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 { static {
register(AssigneeChangedEvent.TYPE, AssigneeChangedEvent.class); register(AssigneeChangedEvent.TYPE, AssigneeChangedEvent.class);
register(ChangeAbandonedEvent.TYPE, ChangeAbandonedEvent.class); register(ChangeAbandonedEvent.TYPE, ChangeAbandonedEvent.class);
register(ChangeDeletedEvent.TYPE, ChangeDeletedEvent.class);
register(ChangeMergedEvent.TYPE, ChangeMergedEvent.class); register(ChangeMergedEvent.TYPE, ChangeMergedEvent.class);
register(ChangeRestoredEvent.TYPE, ChangeRestoredEvent.class); register(ChangeRestoredEvent.TYPE, ChangeRestoredEvent.class);
register(CommentAddedEvent.TYPE, CommentAddedEvent.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.common.RevisionInfo;
import com.google.gerrit.extensions.events.AssigneeChangedListener; import com.google.gerrit.extensions.events.AssigneeChangedListener;
import com.google.gerrit.extensions.events.ChangeAbandonedListener; 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.ChangeMergedListener;
import com.google.gerrit.extensions.events.ChangeRestoredListener; import com.google.gerrit.extensions.events.ChangeRestoredListener;
import com.google.gerrit.extensions.events.CommentAddedListener; import com.google.gerrit.extensions.events.CommentAddedListener;
@ -77,6 +78,7 @@ import org.slf4j.LoggerFactory;
public class StreamEventsApiListener public class StreamEventsApiListener
implements AssigneeChangedListener, implements AssigneeChangedListener,
ChangeAbandonedListener, ChangeAbandonedListener,
ChangeDeletedListener,
ChangeMergedListener, ChangeMergedListener,
ChangeRestoredListener, ChangeRestoredListener,
WorkInProgressStateChangedListener, WorkInProgressStateChangedListener,
@ -97,6 +99,7 @@ public class StreamEventsApiListener
protected void configure() { protected void configure() {
DynamicSet.bind(binder(), AssigneeChangedListener.class).to(StreamEventsApiListener.class); DynamicSet.bind(binder(), AssigneeChangedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ChangeAbandonedListener.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(), ChangeMergedListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), ChangeRestoredListener.class).to(StreamEventsApiListener.class); DynamicSet.bind(binder(), ChangeRestoredListener.class).to(StreamEventsApiListener.class);
DynamicSet.bind(binder(), CommentAddedListener.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); 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