Set hashtags using a BatchUpdate.Op
Rather than having a utility method that creates a new ChangeUpdate on invocation, get the update from the ChangeContext. Also split out the post-update steps into postUpdate. The end result is admittedly a little convoluted, requiring assisted injection just to create the op to pass to the BatchUpdate. (It should still be a little shorter than the original.) However, we don't expect most new Ops to require assisted injection, since they can typically get everything they need from the context objects. Change-Id: I4d6024dd00b00bbc1825e700b1eda0923f29d278
This commit is contained in:
parent
ed1523094d
commit
bce0e922c4
@ -22,4 +22,11 @@ public class HashtagsInput {
|
|||||||
@DefaultInput
|
@DefaultInput
|
||||||
public Set<String> add;
|
public Set<String> add;
|
||||||
public Set<String> remove;
|
public Set<String> remove;
|
||||||
|
|
||||||
|
public HashtagsInput(){
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashtagsInput(Set<String> add) {
|
||||||
|
this.add = add;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ class ChangeApiImpl implements ChangeApi {
|
|||||||
public void setHashtags(HashtagsInput input) throws RestApiException {
|
public void setHashtags(HashtagsInput input) throws RestApiException {
|
||||||
try {
|
try {
|
||||||
postHashtags.apply(change, input);
|
postHashtags.apply(change, input);
|
||||||
} catch (IOException | OrmException e) {
|
} catch (RestApiException | UpdateException e) {
|
||||||
throw new RestApiException("Cannot post hashtags", e);
|
throw new RestApiException("Cannot post hashtags", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,6 @@ import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
|
|||||||
|
|
||||||
import com.google.gerrit.common.ChangeHooks;
|
import com.google.gerrit.common.ChangeHooks;
|
||||||
import com.google.gerrit.common.data.LabelTypes;
|
import com.google.gerrit.common.data.LabelTypes;
|
||||||
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
@ -34,7 +32,6 @@ import com.google.gerrit.server.ApprovalsUtil;
|
|||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
import com.google.gerrit.server.ChangeUtil;
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.account.AccountCache;
|
|
||||||
import com.google.gerrit.server.events.CommitReceivedEvent;
|
import com.google.gerrit.server.events.CommitReceivedEvent;
|
||||||
import com.google.gerrit.server.git.BanCommit;
|
import com.google.gerrit.server.git.BanCommit;
|
||||||
import com.google.gerrit.server.git.BatchUpdate;
|
import com.google.gerrit.server.git.BatchUpdate;
|
||||||
@ -53,7 +50,6 @@ import com.google.gerrit.server.project.InvalidChangeOperationException;
|
|||||||
import com.google.gerrit.server.project.RefControl;
|
import com.google.gerrit.server.project.RefControl;
|
||||||
import com.google.gerrit.server.ssh.NoSshInfo;
|
import com.google.gerrit.server.ssh.NoSshInfo;
|
||||||
import com.google.gerrit.server.util.RequestScopePropagator;
|
import com.google.gerrit.server.util.RequestScopePropagator;
|
||||||
import com.google.gerrit.server.validators.ValidationException;
|
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
@ -83,8 +79,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
private final ApprovalsUtil approvalsUtil;
|
private final ApprovalsUtil approvalsUtil;
|
||||||
private final ChangeMessagesUtil cmUtil;
|
private final ChangeMessagesUtil cmUtil;
|
||||||
private final CreateChangeSender.Factory createChangeSenderFactory;
|
private final CreateChangeSender.Factory createChangeSenderFactory;
|
||||||
private final HashtagsUtil hashtagsUtil;
|
|
||||||
private final AccountCache accountCache;
|
|
||||||
private final WorkQueue workQueue;
|
private final WorkQueue workQueue;
|
||||||
private final CommitValidators.Factory commitValidatorsFactory;
|
private final CommitValidators.Factory commitValidatorsFactory;
|
||||||
|
|
||||||
@ -101,7 +95,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
private Set<Account.Id> reviewers;
|
private Set<Account.Id> reviewers;
|
||||||
private Set<Account.Id> extraCC;
|
private Set<Account.Id> extraCC;
|
||||||
private Map<String, Short> approvals;
|
private Map<String, Short> approvals;
|
||||||
private Set<String> hashtags;
|
|
||||||
private RequestScopePropagator requestScopePropagator;
|
private RequestScopePropagator requestScopePropagator;
|
||||||
private boolean runHooks;
|
private boolean runHooks;
|
||||||
private boolean sendMail;
|
private boolean sendMail;
|
||||||
@ -117,8 +110,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
ApprovalsUtil approvalsUtil,
|
ApprovalsUtil approvalsUtil,
|
||||||
ChangeMessagesUtil cmUtil,
|
ChangeMessagesUtil cmUtil,
|
||||||
CreateChangeSender.Factory createChangeSenderFactory,
|
CreateChangeSender.Factory createChangeSenderFactory,
|
||||||
HashtagsUtil hashtagsUtil,
|
|
||||||
AccountCache accountCache,
|
|
||||||
WorkQueue workQueue,
|
WorkQueue workQueue,
|
||||||
CommitValidators.Factory commitValidatorsFactory,
|
CommitValidators.Factory commitValidatorsFactory,
|
||||||
@Assisted RefControl refControl,
|
@Assisted RefControl refControl,
|
||||||
@ -136,8 +127,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
this.approvalsUtil = approvalsUtil;
|
this.approvalsUtil = approvalsUtil;
|
||||||
this.cmUtil = cmUtil;
|
this.cmUtil = cmUtil;
|
||||||
this.createChangeSenderFactory = createChangeSenderFactory;
|
this.createChangeSenderFactory = createChangeSenderFactory;
|
||||||
this.hashtagsUtil = hashtagsUtil;
|
|
||||||
this.accountCache = accountCache;
|
|
||||||
this.workQueue = workQueue;
|
this.workQueue = workQueue;
|
||||||
this.commitValidatorsFactory = commitValidatorsFactory;
|
this.commitValidatorsFactory = commitValidatorsFactory;
|
||||||
|
|
||||||
@ -147,7 +136,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
this.reviewers = Collections.emptySet();
|
this.reviewers = Collections.emptySet();
|
||||||
this.extraCC = Collections.emptySet();
|
this.extraCC = Collections.emptySet();
|
||||||
this.approvals = Collections.emptyMap();
|
this.approvals = Collections.emptyMap();
|
||||||
this.hashtags = Collections.emptySet();
|
|
||||||
this.runHooks = true;
|
this.runHooks = true;
|
||||||
this.sendMail = true;
|
this.sendMail = true;
|
||||||
this.updateRef = true;
|
this.updateRef = true;
|
||||||
@ -206,11 +194,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeInserter setHashtags(Set<String> hashtags) {
|
|
||||||
this.hashtags = hashtags;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangeInserter setRunHooks(boolean runHooks) {
|
public ChangeInserter setRunHooks(boolean runHooks) {
|
||||||
this.runHooks = runHooks;
|
this.runHooks = runHooks;
|
||||||
return this;
|
return this;
|
||||||
@ -287,18 +270,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
changeMessage.setMessage(message);
|
changeMessage.setMessage(message);
|
||||||
cmUtil.addChangeMessage(db, update, changeMessage);
|
cmUtil.addChangeMessage(db, update, changeMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hashtags != null && hashtags.size() > 0) {
|
|
||||||
try {
|
|
||||||
HashtagsInput input = new HashtagsInput();
|
|
||||||
input.add = hashtags;
|
|
||||||
// TODO(dborowitz): Migrate HashtagsUtil so it doesn't create another
|
|
||||||
// ChangeUpdate.
|
|
||||||
hashtagsUtil.setHashtags(ctl, input, false, false);
|
|
||||||
} catch (ValidationException | AuthException e) {
|
|
||||||
log.error("Cannot add hashtags to change " + change.getId(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -335,12 +306,6 @@ public class ChangeInserter extends BatchUpdate.InsertChangeOp {
|
|||||||
if (runHooks) {
|
if (runHooks) {
|
||||||
ReviewDb db = ctx.getDb();
|
ReviewDb db = ctx.getDb();
|
||||||
hooks.doPatchsetCreatedHook(change, patchSet, db);
|
hooks.doPatchsetCreatedHook(change, patchSet, db);
|
||||||
if (hashtags != null && hashtags.size() > 0) {
|
|
||||||
hooks.doHashtagsChangedHook(change,
|
|
||||||
accountCache.get(change.getOwner()).getAccount(),
|
|
||||||
hashtags, null, hashtags, db);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (approvals != null && !approvals.isEmpty()) {
|
if (approvals != null && !approvals.isEmpty()) {
|
||||||
hooks.doCommentAddedHook(change,
|
hooks.doCommentAddedHook(change,
|
||||||
((IdentifiedUser) refControl.getCurrentUser()).getAccount(),
|
((IdentifiedUser) refControl.getCurrentUser()).getAccount(),
|
||||||
|
@ -16,56 +16,18 @@ package com.google.gerrit.server.change;
|
|||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.gerrit.common.ChangeHooks;
|
|
||||||
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
|
||||||
import com.google.gerrit.extensions.registration.DynamicSet;
|
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
|
||||||
import com.google.gerrit.server.index.ChangeIndexer;
|
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
|
||||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
|
||||||
import com.google.gerrit.server.validators.HashtagValidationListener;
|
|
||||||
import com.google.gerrit.server.validators.ValidationException;
|
|
||||||
import com.google.gwtorm.server.OrmException;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class HashtagsUtil {
|
public class HashtagsUtil {
|
||||||
private static final CharMatcher LEADER =
|
private static final CharMatcher LEADER =
|
||||||
CharMatcher.whitespace().or(CharMatcher.is('#'));
|
CharMatcher.whitespace().or(CharMatcher.is('#'));
|
||||||
private static final String PATTERN = "(?:\\s|\\A)#[\\p{L}[0-9]-_]+";
|
private static final String PATTERN = "(?:\\s|\\A)#[\\p{L}[0-9]-_]+";
|
||||||
|
|
||||||
private final ChangeUpdate.Factory updateFactory;
|
|
||||||
private final Provider<ReviewDb> dbProvider;
|
|
||||||
private final ChangeIndexer indexer;
|
|
||||||
private final ChangeHooks hooks;
|
|
||||||
private final DynamicSet<HashtagValidationListener> hashtagValidationListeners;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
HashtagsUtil(ChangeUpdate.Factory updateFactory,
|
|
||||||
Provider<ReviewDb> dbProvider,
|
|
||||||
ChangeIndexer indexer,
|
|
||||||
ChangeHooks hooks,
|
|
||||||
DynamicSet<HashtagValidationListener> hashtagValidationListeners) {
|
|
||||||
this.updateFactory = updateFactory;
|
|
||||||
this.dbProvider = dbProvider;
|
|
||||||
this.indexer = indexer;
|
|
||||||
this.hooks = hooks;
|
|
||||||
this.hashtagValidationListeners = hashtagValidationListeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String cleanupHashtag(String hashtag) {
|
public static String cleanupHashtag(String hashtag) {
|
||||||
hashtag = LEADER.trimLeadingFrom(hashtag);
|
hashtag = LEADER.trimLeadingFrom(hashtag);
|
||||||
hashtag = CharMatcher.whitespace().trimTrailingFrom(hashtag);
|
hashtag = CharMatcher.whitespace().trimTrailingFrom(hashtag);
|
||||||
@ -83,7 +45,7 @@ public class HashtagsUtil {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> extractTags(Set<String> input)
|
static Set<String> extractTags(Set<String> input)
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
@ -102,54 +64,6 @@ public class HashtagsUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TreeSet<String> setHashtags(ChangeControl control,
|
private HashtagsUtil() {
|
||||||
HashtagsInput input, boolean runHooks, boolean index)
|
|
||||||
throws IllegalArgumentException, IOException,
|
|
||||||
ValidationException, AuthException, OrmException {
|
|
||||||
if (input == null
|
|
||||||
|| (input.add == null && input.remove == null)) {
|
|
||||||
throw new IllegalArgumentException("Hashtags are required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!control.canEditHashtags()) {
|
|
||||||
throw new AuthException("Editing hashtags not permitted");
|
|
||||||
}
|
|
||||||
ChangeUpdate update = updateFactory.create(control);
|
|
||||||
ChangeNotes notes = control.getNotes().load();
|
|
||||||
|
|
||||||
Set<String> existingHashtags = notes.getHashtags();
|
|
||||||
Set<String> updatedHashtags = new HashSet<>();
|
|
||||||
Set<String> toAdd = new HashSet<>(extractTags(input.add));
|
|
||||||
Set<String> toRemove = new HashSet<>(extractTags(input.remove));
|
|
||||||
|
|
||||||
for (HashtagValidationListener validator : hashtagValidationListeners) {
|
|
||||||
validator.validateHashtags(update.getChange(), toAdd, toRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingHashtags != null && !existingHashtags.isEmpty()) {
|
|
||||||
updatedHashtags.addAll(existingHashtags);
|
|
||||||
toAdd.removeAll(existingHashtags);
|
|
||||||
toRemove.retainAll(existingHashtags);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toAdd.size() > 0 || toRemove.size() > 0) {
|
|
||||||
updatedHashtags.addAll(toAdd);
|
|
||||||
updatedHashtags.removeAll(toRemove);
|
|
||||||
update.setHashtags(updatedHashtags);
|
|
||||||
update.commit();
|
|
||||||
|
|
||||||
if (index) {
|
|
||||||
indexer.index(dbProvider.get(), update.getChange());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runHooks) {
|
|
||||||
IdentifiedUser currentUser = ((IdentifiedUser) control.getCurrentUser());
|
|
||||||
hooks.doHashtagsChangedHook(
|
|
||||||
update.getChange(), currentUser.getAccount(),
|
|
||||||
toAdd, toRemove, updatedHashtags,
|
|
||||||
dbProvider.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new TreeSet<>(updatedHashtags);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,12 +119,13 @@ public class Module extends RestApiModule {
|
|||||||
get(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Get.class);
|
get(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Get.class);
|
||||||
get(CHANGE_EDIT_KIND, "meta").to(ChangeEdits.GetMeta.class);
|
get(CHANGE_EDIT_KIND, "meta").to(ChangeEdits.GetMeta.class);
|
||||||
|
|
||||||
factory(ReviewerResource.Factory.class);
|
|
||||||
factory(AccountLoader.Factory.class);
|
factory(AccountLoader.Factory.class);
|
||||||
factory(EmailReviewComments.Factory.class);
|
|
||||||
factory(ChangeInserter.Factory.class);
|
|
||||||
factory(PatchSetInserter.Factory.class);
|
|
||||||
factory(ChangeEdits.Create.Factory.class);
|
factory(ChangeEdits.Create.Factory.class);
|
||||||
factory(ChangeEdits.DeleteFile.Factory.class);
|
factory(ChangeEdits.DeleteFile.Factory.class);
|
||||||
|
factory(ChangeInserter.Factory.class);
|
||||||
|
factory(EmailReviewComments.Factory.class);
|
||||||
|
factory(PatchSetInserter.Factory.class);
|
||||||
|
factory(ReviewerResource.Factory.class);
|
||||||
|
factory(SetHashtagsOp.Factory.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,43 +14,47 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.change;
|
package com.google.gerrit.server.change;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
|
import com.google.gerrit.common.TimeUtil;
|
||||||
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
|
||||||
import com.google.gerrit.extensions.restapi.Response;
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
import com.google.gerrit.extensions.webui.UiAction;
|
import com.google.gerrit.extensions.webui.UiAction;
|
||||||
import com.google.gerrit.server.validators.ValidationException;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gerrit.server.git.BatchUpdate;
|
||||||
|
import com.google.gerrit.server.git.UpdateException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class PostHashtags implements RestModifyView<ChangeResource, HashtagsInput>,
|
public class PostHashtags
|
||||||
|
implements RestModifyView<ChangeResource, HashtagsInput>,
|
||||||
UiAction<ChangeResource> {
|
UiAction<ChangeResource> {
|
||||||
private HashtagsUtil hashtagsUtil;
|
private final Provider<ReviewDb> db;
|
||||||
|
private final BatchUpdate.Factory batchUpdateFactory;
|
||||||
|
private final SetHashtagsOp.Factory hashtagsFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PostHashtags(HashtagsUtil hashtagsUtil) {
|
PostHashtags(Provider<ReviewDb> db,
|
||||||
this.hashtagsUtil = hashtagsUtil;
|
BatchUpdate.Factory batchUpdateFactory,
|
||||||
|
SetHashtagsOp.Factory hashtagsFactory) {
|
||||||
|
this.db = db;
|
||||||
|
this.batchUpdateFactory = batchUpdateFactory;
|
||||||
|
this.hashtagsFactory = hashtagsFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<Set<String>> apply(ChangeResource req, HashtagsInput input)
|
public Response<ImmutableSortedSet<String>> apply(ChangeResource req,
|
||||||
throws AuthException, OrmException, IOException, BadRequestException,
|
HashtagsInput input) throws RestApiException, UpdateException {
|
||||||
ResourceConflictException {
|
try (BatchUpdate bu = batchUpdateFactory.create(db.get(),
|
||||||
|
req.getChange().getProject(), req.getControl().getCurrentUser(),
|
||||||
try {
|
TimeUtil.nowTs())) {
|
||||||
return Response.<Set<String>> ok(hashtagsUtil.setHashtags(
|
SetHashtagsOp op = hashtagsFactory.create(input);
|
||||||
req.getControl(), input, true, true));
|
bu.addOp(req.getChange().getId(), op);
|
||||||
} catch (IllegalArgumentException e) {
|
bu.execute();
|
||||||
throw new BadRequestException(e.getMessage());
|
return Response.<ImmutableSortedSet<String>> ok(op.getUpdatedHashtags());
|
||||||
} catch (ValidationException e) {
|
|
||||||
throw new ResourceConflictException(e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (C) 2015 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.change;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static com.google.gerrit.server.change.HashtagsUtil.extractTags;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
|
import com.google.gerrit.common.ChangeHooks;
|
||||||
|
import com.google.gerrit.common.Nullable;
|
||||||
|
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
||||||
|
import com.google.gerrit.extensions.registration.DynamicSet;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.git.BatchUpdate;
|
||||||
|
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
|
||||||
|
import com.google.gerrit.server.git.BatchUpdate.Context;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||||
|
import com.google.gerrit.server.validators.HashtagValidationListener;
|
||||||
|
import com.google.gerrit.server.validators.ValidationException;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import com.google.inject.assistedinject.AssistedInject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SetHashtagsOp extends BatchUpdate.Op {
|
||||||
|
public interface Factory {
|
||||||
|
SetHashtagsOp create(HashtagsInput input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ChangeHooks hooks;
|
||||||
|
private final DynamicSet<HashtagValidationListener> validationListeners;
|
||||||
|
private final HashtagsInput input;
|
||||||
|
|
||||||
|
private boolean runHooks = true;
|
||||||
|
|
||||||
|
private Change change;
|
||||||
|
private Set<String> toAdd;
|
||||||
|
private Set<String> toRemove;
|
||||||
|
private ImmutableSortedSet<String> updatedHashtags;
|
||||||
|
|
||||||
|
@AssistedInject
|
||||||
|
SetHashtagsOp(
|
||||||
|
ChangeHooks hooks,
|
||||||
|
DynamicSet<HashtagValidationListener> validationListeners,
|
||||||
|
@Assisted @Nullable HashtagsInput input) {
|
||||||
|
this.hooks = hooks;
|
||||||
|
this.validationListeners = validationListeners;
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SetHashtagsOp setRunHooks(boolean runHooks) {
|
||||||
|
this.runHooks = runHooks;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateChange(ChangeContext ctx)
|
||||||
|
throws AuthException, BadRequestException, OrmException, IOException {
|
||||||
|
if (input == null
|
||||||
|
|| (input.add == null && input.remove == null)) {
|
||||||
|
updatedHashtags = ImmutableSortedSet.of();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ctx.getChangeControl().canEditHashtags()) {
|
||||||
|
throw new AuthException("Editing hashtags not permitted");
|
||||||
|
}
|
||||||
|
ChangeUpdate update = ctx.getChangeUpdate();
|
||||||
|
ChangeNotes notes = update.getChangeNotes().load();
|
||||||
|
|
||||||
|
Set<String> existingHashtags = notes.getHashtags();
|
||||||
|
Set<String> updated = new HashSet<>();
|
||||||
|
toAdd = new HashSet<>(extractTags(input.add));
|
||||||
|
toRemove = new HashSet<>(extractTags(input.remove));
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (HashtagValidationListener validator : validationListeners) {
|
||||||
|
validator.validateHashtags(update.getChange(), toAdd, toRemove);
|
||||||
|
}
|
||||||
|
} catch (ValidationException e) {
|
||||||
|
throw new BadRequestException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingHashtags != null && !existingHashtags.isEmpty()) {
|
||||||
|
updated.addAll(existingHashtags);
|
||||||
|
toAdd.removeAll(existingHashtags);
|
||||||
|
toRemove.retainAll(existingHashtags);
|
||||||
|
}
|
||||||
|
if (updated()) {
|
||||||
|
updated.addAll(toAdd);
|
||||||
|
updated.removeAll(toRemove);
|
||||||
|
update.setHashtags(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
change = update.getChange();
|
||||||
|
updatedHashtags = ImmutableSortedSet.copyOf(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postUpdate(Context ctx) throws OrmException {
|
||||||
|
if (updated() && runHooks) {
|
||||||
|
IdentifiedUser currentUser = (IdentifiedUser) ctx.getUser();
|
||||||
|
hooks.doHashtagsChangedHook(
|
||||||
|
change, currentUser.getAccount(),
|
||||||
|
toAdd, toRemove, updatedHashtags,
|
||||||
|
ctx.getDb());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableSortedSet<String> getUpdatedHashtags() {
|
||||||
|
checkState(updatedHashtags != null,
|
||||||
|
"getUpdatedHashtags() only valid after executing op");
|
||||||
|
return updatedHashtags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updated() {
|
||||||
|
return !isNullOrEmpty(toAdd) || !isNullOrEmpty(toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNullOrEmpty(Collection<?> coll) {
|
||||||
|
return coll == null || coll.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
@ -85,6 +85,10 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
public ReviewDb getDb() {
|
public ReviewDb getDb() {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CurrentUser getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RepoContext extends Context {
|
public class RepoContext extends Context {
|
||||||
@ -136,10 +140,6 @@ public class BatchUpdate implements AutoCloseable {
|
|||||||
public Change getChange() {
|
public Change getChange() {
|
||||||
return update.getChange();
|
return update.getChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurrentUser getUser() {
|
|
||||||
return update.getUser();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Op {
|
public static class Op {
|
||||||
|
@ -62,6 +62,7 @@ import com.google.gerrit.common.data.LabelType;
|
|||||||
import com.google.gerrit.common.data.LabelTypes;
|
import com.google.gerrit.common.data.LabelTypes;
|
||||||
import com.google.gerrit.common.data.Permission;
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.common.data.PermissionRule;
|
import com.google.gerrit.common.data.PermissionRule;
|
||||||
|
import com.google.gerrit.extensions.api.changes.HashtagsInput;
|
||||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||||
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
|
import com.google.gerrit.extensions.registration.DynamicMap.Entry;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
@ -90,6 +91,7 @@ import com.google.gerrit.server.change.ChangeKind;
|
|||||||
import com.google.gerrit.server.change.ChangeKindCache;
|
import com.google.gerrit.server.change.ChangeKindCache;
|
||||||
import com.google.gerrit.server.change.ChangesCollection;
|
import com.google.gerrit.server.change.ChangesCollection;
|
||||||
import com.google.gerrit.server.change.RevisionResource;
|
import com.google.gerrit.server.change.RevisionResource;
|
||||||
|
import com.google.gerrit.server.change.SetHashtagsOp;
|
||||||
import com.google.gerrit.server.change.Submit;
|
import com.google.gerrit.server.change.Submit;
|
||||||
import com.google.gerrit.server.config.AllProjectsName;
|
import com.google.gerrit.server.config.AllProjectsName;
|
||||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||||
@ -308,6 +310,7 @@ public class ReceiveCommits {
|
|||||||
private final ReceiveConfig receiveConfig;
|
private final ReceiveConfig receiveConfig;
|
||||||
private final ChangeKindCache changeKindCache;
|
private final ChangeKindCache changeKindCache;
|
||||||
private final BatchUpdate.Factory batchUpdateFactory;
|
private final BatchUpdate.Factory batchUpdateFactory;
|
||||||
|
private final SetHashtagsOp.Factory hashtagsFactory;
|
||||||
|
|
||||||
private final ProjectControl projectControl;
|
private final ProjectControl projectControl;
|
||||||
private final Project project;
|
private final Project project;
|
||||||
@ -385,7 +388,8 @@ public class ReceiveCommits {
|
|||||||
final DynamicMap<ProjectConfigEntry> pluginConfigEntries,
|
final DynamicMap<ProjectConfigEntry> pluginConfigEntries,
|
||||||
final NotesMigration notesMigration,
|
final NotesMigration notesMigration,
|
||||||
final ChangeEditUtil editUtil,
|
final ChangeEditUtil editUtil,
|
||||||
final BatchUpdate.Factory batchUpdateFactory) throws IOException {
|
final BatchUpdate.Factory batchUpdateFactory,
|
||||||
|
final SetHashtagsOp.Factory hashtagsFactory) throws IOException {
|
||||||
this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
|
this.currentUser = (IdentifiedUser) projectControl.getCurrentUser();
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.queryProvider = queryProvider;
|
this.queryProvider = queryProvider;
|
||||||
@ -419,6 +423,7 @@ public class ReceiveCommits {
|
|||||||
this.receiveConfig = config;
|
this.receiveConfig = config;
|
||||||
this.changeKindCache = changeKindCache;
|
this.changeKindCache = changeKindCache;
|
||||||
this.batchUpdateFactory = batchUpdateFactory;
|
this.batchUpdateFactory = batchUpdateFactory;
|
||||||
|
this.hashtagsFactory = hashtagsFactory;
|
||||||
|
|
||||||
this.projectControl = projectControl;
|
this.projectControl = projectControl;
|
||||||
this.labelTypes = projectControl.getLabelTypes();
|
this.labelTypes = projectControl.getLabelTypes();
|
||||||
@ -1765,7 +1770,6 @@ public class ReceiveCommits {
|
|||||||
if (magicBranch != null) {
|
if (magicBranch != null) {
|
||||||
recipients.add(magicBranch.getMailRecipients());
|
recipients.add(magicBranch.getMailRecipients());
|
||||||
approvals = magicBranch.labels;
|
approvals = magicBranch.labels;
|
||||||
ins.setHashtags(magicBranch.hashtags);
|
|
||||||
}
|
}
|
||||||
recipients.add(getRecipientsFromFooters(accountResolver, ps, footerLines));
|
recipients.add(getRecipientsFromFooters(accountResolver, ps, footerLines));
|
||||||
recipients.remove(me);
|
recipients.remove(me);
|
||||||
@ -1783,6 +1787,12 @@ public class ReceiveCommits {
|
|||||||
.setRequestScopePropagator(requestScopePropagator)
|
.setRequestScopePropagator(requestScopePropagator)
|
||||||
.setSendMail(true)
|
.setSendMail(true)
|
||||||
.setUpdateRef(false));
|
.setUpdateRef(false));
|
||||||
|
if (magicBranch != null) {
|
||||||
|
bu.addOp(
|
||||||
|
ins.getChange().getId(),
|
||||||
|
hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
|
||||||
|
.setRunHooks(false));
|
||||||
|
}
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
created = true;
|
created = true;
|
||||||
|
@ -28,7 +28,7 @@ public class HashtagsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void nullCommitMessage() throws Exception {
|
public void nullCommitMessage() throws Exception {
|
||||||
assertThat(HashtagsUtil.extractTags(null)).isEmpty();
|
assertThat(HashtagsUtil.extractTags((String) null)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
Reference in New Issue
Block a user