Merge "Merge branch 'stable-2.15'"

This commit is contained in:
Jonathan Nieder
2018-09-17 16:42:00 +00:00
committed by Gerrit Code Review
16 changed files with 206 additions and 38 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

@@ -186,9 +186,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

@@ -1098,10 +1098,7 @@ If the user has the global capability the string `ok` is returned.
.Response .Response
---- ----
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
ok ok
---- ----

View File

@@ -126,25 +126,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

@@ -25,6 +25,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;
@@ -69,6 +70,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 =
@@ -138,6 +141,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);
} }
@@ -197,6 +215,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

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

@@ -33,6 +33,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;
@@ -311,6 +312,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;
@@ -75,6 +76,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
public class StreamEventsApiListener public class StreamEventsApiListener
implements AssigneeChangedListener, implements AssigneeChangedListener,
ChangeAbandonedListener, ChangeAbandonedListener,
ChangeDeletedListener,
ChangeMergedListener, ChangeMergedListener,
ChangeRestoredListener, ChangeRestoredListener,
WorkInProgressStateChangedListener, WorkInProgressStateChangedListener,
@@ -95,6 +97,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);
@@ -522,4 +525,20 @@ public class StreamEventsApiListener
logger.atSevere().withCause(e).log("Failed to dispatch event"); 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");
}
}
} }

View File

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

View File

@@ -1089,14 +1089,15 @@ public class ChangeData {
public boolean isReviewedBy(Account.Id accountId) throws OrmException { public boolean isReviewedBy(Account.Id accountId) throws OrmException {
Collection<String> stars = stars(accountId); Collection<String> stars = stars(accountId);
if (stars.contains( PatchSet ps = currentPatchSet();
StarredChangesUtil.REVIEWED_LABEL + "/" + currentPatchSet().getPatchSetId())) { if (ps != null) {
return true; if (stars.contains(StarredChangesUtil.REVIEWED_LABEL + "/" + ps.getPatchSetId())) {
} return true;
}
if (stars.contains( if (stars.contains(StarredChangesUtil.UNREVIEWED_LABEL + "/" + ps.getPatchSetId())) {
StarredChangesUtil.UNREVIEWED_LABEL + "/" + currentPatchSet().getPatchSetId())) { return false;
return false; }
} }
return reviewedBy().contains(accountId); return reviewedBy().contains(accountId);

View File

@@ -26,8 +26,8 @@ import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
import com.google.gerrit.extensions.api.access.PluginPermission; import com.google.gerrit.extensions.api.access.PluginPermission;
import com.google.gerrit.extensions.config.CapabilityDefinition; import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.registration.DynamicMap; 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.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
@@ -172,9 +172,9 @@ public class GetCapabilities implements RestReadView<AccountResource> {
} }
@Override @Override
public Response<String> apply(Capability resource) throws ResourceNotFoundException { public BinaryResult apply(Capability resource) throws ResourceNotFoundException {
permissionBackend.checkUsesDefaultCapabilities(); permissionBackend.checkUsesDefaultCapabilities();
return Response.ok("ok"); return BinaryResult.create("ok\n");
} }
} }
} }

View File

@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
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.change.AccountPatchReviewStore; 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.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;
@@ -45,6 +46,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;
@@ -52,10 +54,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
@@ -75,6 +79,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

@@ -1089,6 +1089,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);