Merge changes from topic 'reviewers-by-email'
* changes: Add reviewers by email to REST API and Email sender Add reviewer.enableByEmail to ProjectConfig Add reviewers by email to NoteDb
This commit is contained in:
@@ -291,6 +291,18 @@ Branches not listed in this section will not be included in the mergeability
|
|||||||
check. If the `branchOrder` section is not defined then the mergeability of a
|
check. If the `branchOrder` section is not defined then the mergeability of a
|
||||||
change into other branches will not be done.
|
change into other branches will not be done.
|
||||||
|
|
||||||
|
[[reviewer-section]]
|
||||||
|
=== reviewer section
|
||||||
|
|
||||||
|
Defines config options to adjust a project's reviewer workflow such as enabling
|
||||||
|
reviewers and CCs by email.
|
||||||
|
|
||||||
|
[[reviewer.enableByEmail]]reviewer.enableByEmail::
|
||||||
|
+
|
||||||
|
A boolean indicating if reviewers and CCs that do not currently have a Gerrit
|
||||||
|
account can be added to a change by providing their email address.
|
||||||
|
|
||||||
|
Defaults to `false`.
|
||||||
|
|
||||||
[[file-groups]]
|
[[file-groups]]
|
||||||
== The file +groups+
|
== The file +groups+
|
||||||
|
|||||||
@@ -2805,6 +2805,41 @@ To confirm the addition of the reviewers, resend the request with the
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
If link:config-project-config.html#reviewer.enableByEmail[reviewer.enableByEmail] is set
|
||||||
|
for the project, reviewers and CCs are not required to have a Gerrit account. If you POST
|
||||||
|
an email address of a reviewer or CC then, they will be added to the change even if they
|
||||||
|
don't have a Gerrit account.
|
||||||
|
|
||||||
|
If this option is disabled, the request would fail with `400 Bad Request` if the email
|
||||||
|
address can't be resolved to an active Gerrit account.
|
||||||
|
|
||||||
|
Note that the name is optional so both "un.registered@reviewer.com" and
|
||||||
|
"John Doe <un.registered@reviewer.com>" are valid inputs.
|
||||||
|
|
||||||
|
Reviewers without Gerrit accounts can only be added on changes visible to anonymous users.
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/reviewers HTTP/1.0
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
{
|
||||||
|
"reviewer": "John Doe <un.registered@reviewer.com>"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Response
|
||||||
|
----
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Disposition: attachment
|
||||||
|
Content-Type: application/json; charset=UTF-8
|
||||||
|
|
||||||
|
)]}'
|
||||||
|
{
|
||||||
|
"input": "John Doe <un.registered@reviewer.com>"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
[[delete-reviewer]]
|
[[delete-reviewer]]
|
||||||
=== Delete Reviewer
|
=== Delete Reviewer
|
||||||
--
|
--
|
||||||
@@ -6183,6 +6218,10 @@ In addition `ReviewerInfo` has the following fields:
|
|||||||
|`approvals` |
|
|`approvals` |
|
||||||
The approvals of the reviewer as a map that maps the label names to the
|
The approvals of the reviewer as a map that maps the label names to the
|
||||||
approval values ("`-2`", "`-1`", "`0`", "`+1`", "`+2`").
|
approval values ("`-2`", "`-1`", "`0`", "`+1`", "`+2`").
|
||||||
|
|`_account_id` |
|
||||||
|
This field is inherited from `AccountInfo` but is optional here if an
|
||||||
|
unregistered reviewer was added by email. See
|
||||||
|
link:rest-api-changes.html#add-reviewer[add-reviewer] for details.
|
||||||
|==========================
|
|==========================
|
||||||
|
|
||||||
[[reviewer-input]]
|
[[reviewer-input]]
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ import com.google.gerrit.server.group.SystemGroupBackend;
|
|||||||
import com.google.gerrit.server.index.change.ChangeIndex;
|
import com.google.gerrit.server.index.change.ChangeIndex;
|
||||||
import com.google.gerrit.server.index.change.ChangeIndexCollection;
|
import com.google.gerrit.server.index.change.ChangeIndexCollection;
|
||||||
import com.google.gerrit.server.index.change.ChangeIndexer;
|
import com.google.gerrit.server.index.change.ChangeIndexer;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.mail.send.EmailHeader;
|
import com.google.gerrit.server.mail.send.EmailHeader;
|
||||||
import com.google.gerrit.server.notedb.ChangeNoteUtil;
|
import com.google.gerrit.server.notedb.ChangeNoteUtil;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
@@ -1200,21 +1201,29 @@ public abstract class AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void assertNotifyTo(TestAccount expected) {
|
protected void assertNotifyTo(TestAccount expected) {
|
||||||
|
assertNotifyTo(expected.emailAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertNotifyTo(Address expected) {
|
||||||
assertThat(sender.getMessages()).hasSize(1);
|
assertThat(sender.getMessages()).hasSize(1);
|
||||||
Message m = sender.getMessages().get(0);
|
Message m = sender.getMessages().get(0);
|
||||||
assertThat(m.rcpt()).containsExactly(expected.emailAddress);
|
assertThat(m.rcpt()).containsExactly(expected);
|
||||||
assertThat(((EmailHeader.AddressList) m.headers().get("To")).getAddressList())
|
assertThat(((EmailHeader.AddressList) m.headers().get("To")).getAddressList())
|
||||||
.containsExactly(expected.emailAddress);
|
.containsExactly(expected);
|
||||||
assertThat(m.headers().get("CC").isEmpty()).isTrue();
|
assertThat(m.headers().get("CC").isEmpty()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertNotifyCc(TestAccount expected) {
|
protected void assertNotifyCc(TestAccount expected) {
|
||||||
|
assertNotifyCc(expected.emailAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertNotifyCc(Address expected) {
|
||||||
assertThat(sender.getMessages()).hasSize(1);
|
assertThat(sender.getMessages()).hasSize(1);
|
||||||
Message m = sender.getMessages().get(0);
|
Message m = sender.getMessages().get(0);
|
||||||
assertThat(m.rcpt()).containsExactly(expected.emailAddress);
|
assertThat(m.rcpt()).containsExactly(expected);
|
||||||
assertThat(m.headers().get("To").isEmpty()).isTrue();
|
assertThat(m.headers().get("To").isEmpty()).isTrue();
|
||||||
assertThat(((EmailHeader.AddressList) m.headers().get("CC")).getAddressList())
|
assertThat(((EmailHeader.AddressList) m.headers().get("CC")).getAddressList())
|
||||||
.containsExactly(expected.emailAddress);
|
.containsExactly(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertNotifyBcc(TestAccount expected) {
|
protected void assertNotifyBcc(TestAccount expected) {
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
// Copyright (C) 2017 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.acceptance.rest.change;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.TruthJUnit.assume;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
|
import com.google.gerrit.acceptance.NoHttpd;
|
||||||
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
|
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
||||||
|
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||||
|
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||||
|
import com.google.gerrit.extensions.client.ReviewerState;
|
||||||
|
import com.google.gerrit.extensions.common.AccountInfo;
|
||||||
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
|
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||||
|
import com.google.gerrit.server.git.ProjectConfig;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
|
import com.google.gerrit.testutil.FakeEmailSender.Message;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@NoHttpd
|
||||||
|
public class ChangeReviewersByEmailIT extends AbstractDaemonTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
||||||
|
cfg.setEnableReviewerByEmail(true);
|
||||||
|
saveProjectConfig(project, cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addByEmail() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
AddReviewerInput input = new AddReviewerInput();
|
||||||
|
input.reviewer = toRfcAddressString(acc);
|
||||||
|
input.state = state;
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer(input);
|
||||||
|
|
||||||
|
ChangeInfo info =
|
||||||
|
gApi.changes().id(r.getChangeId()).get(EnumSet.of(ListChangesOption.DETAILED_LABELS));
|
||||||
|
assertThat(info.reviewers).isEqualTo(ImmutableMap.of(state, ImmutableList.of(acc)));
|
||||||
|
// All reviewers added by email should be removable
|
||||||
|
assertThat(info.removableReviewers).isEqualTo(ImmutableList.of(acc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeByEmail() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
AddReviewerInput addInput = new AddReviewerInput();
|
||||||
|
addInput.reviewer = toRfcAddressString(acc);
|
||||||
|
addInput.state = state;
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer(addInput);
|
||||||
|
|
||||||
|
gApi.changes().id(r.getChangeId()).reviewer(acc.email).remove();
|
||||||
|
|
||||||
|
ChangeInfo info =
|
||||||
|
gApi.changes().id(r.getChangeId()).get(EnumSet.of(ListChangesOption.DETAILED_LABELS));
|
||||||
|
assertThat(info.reviewers).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertFromCCToReviewer() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
AddReviewerInput addInput = new AddReviewerInput();
|
||||||
|
addInput.reviewer = toRfcAddressString(acc);
|
||||||
|
addInput.state = ReviewerState.CC;
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer(addInput);
|
||||||
|
|
||||||
|
AddReviewerInput modifyInput = new AddReviewerInput();
|
||||||
|
modifyInput.reviewer = addInput.reviewer;
|
||||||
|
modifyInput.state = ReviewerState.REVIEWER;
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer(modifyInput);
|
||||||
|
|
||||||
|
ChangeInfo info =
|
||||||
|
gApi.changes().id(r.getChangeId()).get(EnumSet.of(ListChangesOption.DETAILED_LABELS));
|
||||||
|
assertThat(info.reviewers)
|
||||||
|
.isEqualTo(ImmutableMap.of(ReviewerState.REVIEWER, ImmutableList.of(acc)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addedReviewersGetNotified() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
AddReviewerInput input = new AddReviewerInput();
|
||||||
|
input.reviewer = toRfcAddressString(acc);
|
||||||
|
input.state = state;
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer(input);
|
||||||
|
|
||||||
|
List<Message> messages = sender.getMessages();
|
||||||
|
assertThat(messages).hasSize(1);
|
||||||
|
assertThat(messages.get(0).rcpt()).containsExactly(Address.parse(input.reviewer));
|
||||||
|
sender.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removingReviewerTriggersNotification() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
AddReviewerInput addInput = new AddReviewerInput();
|
||||||
|
addInput.reviewer = toRfcAddressString(acc);
|
||||||
|
addInput.state = state;
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer(addInput);
|
||||||
|
|
||||||
|
// Review change as user
|
||||||
|
ReviewInput reviewInput = new ReviewInput();
|
||||||
|
reviewInput.message = "I have a comment";
|
||||||
|
setApiUser(user);
|
||||||
|
revision(r).review(reviewInput);
|
||||||
|
setApiUser(admin);
|
||||||
|
|
||||||
|
sender.clear();
|
||||||
|
|
||||||
|
// Delete as admin
|
||||||
|
gApi.changes().id(r.getChangeId()).reviewer(addInput.reviewer).remove();
|
||||||
|
|
||||||
|
List<Message> messages = sender.getMessages();
|
||||||
|
assertThat(messages).hasSize(1);
|
||||||
|
assertThat(messages.get(0).rcpt())
|
||||||
|
.containsExactly(Address.parse(addInput.reviewer), user.emailAddress);
|
||||||
|
sender.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reviewerAndCCReceiveRegularNotification() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
AddReviewerInput input = new AddReviewerInput();
|
||||||
|
input.reviewer = toRfcAddressString(acc);
|
||||||
|
input.state = state;
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer(input);
|
||||||
|
sender.clear();
|
||||||
|
|
||||||
|
gApi.changes()
|
||||||
|
.id(r.getChangeId())
|
||||||
|
.revision(r.getCommit().name())
|
||||||
|
.review(ReviewInput.approve());
|
||||||
|
|
||||||
|
if (state == ReviewerState.CC) {
|
||||||
|
assertNotifyCc(Address.parse(input.reviewer));
|
||||||
|
} else {
|
||||||
|
assertNotifyTo(Address.parse(input.reviewer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectMissingEmail() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
exception.expect(UnprocessableEntityException.class);
|
||||||
|
exception.expectMessage("email invalid");
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectMalformedEmail() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
exception.expect(UnprocessableEntityException.class);
|
||||||
|
exception.expectMessage("email invalid");
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer("Foo Bar <foo.bar@");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectOnNonPublicChange() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
PushOneCommit.Result r = createDraftChange();
|
||||||
|
|
||||||
|
exception.expect(BadRequestException.class);
|
||||||
|
exception.expectMessage("change is not publicly visible");
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer("Foo Bar <foo.bar@gerritcodereview.com>");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectWhenFeatureIsDisabled() throws Exception {
|
||||||
|
assume().that(notesMigration.readChanges()).isTrue();
|
||||||
|
|
||||||
|
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
||||||
|
cfg.setEnableReviewerByEmail(false);
|
||||||
|
saveProjectConfig(project, cfg);
|
||||||
|
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
|
||||||
|
exception.expect(UnprocessableEntityException.class);
|
||||||
|
exception.expectMessage(
|
||||||
|
"Foo Bar <foo.bar@gerritcodereview.com> does not identify a registered user or group");
|
||||||
|
gApi.changes().id(r.getChangeId()).addReviewer("Foo Bar <foo.bar@gerritcodereview.com>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toRfcAddressString(AccountInfo info) {
|
||||||
|
return (new Address(info.name, info.email)).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,13 @@ public class ReviewerInfo extends AccountInfo {
|
|||||||
*/
|
*/
|
||||||
@Nullable public Map<String, String> approvals;
|
@Nullable public Map<String, String> approvals;
|
||||||
|
|
||||||
|
public static ReviewerInfo byEmail(@Nullable String name, String email) {
|
||||||
|
ReviewerInfo info = new ReviewerInfo();
|
||||||
|
info.name = name;
|
||||||
|
info.email = email;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
public ReviewerInfo(Integer id) {
|
public ReviewerInfo(Integer id) {
|
||||||
super(id);
|
super(id);
|
||||||
}
|
}
|
||||||
@@ -33,4 +40,6 @@ public class ReviewerInfo extends AccountInfo {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReviewerInfo() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class ConfigInfo {
|
|||||||
public InheritedBooleanInfo enableSignedPush;
|
public InheritedBooleanInfo enableSignedPush;
|
||||||
public InheritedBooleanInfo requireSignedPush;
|
public InheritedBooleanInfo requireSignedPush;
|
||||||
public InheritedBooleanInfo rejectImplicitMerges;
|
public InheritedBooleanInfo rejectImplicitMerges;
|
||||||
|
public InheritedBooleanInfo enableReviewerByEmail;
|
||||||
public MaxObjectSizeLimitInfo maxObjectSizeLimit;
|
public MaxObjectSizeLimitInfo maxObjectSizeLimit;
|
||||||
public SubmitType submitType;
|
public SubmitType submitType;
|
||||||
public ProjectState state;
|
public ProjectState state;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package com.google.gerrit.extensions.common;
|
package com.google.gerrit.extensions.common;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class AccountInfo {
|
public class AccountInfo {
|
||||||
public Integer _accountId;
|
public Integer _accountId;
|
||||||
@@ -29,4 +30,34 @@ public class AccountInfo {
|
|||||||
public AccountInfo(Integer id) {
|
public AccountInfo(Integer id) {
|
||||||
this._accountId = id;
|
this._accountId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** To be used ONLY in connection with unregistered reviewers and CCs. */
|
||||||
|
public AccountInfo(String name, String email) {
|
||||||
|
this.name = name;
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof AccountInfo) {
|
||||||
|
AccountInfo accountInfo = (AccountInfo) o;
|
||||||
|
return Objects.equals(_accountId, accountInfo._accountId)
|
||||||
|
&& Objects.equals(name, accountInfo.name)
|
||||||
|
&& Objects.equals(email, accountInfo.email)
|
||||||
|
&& Objects.equals(secondaryEmails, accountInfo.secondaryEmails)
|
||||||
|
&& Objects.equals(username, accountInfo.username)
|
||||||
|
&& Objects.equals(avatars, accountInfo.avatars)
|
||||||
|
&& Objects.equals(_moreAccounts, accountInfo._moreAccounts)
|
||||||
|
&& Objects.equals(status, accountInfo.status);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
_accountId, name, email, secondaryEmails, username, avatars, _moreAccounts, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AccountInfo() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ public class ProjectInfoScreen extends ProjectScreen {
|
|||||||
private ListBox enableSignedPush;
|
private ListBox enableSignedPush;
|
||||||
private ListBox requireSignedPush;
|
private ListBox requireSignedPush;
|
||||||
private ListBox rejectImplicitMerges;
|
private ListBox rejectImplicitMerges;
|
||||||
|
private ListBox enableReviewerByEmail;
|
||||||
private NpTextBox maxObjectSizeLimit;
|
private NpTextBox maxObjectSizeLimit;
|
||||||
private Label effectiveMaxObjectSizeLimit;
|
private Label effectiveMaxObjectSizeLimit;
|
||||||
private Map<String, Map<String, HasEnabled>> pluginConfigWidgets;
|
private Map<String, Map<String, HasEnabled>> pluginConfigWidgets;
|
||||||
@@ -191,6 +192,7 @@ public class ProjectInfoScreen extends ProjectScreen {
|
|||||||
requireChangeID.setEnabled(isOwner);
|
requireChangeID.setEnabled(isOwner);
|
||||||
rejectImplicitMerges.setEnabled(isOwner);
|
rejectImplicitMerges.setEnabled(isOwner);
|
||||||
maxObjectSizeLimit.setEnabled(isOwner);
|
maxObjectSizeLimit.setEnabled(isOwner);
|
||||||
|
enableReviewerByEmail.setEnabled(isOwner);
|
||||||
|
|
||||||
if (pluginConfigWidgets != null) {
|
if (pluginConfigWidgets != null) {
|
||||||
for (Map<String, HasEnabled> widgetMap : pluginConfigWidgets.values()) {
|
for (Map<String, HasEnabled> widgetMap : pluginConfigWidgets.values()) {
|
||||||
@@ -264,6 +266,10 @@ public class ProjectInfoScreen extends ProjectScreen {
|
|||||||
saveEnabler.listenTo(rejectImplicitMerges);
|
saveEnabler.listenTo(rejectImplicitMerges);
|
||||||
grid.addHtml(AdminConstants.I.rejectImplicitMerges(), rejectImplicitMerges);
|
grid.addHtml(AdminConstants.I.rejectImplicitMerges(), rejectImplicitMerges);
|
||||||
|
|
||||||
|
enableReviewerByEmail = newInheritedBooleanBox();
|
||||||
|
saveEnabler.listenTo(enableReviewerByEmail);
|
||||||
|
grid.addHtml(AdminConstants.I.rejectImplicitMerges(), enableReviewerByEmail);
|
||||||
|
|
||||||
maxObjectSizeLimit = new NpTextBox();
|
maxObjectSizeLimit = new NpTextBox();
|
||||||
saveEnabler.listenTo(maxObjectSizeLimit);
|
saveEnabler.listenTo(maxObjectSizeLimit);
|
||||||
effectiveMaxObjectSizeLimit = new Label();
|
effectiveMaxObjectSizeLimit = new Label();
|
||||||
@@ -395,6 +401,7 @@ public class ProjectInfoScreen extends ProjectScreen {
|
|||||||
setBool(requireSignedPush, result.requireSignedPush());
|
setBool(requireSignedPush, result.requireSignedPush());
|
||||||
}
|
}
|
||||||
setBool(rejectImplicitMerges, result.rejectImplicitMerges());
|
setBool(rejectImplicitMerges, result.rejectImplicitMerges());
|
||||||
|
setBool(enableReviewerByEmail, result.enableReviewerByEmail());
|
||||||
setSubmitType(result.submitType());
|
setSubmitType(result.submitType());
|
||||||
setState(result.state());
|
setState(result.state());
|
||||||
maxObjectSizeLimit.setText(result.maxObjectSizeLimit().configuredValue());
|
maxObjectSizeLimit.setText(result.maxObjectSizeLimit().configuredValue());
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ public class ConfigInfo extends JavaScriptObject {
|
|||||||
public final native InheritedBooleanInfo rejectImplicitMerges()
|
public final native InheritedBooleanInfo rejectImplicitMerges()
|
||||||
/*-{ return this.reject_implicit_merges; }-*/ ;
|
/*-{ return this.reject_implicit_merges; }-*/ ;
|
||||||
|
|
||||||
|
public final native InheritedBooleanInfo enableReviewerByEmail()
|
||||||
|
/*-{ return this.enable_reviewer_by_email; }-*/ ;
|
||||||
|
|
||||||
public final SubmitType submitType() {
|
public final SubmitType submitType() {
|
||||||
return SubmitType.valueOf(submitTypeRaw());
|
return SubmitType.valueOf(submitTypeRaw());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ public final class Project {
|
|||||||
|
|
||||||
protected InheritableBoolean rejectImplicitMerges;
|
protected InheritableBoolean rejectImplicitMerges;
|
||||||
|
|
||||||
|
protected InheritableBoolean enableReviewerByEmail;
|
||||||
|
|
||||||
protected Project() {}
|
protected Project() {}
|
||||||
|
|
||||||
public Project(Project.NameKey nameKey) {
|
public Project(Project.NameKey nameKey) {
|
||||||
@@ -112,6 +114,7 @@ public final class Project {
|
|||||||
createNewChangeForAllNotInTarget = InheritableBoolean.INHERIT;
|
createNewChangeForAllNotInTarget = InheritableBoolean.INHERIT;
|
||||||
enableSignedPush = InheritableBoolean.INHERIT;
|
enableSignedPush = InheritableBoolean.INHERIT;
|
||||||
requireSignedPush = InheritableBoolean.INHERIT;
|
requireSignedPush = InheritableBoolean.INHERIT;
|
||||||
|
enableReviewerByEmail = InheritableBoolean.INHERIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Project.NameKey getNameKey() {
|
public Project.NameKey getNameKey() {
|
||||||
@@ -154,6 +157,10 @@ public final class Project {
|
|||||||
return rejectImplicitMerges;
|
return rejectImplicitMerges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InheritableBoolean getEnableReviewerByEmail() {
|
||||||
|
return enableReviewerByEmail;
|
||||||
|
}
|
||||||
|
|
||||||
public void setUseContributorAgreements(final InheritableBoolean u) {
|
public void setUseContributorAgreements(final InheritableBoolean u) {
|
||||||
useContributorAgreements = u;
|
useContributorAgreements = u;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (C) 2017 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;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.ImmutableTable;
|
||||||
|
import com.google.common.collect.Table;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
|
import com.google.gerrit.server.notedb.ReviewerStateInternal;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of reviewers on a change that do not have a Gerrit account and were added by email instead.
|
||||||
|
*
|
||||||
|
* <p>A given account may appear in multiple states and at different timestamps. No reviewers with
|
||||||
|
* state {@link ReviewerStateInternal#REMOVED} are ever exposed by this interface.
|
||||||
|
*/
|
||||||
|
public class ReviewerByEmailSet {
|
||||||
|
private static final ReviewerByEmailSet EMPTY = new ReviewerByEmailSet(ImmutableTable.of());
|
||||||
|
|
||||||
|
public static ReviewerByEmailSet fromTable(
|
||||||
|
Table<ReviewerStateInternal, Address, Timestamp> table) {
|
||||||
|
return new ReviewerByEmailSet(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReviewerByEmailSet empty() {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ImmutableTable<ReviewerStateInternal, Address, Timestamp> table;
|
||||||
|
private ImmutableSet<Address> users;
|
||||||
|
|
||||||
|
private ReviewerByEmailSet(Table<ReviewerStateInternal, Address, Timestamp> table) {
|
||||||
|
this.table = ImmutableTable.copyOf(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableSet<Address> all() {
|
||||||
|
if (users == null) {
|
||||||
|
// Idempotent and immutable, don't bother locking.
|
||||||
|
users = ImmutableSet.copyOf(table.columnKeySet());
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableSet<Address> byState(ReviewerStateInternal state) {
|
||||||
|
return table.row(state).keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableTable<ReviewerStateInternal, Address, Timestamp> asTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return (o instanceof ReviewerByEmailSet) && table.equals(((ReviewerByEmailSet) o).table);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return table.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + table;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,6 +108,7 @@ import com.google.gerrit.server.git.LabelNormalizer;
|
|||||||
import com.google.gerrit.server.git.MergeUtil;
|
import com.google.gerrit.server.git.MergeUtil;
|
||||||
import com.google.gerrit.server.index.change.ChangeField;
|
import com.google.gerrit.server.index.change.ChangeField;
|
||||||
import com.google.gerrit.server.index.change.ChangeIndexCollection;
|
import com.google.gerrit.server.index.change.ChangeIndexCollection;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
import com.google.gerrit.server.notedb.ReviewerStateInternal;
|
import com.google.gerrit.server.notedb.ReviewerStateInternal;
|
||||||
import com.google.gerrit.server.patch.PatchListNotAvailableException;
|
import com.google.gerrit.server.patch.PatchListNotAvailableException;
|
||||||
@@ -531,6 +532,12 @@ public class ChangeJson {
|
|||||||
cd.reviewers().asTable().rowMap().entrySet()) {
|
cd.reviewers().asTable().rowMap().entrySet()) {
|
||||||
out.reviewers.put(e.getKey().asReviewerState(), toAccountInfo(e.getValue().keySet()));
|
out.reviewers.put(e.getKey().asReviewerState(), toAccountInfo(e.getValue().keySet()));
|
||||||
}
|
}
|
||||||
|
// TODO(hiesel) Load from ChangeData instead after the data was added there
|
||||||
|
for (Map.Entry<ReviewerStateInternal, Map<Address, Timestamp>> e :
|
||||||
|
cd.notes().getReviewersByEmail().asTable().rowMap().entrySet()) {
|
||||||
|
out.reviewers.put(
|
||||||
|
e.getKey().asReviewerState(), toAccountInfoByEmail(e.getValue().keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
out.removableReviewers = removableReviewers(ctl, out);
|
out.removableReviewers = removableReviewers(ctl, out);
|
||||||
}
|
}
|
||||||
@@ -1075,12 +1082,14 @@ public class ChangeJson {
|
|||||||
Collection<AccountInfo> ccs = out.reviewers.get(ReviewerState.CC);
|
Collection<AccountInfo> ccs = out.reviewers.get(ReviewerState.CC);
|
||||||
if (ccs != null) {
|
if (ccs != null) {
|
||||||
for (AccountInfo ai : ccs) {
|
for (AccountInfo ai : ccs) {
|
||||||
|
if (ai._accountId != null) {
|
||||||
Account.Id id = new Account.Id(ai._accountId);
|
Account.Id id = new Account.Id(ai._accountId);
|
||||||
if (ctl.canRemoveReviewer(id, 0)) {
|
if (ctl.canRemoveReviewer(id, 0)) {
|
||||||
removable.add(id);
|
removable.add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Subtract any reviewers with non-removable approvals from the "removable"
|
// Subtract any reviewers with non-removable approvals from the "removable"
|
||||||
// set. This also subtracts any CCs that for some reason also hold
|
// set. This also subtracts any CCs that for some reason also hold
|
||||||
@@ -1091,6 +1100,14 @@ public class ChangeJson {
|
|||||||
for (Account.Id id : removable) {
|
for (Account.Id id : removable) {
|
||||||
result.add(accountLoader.get(id));
|
result.add(accountLoader.get(id));
|
||||||
}
|
}
|
||||||
|
// Reviewers added by email are always removable
|
||||||
|
for (Collection<AccountInfo> infos : out.reviewers.values()) {
|
||||||
|
for (AccountInfo info : infos) {
|
||||||
|
if (info._accountId == null) {
|
||||||
|
result.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,6 +1119,14 @@ public class ChangeJson {
|
|||||||
.collect(toList());
|
.collect(toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Collection<AccountInfo> toAccountInfoByEmail(Collection<Address> addresses) {
|
||||||
|
return addresses
|
||||||
|
.stream()
|
||||||
|
.map(a -> new AccountInfo(a.getName(), a.getEmail()))
|
||||||
|
.sorted(AccountInfoComparator.ORDER_NULLS_FIRST)
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Repository openRepoIfNecessary(ChangeControl ctl) throws IOException {
|
private Repository openRepoIfNecessary(ChangeControl ctl) throws IOException {
|
||||||
if (has(ALL_COMMITS) || has(CURRENT_COMMIT) || has(COMMIT_FOOTERS)) {
|
if (has(ALL_COMMITS) || has(CURRENT_COMMIT) || has(COMMIT_FOOTERS)) {
|
||||||
|
|||||||
@@ -14,92 +14,38 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.change;
|
package com.google.gerrit.server.change;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.gerrit.common.TimeUtil;
|
import com.google.gerrit.common.TimeUtil;
|
||||||
import com.google.gerrit.common.data.LabelType;
|
|
||||||
import com.google.gerrit.common.data.LabelTypes;
|
|
||||||
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
||||||
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
|
||||||
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.RestApiException;
|
||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
|
||||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
|
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
|
||||||
import com.google.gerrit.server.PatchSetUtil;
|
|
||||||
import com.google.gerrit.server.extensions.events.ReviewerDeleted;
|
|
||||||
import com.google.gerrit.server.mail.send.DeleteReviewerSender;
|
|
||||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
|
||||||
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
|
|
||||||
import com.google.gerrit.server.notedb.NotesMigration;
|
|
||||||
import com.google.gerrit.server.update.BatchUpdate;
|
import com.google.gerrit.server.update.BatchUpdate;
|
||||||
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.ChangeContext;
|
|
||||||
import com.google.gerrit.server.update.Context;
|
|
||||||
import com.google.gerrit.server.update.UpdateException;
|
import com.google.gerrit.server.update.UpdateException;
|
||||||
import com.google.gwtorm.server.OrmException;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class DeleteReviewer implements RestModifyView<ReviewerResource, DeleteReviewerInput> {
|
public class DeleteReviewer implements RestModifyView<ReviewerResource, DeleteReviewerInput> {
|
||||||
private static final Logger log = LoggerFactory.getLogger(DeleteReviewer.class);
|
|
||||||
|
|
||||||
private final Provider<ReviewDb> dbProvider;
|
private final Provider<ReviewDb> dbProvider;
|
||||||
private final ApprovalsUtil approvalsUtil;
|
|
||||||
private final PatchSetUtil psUtil;
|
|
||||||
private final ChangeMessagesUtil cmUtil;
|
|
||||||
private final BatchUpdate.Factory batchUpdateFactory;
|
private final BatchUpdate.Factory batchUpdateFactory;
|
||||||
private final IdentifiedUser.GenericFactory userFactory;
|
private final DeleteReviewerOp.Factory deleteReviewerOpFactory;
|
||||||
private final ReviewerDeleted reviewerDeleted;
|
private final DeleteReviewerByEmailOp.Factory deleteReviewerByEmailOpFactory;
|
||||||
private final Provider<IdentifiedUser> user;
|
|
||||||
private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
|
|
||||||
private final NotesMigration migration;
|
|
||||||
private final NotifyUtil notifyUtil;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DeleteReviewer(
|
DeleteReviewer(
|
||||||
Provider<ReviewDb> dbProvider,
|
Provider<ReviewDb> dbProvider,
|
||||||
ApprovalsUtil approvalsUtil,
|
|
||||||
PatchSetUtil psUtil,
|
|
||||||
ChangeMessagesUtil cmUtil,
|
|
||||||
BatchUpdate.Factory batchUpdateFactory,
|
BatchUpdate.Factory batchUpdateFactory,
|
||||||
IdentifiedUser.GenericFactory userFactory,
|
DeleteReviewerOp.Factory deleteReviewerOpFactory,
|
||||||
ReviewerDeleted reviewerDeleted,
|
DeleteReviewerByEmailOp.Factory deleteReviewerByEmailOpFactory) {
|
||||||
Provider<IdentifiedUser> user,
|
|
||||||
DeleteReviewerSender.Factory deleteReviewerSenderFactory,
|
|
||||||
NotesMigration migration,
|
|
||||||
NotifyUtil notifyUtil) {
|
|
||||||
this.dbProvider = dbProvider;
|
this.dbProvider = dbProvider;
|
||||||
this.approvalsUtil = approvalsUtil;
|
|
||||||
this.psUtil = psUtil;
|
|
||||||
this.cmUtil = cmUtil;
|
|
||||||
this.batchUpdateFactory = batchUpdateFactory;
|
this.batchUpdateFactory = batchUpdateFactory;
|
||||||
this.userFactory = userFactory;
|
this.deleteReviewerOpFactory = deleteReviewerOpFactory;
|
||||||
this.reviewerDeleted = reviewerDeleted;
|
this.deleteReviewerByEmailOpFactory = deleteReviewerByEmailOpFactory;
|
||||||
this.user = user;
|
|
||||||
this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
|
|
||||||
this.migration = migration;
|
|
||||||
this.notifyUtil = notifyUtil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -118,151 +64,15 @@ public class DeleteReviewer implements RestModifyView<ReviewerResource, DeleteRe
|
|||||||
rsrc.getChangeResource().getProject(),
|
rsrc.getChangeResource().getProject(),
|
||||||
rsrc.getChangeResource().getUser(),
|
rsrc.getChangeResource().getUser(),
|
||||||
TimeUtil.nowTs())) {
|
TimeUtil.nowTs())) {
|
||||||
Op op = new Op(rsrc.getReviewerUser().getAccount(), input);
|
BatchUpdateOp op;
|
||||||
|
if (rsrc.isByEmail()) {
|
||||||
|
op = deleteReviewerByEmailOpFactory.create(rsrc.getReviewerByEmail(), input);
|
||||||
|
} else {
|
||||||
|
op = deleteReviewerOpFactory.create(rsrc.getReviewerUser().getAccount(), input);
|
||||||
|
}
|
||||||
bu.addOp(rsrc.getChange().getId(), op);
|
bu.addOp(rsrc.getChange().getId(), op);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.none();
|
return Response.none();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Op implements BatchUpdateOp {
|
|
||||||
private final Account reviewer;
|
|
||||||
private final DeleteReviewerInput input;
|
|
||||||
ChangeMessage changeMessage;
|
|
||||||
Change currChange;
|
|
||||||
PatchSet currPs;
|
|
||||||
Map<String, Short> newApprovals = new HashMap<>();
|
|
||||||
Map<String, Short> oldApprovals = new HashMap<>();
|
|
||||||
|
|
||||||
Op(Account reviewerAccount, DeleteReviewerInput input) {
|
|
||||||
this.reviewer = reviewerAccount;
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean updateChange(ChangeContext ctx)
|
|
||||||
throws AuthException, ResourceNotFoundException, OrmException {
|
|
||||||
Account.Id reviewerId = reviewer.getId();
|
|
||||||
if (!approvalsUtil.getReviewers(ctx.getDb(), ctx.getNotes()).all().contains(reviewerId)) {
|
|
||||||
throw new ResourceNotFoundException();
|
|
||||||
}
|
|
||||||
currChange = ctx.getChange();
|
|
||||||
currPs = psUtil.current(ctx.getDb(), ctx.getNotes());
|
|
||||||
|
|
||||||
LabelTypes labelTypes = ctx.getControl().getLabelTypes();
|
|
||||||
// removing a reviewer will remove all her votes
|
|
||||||
for (LabelType lt : labelTypes.getLabelTypes()) {
|
|
||||||
newApprovals.put(lt.getName(), (short) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder msg = new StringBuilder();
|
|
||||||
msg.append("Removed reviewer " + reviewer.getFullName());
|
|
||||||
StringBuilder removedVotesMsg = new StringBuilder();
|
|
||||||
removedVotesMsg.append(" with the following votes:\n\n");
|
|
||||||
List<PatchSetApproval> del = new ArrayList<>();
|
|
||||||
boolean votesRemoved = false;
|
|
||||||
for (PatchSetApproval a : approvals(ctx, reviewerId)) {
|
|
||||||
if (ctx.getControl().canRemoveReviewer(a)) {
|
|
||||||
del.add(a);
|
|
||||||
if (a.getPatchSetId().equals(currPs.getId()) && a.getValue() != 0) {
|
|
||||||
oldApprovals.put(a.getLabel(), a.getValue());
|
|
||||||
removedVotesMsg
|
|
||||||
.append("* ")
|
|
||||||
.append(a.getLabel())
|
|
||||||
.append(formatLabelValue(a.getValue()))
|
|
||||||
.append(" by ")
|
|
||||||
.append(userFactory.create(a.getAccountId()).getNameEmail())
|
|
||||||
.append("\n");
|
|
||||||
votesRemoved = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new AuthException("delete reviewer not permitted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (votesRemoved) {
|
|
||||||
msg.append(removedVotesMsg);
|
|
||||||
} else {
|
|
||||||
msg.append(".");
|
|
||||||
}
|
|
||||||
ctx.getDb().patchSetApprovals().delete(del);
|
|
||||||
ChangeUpdate update = ctx.getUpdate(currPs.getId());
|
|
||||||
update.removeReviewer(reviewerId);
|
|
||||||
|
|
||||||
changeMessage =
|
|
||||||
ChangeMessagesUtil.newMessage(
|
|
||||||
ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_REVIEWER);
|
|
||||||
cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postUpdate(Context ctx) {
|
|
||||||
if (NotifyUtil.shouldNotify(input.notify, input.notifyDetails)) {
|
|
||||||
emailReviewers(ctx.getProject(), currChange, changeMessage);
|
|
||||||
}
|
|
||||||
reviewerDeleted.fire(
|
|
||||||
currChange,
|
|
||||||
currPs,
|
|
||||||
reviewer,
|
|
||||||
ctx.getAccount(),
|
|
||||||
changeMessage.getMessage(),
|
|
||||||
newApprovals,
|
|
||||||
oldApprovals,
|
|
||||||
input.notify,
|
|
||||||
ctx.getWhen());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Iterable<PatchSetApproval> approvals(ChangeContext ctx, Account.Id accountId)
|
|
||||||
throws OrmException {
|
|
||||||
Change.Id changeId = ctx.getNotes().getChangeId();
|
|
||||||
Iterable<PatchSetApproval> approvals;
|
|
||||||
PrimaryStorage r = PrimaryStorage.of(ctx.getChange());
|
|
||||||
|
|
||||||
if (migration.readChanges() && r == PrimaryStorage.REVIEW_DB) {
|
|
||||||
// Because NoteDb and ReviewDb have different semantics for zero-value
|
|
||||||
// approvals, we must fall back to ReviewDb as the source of truth here.
|
|
||||||
ReviewDb db = ctx.getDb();
|
|
||||||
|
|
||||||
if (db instanceof BatchUpdateReviewDb) {
|
|
||||||
db = ((BatchUpdateReviewDb) db).unsafeGetDelegate();
|
|
||||||
}
|
|
||||||
db = ReviewDbUtil.unwrapDb(db);
|
|
||||||
approvals = db.patchSetApprovals().byChange(changeId);
|
|
||||||
} else {
|
|
||||||
approvals = approvalsUtil.byChange(ctx.getDb(), ctx.getNotes()).values();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Iterables.filter(approvals, psa -> accountId.equals(psa.getAccountId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String formatLabelValue(short value) {
|
|
||||||
if (value > 0) {
|
|
||||||
return "+" + value;
|
|
||||||
}
|
|
||||||
return Short.toString(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void emailReviewers(
|
|
||||||
Project.NameKey projectName, Change change, ChangeMessage changeMessage) {
|
|
||||||
Account.Id userId = user.get().getAccountId();
|
|
||||||
if (userId.equals(reviewer.getId())) {
|
|
||||||
// The user knows they removed themselves, don't bother emailing them.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
DeleteReviewerSender cm = deleteReviewerSenderFactory.create(projectName, change.getId());
|
|
||||||
cm.setFrom(userId);
|
|
||||||
cm.addReviewers(Collections.singleton(reviewer.getId()));
|
|
||||||
cm.setChangeMessage(changeMessage.getMessage(), changeMessage.getWrittenOn());
|
|
||||||
cm.setNotify(input.notify);
|
|
||||||
cm.setAccountsToNotify(notifyUtil.resolveAccounts(input.notifyDetails));
|
|
||||||
cm.send();
|
|
||||||
} catch (Exception err) {
|
|
||||||
log.error("Cannot email update for change " + change.getId(), err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (C) 2017 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 com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.server.ChangeUtil;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
|
import com.google.gerrit.server.mail.send.DeleteReviewerSender;
|
||||||
|
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||||
|
import com.google.gerrit.server.update.ChangeContext;
|
||||||
|
import com.google.gerrit.server.update.Context;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class DeleteReviewerByEmailOp implements BatchUpdateOp {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DeleteReviewer.class);
|
||||||
|
|
||||||
|
public interface Factory {
|
||||||
|
DeleteReviewerByEmailOp create(Address reviewer, DeleteReviewerInput input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
|
||||||
|
private final NotifyUtil notifyUtil;
|
||||||
|
private final Address reviewer;
|
||||||
|
private final DeleteReviewerInput input;
|
||||||
|
|
||||||
|
private ChangeMessage changeMessage;
|
||||||
|
private Change.Id changeId;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DeleteReviewerByEmailOp(
|
||||||
|
DeleteReviewerSender.Factory deleteReviewerSenderFactory,
|
||||||
|
NotifyUtil notifyUtil,
|
||||||
|
@Assisted Address reviewer,
|
||||||
|
@Assisted DeleteReviewerInput input) {
|
||||||
|
this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
|
||||||
|
this.notifyUtil = notifyUtil;
|
||||||
|
this.reviewer = reviewer;
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateChange(ChangeContext ctx) throws OrmException {
|
||||||
|
changeId = ctx.getChange().getId();
|
||||||
|
PatchSet.Id psId = ctx.getChange().currentPatchSetId();
|
||||||
|
String msg = "Removed reviewer " + reviewer;
|
||||||
|
changeMessage =
|
||||||
|
new ChangeMessage(
|
||||||
|
new ChangeMessage.Key(changeId, ChangeUtil.messageUuid()),
|
||||||
|
ctx.getAccountId(),
|
||||||
|
ctx.getWhen(),
|
||||||
|
psId);
|
||||||
|
changeMessage.setMessage(msg);
|
||||||
|
|
||||||
|
ctx.getUpdate(psId).setChangeMessage(msg);
|
||||||
|
ctx.getUpdate(psId).removeReviewerByEmail(reviewer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postUpdate(Context ctx) {
|
||||||
|
if (!NotifyUtil.shouldNotify(input.notify, input.notifyDetails)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DeleteReviewerSender cm = deleteReviewerSenderFactory.create(ctx.getProject(), changeId);
|
||||||
|
cm.setFrom(ctx.getAccountId());
|
||||||
|
cm.addReviewersByEmail(Collections.singleton(reviewer));
|
||||||
|
cm.setChangeMessage(changeMessage.getMessage(), changeMessage.getWrittenOn());
|
||||||
|
cm.setNotify(input.notify);
|
||||||
|
cm.setAccountsToNotify(notifyUtil.resolveAccounts(input.notifyDetails));
|
||||||
|
cm.send();
|
||||||
|
} catch (Exception err) {
|
||||||
|
log.error("Cannot email update for change " + changeId, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
// Copyright (C) 2017 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 com.google.common.collect.Iterables;
|
||||||
|
import com.google.gerrit.common.data.LabelType;
|
||||||
|
import com.google.gerrit.common.data.LabelTypes;
|
||||||
|
import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
|
||||||
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.PatchSetUtil;
|
||||||
|
import com.google.gerrit.server.extensions.events.ReviewerDeleted;
|
||||||
|
import com.google.gerrit.server.mail.send.DeleteReviewerSender;
|
||||||
|
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||||
|
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
|
||||||
|
import com.google.gerrit.server.notedb.NotesMigration;
|
||||||
|
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||||
|
import com.google.gerrit.server.update.BatchUpdateReviewDb;
|
||||||
|
import com.google.gerrit.server.update.ChangeContext;
|
||||||
|
import com.google.gerrit.server.update.Context;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.assistedinject.Assisted;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class DeleteReviewerOp implements BatchUpdateOp {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DeleteReviewer.class);
|
||||||
|
|
||||||
|
public interface Factory {
|
||||||
|
DeleteReviewerOp create(Account reviewerAccount, DeleteReviewerInput input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ApprovalsUtil approvalsUtil;
|
||||||
|
private final PatchSetUtil psUtil;
|
||||||
|
private final ChangeMessagesUtil cmUtil;
|
||||||
|
private final IdentifiedUser.GenericFactory userFactory;
|
||||||
|
private final ReviewerDeleted reviewerDeleted;
|
||||||
|
private final Provider<IdentifiedUser> user;
|
||||||
|
private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
|
||||||
|
private final NotesMigration migration;
|
||||||
|
private final NotifyUtil notifyUtil;
|
||||||
|
|
||||||
|
private final Account reviewer;
|
||||||
|
private final DeleteReviewerInput input;
|
||||||
|
|
||||||
|
ChangeMessage changeMessage;
|
||||||
|
Change currChange;
|
||||||
|
PatchSet currPs;
|
||||||
|
Map<String, Short> newApprovals = new HashMap<>();
|
||||||
|
Map<String, Short> oldApprovals = new HashMap<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DeleteReviewerOp(
|
||||||
|
ApprovalsUtil approvalsUtil,
|
||||||
|
PatchSetUtil psUtil,
|
||||||
|
ChangeMessagesUtil cmUtil,
|
||||||
|
IdentifiedUser.GenericFactory userFactory,
|
||||||
|
ReviewerDeleted reviewerDeleted,
|
||||||
|
Provider<IdentifiedUser> user,
|
||||||
|
DeleteReviewerSender.Factory deleteReviewerSenderFactory,
|
||||||
|
NotesMigration migration,
|
||||||
|
NotifyUtil notifyUtil,
|
||||||
|
@Assisted Account reviewerAccount,
|
||||||
|
@Assisted DeleteReviewerInput input) {
|
||||||
|
this.approvalsUtil = approvalsUtil;
|
||||||
|
this.psUtil = psUtil;
|
||||||
|
this.cmUtil = cmUtil;
|
||||||
|
this.userFactory = userFactory;
|
||||||
|
this.reviewerDeleted = reviewerDeleted;
|
||||||
|
this.user = user;
|
||||||
|
this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
|
||||||
|
this.migration = migration;
|
||||||
|
this.notifyUtil = notifyUtil;
|
||||||
|
|
||||||
|
this.reviewer = reviewerAccount;
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateChange(ChangeContext ctx)
|
||||||
|
throws AuthException, ResourceNotFoundException, OrmException {
|
||||||
|
Account.Id reviewerId = reviewer.getId();
|
||||||
|
if (!approvalsUtil.getReviewers(ctx.getDb(), ctx.getNotes()).all().contains(reviewerId)) {
|
||||||
|
throw new ResourceNotFoundException();
|
||||||
|
}
|
||||||
|
currChange = ctx.getChange();
|
||||||
|
currPs = psUtil.current(ctx.getDb(), ctx.getNotes());
|
||||||
|
|
||||||
|
LabelTypes labelTypes = ctx.getControl().getLabelTypes();
|
||||||
|
// removing a reviewer will remove all her votes
|
||||||
|
for (LabelType lt : labelTypes.getLabelTypes()) {
|
||||||
|
newApprovals.put(lt.getName(), (short) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("Removed reviewer " + reviewer.getFullName());
|
||||||
|
StringBuilder removedVotesMsg = new StringBuilder();
|
||||||
|
removedVotesMsg.append(" with the following votes:\n\n");
|
||||||
|
List<PatchSetApproval> del = new ArrayList<>();
|
||||||
|
boolean votesRemoved = false;
|
||||||
|
for (PatchSetApproval a : approvals(ctx, reviewerId)) {
|
||||||
|
if (ctx.getControl().canRemoveReviewer(a)) {
|
||||||
|
del.add(a);
|
||||||
|
if (a.getPatchSetId().equals(currPs.getId()) && a.getValue() != 0) {
|
||||||
|
oldApprovals.put(a.getLabel(), a.getValue());
|
||||||
|
removedVotesMsg
|
||||||
|
.append("* ")
|
||||||
|
.append(a.getLabel())
|
||||||
|
.append(formatLabelValue(a.getValue()))
|
||||||
|
.append(" by ")
|
||||||
|
.append(userFactory.create(a.getAccountId()).getNameEmail())
|
||||||
|
.append("\n");
|
||||||
|
votesRemoved = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new AuthException("delete reviewer not permitted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (votesRemoved) {
|
||||||
|
msg.append(removedVotesMsg);
|
||||||
|
} else {
|
||||||
|
msg.append(".");
|
||||||
|
}
|
||||||
|
ctx.getDb().patchSetApprovals().delete(del);
|
||||||
|
ChangeUpdate update = ctx.getUpdate(currPs.getId());
|
||||||
|
update.removeReviewer(reviewerId);
|
||||||
|
|
||||||
|
changeMessage =
|
||||||
|
ChangeMessagesUtil.newMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_REVIEWER);
|
||||||
|
cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postUpdate(Context ctx) {
|
||||||
|
if (NotifyUtil.shouldNotify(input.notify, input.notifyDetails)) {
|
||||||
|
emailReviewers(ctx.getProject(), currChange, changeMessage);
|
||||||
|
}
|
||||||
|
reviewerDeleted.fire(
|
||||||
|
currChange,
|
||||||
|
currPs,
|
||||||
|
reviewer,
|
||||||
|
ctx.getAccount(),
|
||||||
|
changeMessage.getMessage(),
|
||||||
|
newApprovals,
|
||||||
|
oldApprovals,
|
||||||
|
input.notify,
|
||||||
|
ctx.getWhen());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Iterable<PatchSetApproval> approvals(ChangeContext ctx, Account.Id accountId)
|
||||||
|
throws OrmException {
|
||||||
|
Change.Id changeId = ctx.getNotes().getChangeId();
|
||||||
|
Iterable<PatchSetApproval> approvals;
|
||||||
|
PrimaryStorage r = PrimaryStorage.of(ctx.getChange());
|
||||||
|
|
||||||
|
if (migration.readChanges() && r == PrimaryStorage.REVIEW_DB) {
|
||||||
|
// Because NoteDb and ReviewDb have different semantics for zero-value
|
||||||
|
// approvals, we must fall back to ReviewDb as the source of truth here.
|
||||||
|
ReviewDb db = ctx.getDb();
|
||||||
|
|
||||||
|
if (db instanceof BatchUpdateReviewDb) {
|
||||||
|
db = ((BatchUpdateReviewDb) db).unsafeGetDelegate();
|
||||||
|
}
|
||||||
|
db = ReviewDbUtil.unwrapDb(db);
|
||||||
|
approvals = db.patchSetApprovals().byChange(changeId);
|
||||||
|
} else {
|
||||||
|
approvals = approvalsUtil.byChange(ctx.getDb(), ctx.getNotes()).values();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Iterables.filter(approvals, psa -> accountId.equals(psa.getAccountId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatLabelValue(short value) {
|
||||||
|
if (value > 0) {
|
||||||
|
return "+" + value;
|
||||||
|
}
|
||||||
|
return Short.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void emailReviewers(
|
||||||
|
Project.NameKey projectName, Change change, ChangeMessage changeMessage) {
|
||||||
|
Account.Id userId = user.get().getAccountId();
|
||||||
|
if (userId.equals(reviewer.getId())) {
|
||||||
|
// The user knows they removed themselves, don't bother emailing them.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DeleteReviewerSender cm = deleteReviewerSenderFactory.create(projectName, change.getId());
|
||||||
|
cm.setFrom(userId);
|
||||||
|
cm.addReviewers(Collections.singleton(reviewer.getId()));
|
||||||
|
cm.setChangeMessage(changeMessage.getMessage(), changeMessage.getWrittenOn());
|
||||||
|
cm.setNotify(input.notify);
|
||||||
|
cm.setAccountsToNotify(notifyUtil.resolveAccounts(input.notifyDetails));
|
||||||
|
cm.send();
|
||||||
|
} catch (Exception err) {
|
||||||
|
log.error("Cannot email update for change " + change.getId(), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import com.google.gerrit.extensions.restapi.RestReadView;
|
|||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
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.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -48,11 +49,16 @@ class ListReviewers implements RestReadView<ChangeResource> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ReviewerInfo> apply(ChangeResource rsrc) throws OrmException {
|
public List<ReviewerInfo> apply(ChangeResource rsrc) throws OrmException {
|
||||||
Map<Account.Id, ReviewerResource> reviewers = new LinkedHashMap<>();
|
Map<String, ReviewerResource> reviewers = new LinkedHashMap<>();
|
||||||
ReviewDb db = dbProvider.get();
|
ReviewDb db = dbProvider.get();
|
||||||
for (Account.Id accountId : approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
|
for (Account.Id accountId : approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
|
||||||
if (!reviewers.containsKey(accountId)) {
|
if (!reviewers.containsKey(accountId.toString())) {
|
||||||
reviewers.put(accountId, resourceFactory.create(rsrc, accountId));
|
reviewers.put(accountId.toString(), resourceFactory.create(rsrc, accountId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Address adr : rsrc.getNotes().getReviewersByEmail().all()) {
|
||||||
|
if (!reviewers.containsKey(adr.toString())) {
|
||||||
|
reviewers.put(adr.toString(), new ReviewerResource(rsrc, adr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return json.format(reviewers.values());
|
return json.format(reviewers.values());
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.restapi.RestReadView;
|
|||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
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.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -54,11 +55,16 @@ class ListRevisionReviewers implements RestReadView<RevisionResource> {
|
|||||||
throw new MethodNotAllowedException("Cannot list reviewers on non-current patch set");
|
throw new MethodNotAllowedException("Cannot list reviewers on non-current patch set");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<Account.Id, ReviewerResource> reviewers = new LinkedHashMap<>();
|
Map<String, ReviewerResource> reviewers = new LinkedHashMap<>();
|
||||||
ReviewDb db = dbProvider.get();
|
ReviewDb db = dbProvider.get();
|
||||||
for (Account.Id accountId : approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
|
for (Account.Id accountId : approvalsUtil.getReviewers(db, rsrc.getNotes()).all()) {
|
||||||
if (!reviewers.containsKey(accountId)) {
|
if (!reviewers.containsKey(accountId.toString())) {
|
||||||
reviewers.put(accountId, resourceFactory.create(rsrc, accountId));
|
reviewers.put(accountId.toString(), resourceFactory.create(rsrc, accountId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Address address : rsrc.getNotes().getReviewersByEmail().all()) {
|
||||||
|
if (!reviewers.containsKey(address.toString())) {
|
||||||
|
reviewers.put(address.toString(), new ReviewerResource(rsrc, address));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return json.format(reviewers.values());
|
return json.format(reviewers.values());
|
||||||
|
|||||||
@@ -161,5 +161,7 @@ public class Module extends RestApiModule {
|
|||||||
factory(SetAssigneeOp.Factory.class);
|
factory(SetAssigneeOp.Factory.class);
|
||||||
factory(SetHashtagsOp.Factory.class);
|
factory(SetHashtagsOp.Factory.class);
|
||||||
factory(ChangeResource.Factory.class);
|
factory(ChangeResource.Factory.class);
|
||||||
|
factory(DeleteReviewerOp.Factory.class);
|
||||||
|
factory(DeleteReviewerByEmailOp.Factory.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ import com.google.gerrit.server.PatchSetUtil;
|
|||||||
import com.google.gerrit.server.ReviewerSet;
|
import com.google.gerrit.server.ReviewerSet;
|
||||||
import com.google.gerrit.server.account.AccountsCollection;
|
import com.google.gerrit.server.account.AccountsCollection;
|
||||||
import com.google.gerrit.server.extensions.events.CommentAdded;
|
import com.google.gerrit.server.extensions.events.CommentAdded;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
import com.google.gerrit.server.notedb.ChangeUpdate;
|
import com.google.gerrit.server.notedb.ChangeUpdate;
|
||||||
import com.google.gerrit.server.notedb.NotesMigration;
|
import com.google.gerrit.server.notedb.NotesMigration;
|
||||||
@@ -313,14 +314,18 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
|
|||||||
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
||||||
List<Account.Id> to = new ArrayList<>();
|
List<Account.Id> to = new ArrayList<>();
|
||||||
List<Account.Id> cc = new ArrayList<>();
|
List<Account.Id> cc = new ArrayList<>();
|
||||||
|
List<Address> toByEmail = new ArrayList<>();
|
||||||
|
List<Address> ccByEmail = new ArrayList<>();
|
||||||
for (PostReviewers.Addition addition : reviewerAdditions) {
|
for (PostReviewers.Addition addition : reviewerAdditions) {
|
||||||
if (addition.op.state == ReviewerState.REVIEWER) {
|
if (addition.op.state == ReviewerState.REVIEWER) {
|
||||||
to.addAll(addition.op.reviewers.keySet());
|
to.addAll(addition.op.reviewers.keySet());
|
||||||
|
toByEmail.addAll(addition.op.reviewersByEmail);
|
||||||
} else if (addition.op.state == ReviewerState.CC) {
|
} else if (addition.op.state == ReviewerState.CC) {
|
||||||
cc.addAll(addition.op.reviewers.keySet());
|
cc.addAll(addition.op.reviewers.keySet());
|
||||||
|
ccByEmail.addAll(addition.op.reviewersByEmail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
postReviewers.emailReviewers(change, to, cc, notify, accountsToNotify);
|
postReviewers.emailReviewers(change, to, cc, toByEmail, ccByEmail, notify, accountsToNotify);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevisionResource onBehalfOf(RevisionResource rev, ReviewInput in)
|
private RevisionResource onBehalfOf(RevisionResource rev, ReviewInput in)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableListMultimap;
|
|||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.gerrit.common.Nullable;
|
||||||
import com.google.gerrit.common.TimeUtil;
|
import com.google.gerrit.common.TimeUtil;
|
||||||
import com.google.gerrit.common.data.GroupDescription;
|
import com.google.gerrit.common.data.GroupDescription;
|
||||||
import com.google.gerrit.common.errors.NoSuchGroupException;
|
import com.google.gerrit.common.errors.NoSuchGroupException;
|
||||||
@@ -32,6 +33,7 @@ import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
|||||||
import com.google.gerrit.extensions.api.changes.RecipientType;
|
import com.google.gerrit.extensions.api.changes.RecipientType;
|
||||||
import com.google.gerrit.extensions.api.changes.ReviewerInfo;
|
import com.google.gerrit.extensions.api.changes.ReviewerInfo;
|
||||||
import com.google.gerrit.extensions.client.ReviewerState;
|
import com.google.gerrit.extensions.client.ReviewerState;
|
||||||
|
import com.google.gerrit.extensions.common.AccountInfo;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||||
@@ -42,6 +44,7 @@ import com.google.gerrit.reviewdb.client.Change;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.AnonymousUser;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
@@ -52,12 +55,17 @@ import com.google.gerrit.server.account.AccountsCollection;
|
|||||||
import com.google.gerrit.server.account.GroupMembers;
|
import com.google.gerrit.server.account.GroupMembers;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
import com.google.gerrit.server.extensions.events.ReviewerAdded;
|
import com.google.gerrit.server.extensions.events.ReviewerAdded;
|
||||||
|
import com.google.gerrit.server.git.ProjectConfig;
|
||||||
import com.google.gerrit.server.group.GroupsCollection;
|
import com.google.gerrit.server.group.GroupsCollection;
|
||||||
import com.google.gerrit.server.group.SystemGroupBackend;
|
import com.google.gerrit.server.group.SystemGroupBackend;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.mail.send.AddReviewerSender;
|
import com.google.gerrit.server.mail.send.AddReviewerSender;
|
||||||
|
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
|
||||||
import com.google.gerrit.server.notedb.NotesMigration;
|
import com.google.gerrit.server.notedb.NotesMigration;
|
||||||
|
import com.google.gerrit.server.notedb.ReviewerStateInternal;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||||
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
import com.google.gerrit.server.update.BatchUpdate;
|
import com.google.gerrit.server.update.BatchUpdate;
|
||||||
import com.google.gerrit.server.update.BatchUpdateOp;
|
import com.google.gerrit.server.update.BatchUpdateOp;
|
||||||
import com.google.gerrit.server.update.ChangeContext;
|
import com.google.gerrit.server.update.ChangeContext;
|
||||||
@@ -104,6 +112,8 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
private final NotesMigration migration;
|
private final NotesMigration migration;
|
||||||
private final AccountCache accountCache;
|
private final AccountCache accountCache;
|
||||||
private final NotifyUtil notifyUtil;
|
private final NotifyUtil notifyUtil;
|
||||||
|
private final ProjectCache projectCache;
|
||||||
|
private final Provider<AnonymousUser> anonymousProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PostReviewers(
|
PostReviewers(
|
||||||
@@ -124,7 +134,9 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
ReviewerAdded reviewerAdded,
|
ReviewerAdded reviewerAdded,
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
AccountCache accountCache,
|
AccountCache accountCache,
|
||||||
NotifyUtil notifyUtil) {
|
NotifyUtil notifyUtil,
|
||||||
|
ProjectCache projectCache,
|
||||||
|
Provider<AnonymousUser> anonymousProvider) {
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
this.reviewerFactory = reviewerFactory;
|
this.reviewerFactory = reviewerFactory;
|
||||||
this.approvalsUtil = approvalsUtil;
|
this.approvalsUtil = approvalsUtil;
|
||||||
@@ -143,6 +155,8 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
this.migration = migration;
|
this.migration = migration;
|
||||||
this.accountCache = accountCache;
|
this.accountCache = accountCache;
|
||||||
this.notifyUtil = notifyUtil;
|
this.notifyUtil = notifyUtil;
|
||||||
|
this.projectCache = projectCache;
|
||||||
|
this.anonymousProvider = anonymousProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -167,6 +181,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
return addition.result;
|
return addition.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(hiesel) Refactor this as it starts to become unreadable
|
||||||
public Addition prepareApplication(
|
public Addition prepareApplication(
|
||||||
ChangeResource rsrc, AddReviewerInput input, boolean allowGroup)
|
ChangeResource rsrc, AddReviewerInput input, boolean allowGroup)
|
||||||
throws OrmException, RestApiException, IOException {
|
throws OrmException, RestApiException, IOException {
|
||||||
@@ -174,18 +189,29 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
try {
|
try {
|
||||||
accountId = accounts.parse(input.reviewer).getAccountId();
|
accountId = accounts.parse(input.reviewer).getAccountId();
|
||||||
} catch (UnprocessableEntityException e) {
|
} catch (UnprocessableEntityException e) {
|
||||||
|
ProjectConfig projectConfig = projectCache.checkedGet(rsrc.getProject()).getConfig();
|
||||||
if (allowGroup) {
|
if (allowGroup) {
|
||||||
try {
|
try {
|
||||||
return putGroup(rsrc, input);
|
return putGroup(rsrc, input);
|
||||||
} catch (UnprocessableEntityException e2) {
|
} catch (UnprocessableEntityException e2) {
|
||||||
|
if (!projectConfig.getEnableReviewerByEmail()) {
|
||||||
throw new UnprocessableEntityException(
|
throw new UnprocessableEntityException(
|
||||||
MessageFormat.format(
|
MessageFormat.format(
|
||||||
ChangeMessages.get().reviewerNotFoundUserOrGroup, input.reviewer));
|
ChangeMessages.get().reviewerNotFoundUserOrGroup, input.reviewer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!projectConfig.getEnableReviewerByEmail()) {
|
||||||
throw new UnprocessableEntityException(
|
throw new UnprocessableEntityException(
|
||||||
MessageFormat.format(ChangeMessages.get().reviewerNotFoundUser, input.reviewer));
|
MessageFormat.format(ChangeMessages.get().reviewerNotFoundUser, input.reviewer));
|
||||||
}
|
}
|
||||||
|
return putAccountByEmail(
|
||||||
|
input.reviewer,
|
||||||
|
rsrc,
|
||||||
|
input.state(),
|
||||||
|
input.notify,
|
||||||
|
notifyUtil.resolveAccounts(input.notifyDetails));
|
||||||
|
}
|
||||||
return putAccount(
|
return putAccount(
|
||||||
input.reviewer,
|
input.reviewer,
|
||||||
reviewerFactory.create(rsrc, accountId),
|
reviewerFactory.create(rsrc, accountId),
|
||||||
@@ -199,6 +225,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
user.getUserName(),
|
user.getUserName(),
|
||||||
revision.getChangeResource(),
|
revision.getChangeResource(),
|
||||||
ImmutableMap.of(user.getAccountId(), revision.getControl()),
|
ImmutableMap.of(user.getAccountId(), revision.getControl()),
|
||||||
|
null,
|
||||||
CC,
|
CC,
|
||||||
NotifyHandling.NONE,
|
NotifyHandling.NONE,
|
||||||
ImmutableListMultimap.of());
|
ImmutableListMultimap.of());
|
||||||
@@ -218,6 +245,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
reviewer,
|
reviewer,
|
||||||
rsrc.getChangeResource(),
|
rsrc.getChangeResource(),
|
||||||
ImmutableMap.of(member.getId(), control),
|
ImmutableMap.of(member.getId(), control),
|
||||||
|
null,
|
||||||
state,
|
state,
|
||||||
notify,
|
notify,
|
||||||
accountsToNotify);
|
accountsToNotify);
|
||||||
@@ -228,6 +256,32 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
throw new UnprocessableEntityException(String.format("Account of %s is inactive.", reviewer));
|
throw new UnprocessableEntityException(String.format("Account of %s is inactive.", reviewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Addition putAccountByEmail(
|
||||||
|
String reviewer,
|
||||||
|
ChangeResource rsrc,
|
||||||
|
ReviewerState state,
|
||||||
|
NotifyHandling notify,
|
||||||
|
ListMultimap<RecipientType, Account.Id> accountsToNotify)
|
||||||
|
throws UnprocessableEntityException, OrmException, BadRequestException {
|
||||||
|
if (!rsrc.getControl().forUser(anonymousProvider.get()).isVisible(dbProvider.get())) {
|
||||||
|
throw new BadRequestException("change is not publicly visible");
|
||||||
|
}
|
||||||
|
if (!migration.readChanges()) {
|
||||||
|
throw new BadRequestException("feature only supported in NoteDb");
|
||||||
|
}
|
||||||
|
Address adr;
|
||||||
|
try {
|
||||||
|
adr = Address.parse(reviewer);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new UnprocessableEntityException(String.format("email invalid %s", reviewer));
|
||||||
|
}
|
||||||
|
if (!OutgoingEmailValidator.isValid(adr.getEmail())) {
|
||||||
|
throw new UnprocessableEntityException(String.format("email invalid %s", reviewer));
|
||||||
|
}
|
||||||
|
return new Addition(
|
||||||
|
reviewer, rsrc, null, ImmutableList.of(adr), state, notify, accountsToNotify);
|
||||||
|
}
|
||||||
|
|
||||||
private Addition putGroup(ChangeResource rsrc, AddReviewerInput input)
|
private Addition putGroup(ChangeResource rsrc, AddReviewerInput input)
|
||||||
throws RestApiException, OrmException, IOException {
|
throws RestApiException, OrmException, IOException {
|
||||||
GroupDescription.Basic group = groupsCollection.parseInternal(input.reviewer);
|
GroupDescription.Basic group = groupsCollection.parseInternal(input.reviewer);
|
||||||
@@ -283,6 +337,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
input.reviewer,
|
input.reviewer,
|
||||||
rsrc,
|
rsrc,
|
||||||
reviewers,
|
reviewers,
|
||||||
|
null,
|
||||||
input.state(),
|
input.state(),
|
||||||
input.notify,
|
input.notify,
|
||||||
notifyUtil.resolveAccounts(input.notifyDetails));
|
notifyUtil.resolveAccounts(input.notifyDetails));
|
||||||
@@ -314,26 +369,28 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
final Op op;
|
final Op op;
|
||||||
|
|
||||||
private final Map<Account.Id, ChangeControl> reviewers;
|
private final Map<Account.Id, ChangeControl> reviewers;
|
||||||
|
private final Collection<Address> reviewersByEmail;
|
||||||
|
|
||||||
protected Addition(String reviewer) {
|
protected Addition(String reviewer) {
|
||||||
this(reviewer, null, null, REVIEWER, null, ImmutableListMultimap.of());
|
this(reviewer, null, null, null, REVIEWER, null, ImmutableListMultimap.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Addition(
|
protected Addition(
|
||||||
String reviewer,
|
String reviewer,
|
||||||
ChangeResource rsrc,
|
ChangeResource rsrc,
|
||||||
Map<Account.Id, ChangeControl> reviewers,
|
@Nullable Map<Account.Id, ChangeControl> reviewers,
|
||||||
|
@Nullable Collection<Address> reviewersByEmail,
|
||||||
ReviewerState state,
|
ReviewerState state,
|
||||||
NotifyHandling notify,
|
NotifyHandling notify,
|
||||||
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
||||||
result = new AddReviewerResult(reviewer);
|
result = new AddReviewerResult(reviewer);
|
||||||
if (reviewers == null) {
|
this.reviewers = reviewers == null ? ImmutableMap.of() : reviewers;
|
||||||
this.reviewers = ImmutableMap.of();
|
this.reviewersByEmail = reviewersByEmail == null ? ImmutableList.of() : reviewersByEmail;
|
||||||
|
if (reviewers == null && reviewersByEmail == null) {
|
||||||
op = null;
|
op = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.reviewers = reviewers;
|
op = new Op(rsrc, this.reviewers, this.reviewersByEmail, state, notify, accountsToNotify);
|
||||||
op = new Op(rsrc, reviewers, state, notify, accountsToNotify);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gatherResults() throws OrmException {
|
void gatherResults() throws OrmException {
|
||||||
@@ -345,6 +402,9 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
result.ccs.add(json.format(new ReviewerInfo(accountId.get()), reviewers.get(accountId)));
|
result.ccs.add(json.format(new ReviewerInfo(accountId.get()), reviewers.get(accountId)));
|
||||||
}
|
}
|
||||||
accountLoaderFactory.create(true).fill(result.ccs);
|
accountLoaderFactory.create(true).fill(result.ccs);
|
||||||
|
for (Address a : reviewersByEmail) {
|
||||||
|
result.ccs.add(new AccountInfo(a.getName(), a.getEmail()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result.reviewers = Lists.newArrayListWithCapacity(op.addedReviewers.size());
|
result.reviewers = Lists.newArrayListWithCapacity(op.addedReviewers.size());
|
||||||
for (PatchSetApproval psa : op.addedReviewers) {
|
for (PatchSetApproval psa : op.addedReviewers) {
|
||||||
@@ -356,17 +416,22 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
ImmutableList.of(psa)));
|
ImmutableList.of(psa)));
|
||||||
}
|
}
|
||||||
accountLoaderFactory.create(true).fill(result.reviewers);
|
accountLoaderFactory.create(true).fill(result.reviewers);
|
||||||
|
for (Address a : reviewersByEmail) {
|
||||||
|
result.reviewers.add(ReviewerInfo.byEmail(a.getName(), a.getEmail()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Op implements BatchUpdateOp {
|
public class Op implements BatchUpdateOp {
|
||||||
final Map<Account.Id, ChangeControl> reviewers;
|
final Map<Account.Id, ChangeControl> reviewers;
|
||||||
|
final Collection<Address> reviewersByEmail;
|
||||||
final ReviewerState state;
|
final ReviewerState state;
|
||||||
final NotifyHandling notify;
|
final NotifyHandling notify;
|
||||||
final ListMultimap<RecipientType, Account.Id> accountsToNotify;
|
final ListMultimap<RecipientType, Account.Id> accountsToNotify;
|
||||||
List<PatchSetApproval> addedReviewers;
|
List<PatchSetApproval> addedReviewers = new ArrayList<>();
|
||||||
Collection<Account.Id> addedCCs;
|
Collection<Account.Id> addedCCs = new ArrayList<>();
|
||||||
|
Collection<Address> addedCCsByEmail = new ArrayList<>();
|
||||||
|
|
||||||
private final ChangeResource rsrc;
|
private final ChangeResource rsrc;
|
||||||
private PatchSet patchSet;
|
private PatchSet patchSet;
|
||||||
@@ -374,11 +439,13 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
Op(
|
Op(
|
||||||
ChangeResource rsrc,
|
ChangeResource rsrc,
|
||||||
Map<Account.Id, ChangeControl> reviewers,
|
Map<Account.Id, ChangeControl> reviewers,
|
||||||
|
Collection<Address> reviewersByEmail,
|
||||||
ReviewerState state,
|
ReviewerState state,
|
||||||
NotifyHandling notify,
|
NotifyHandling notify,
|
||||||
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
||||||
this.rsrc = rsrc;
|
this.rsrc = rsrc;
|
||||||
this.reviewers = reviewers;
|
this.reviewers = reviewers;
|
||||||
|
this.reviewersByEmail = reviewersByEmail;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.notify = notify;
|
this.notify = notify;
|
||||||
this.accountsToNotify = checkNotNull(accountsToNotify);
|
this.accountsToNotify = checkNotNull(accountsToNotify);
|
||||||
@@ -387,6 +454,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
@Override
|
@Override
|
||||||
public boolean updateChange(ChangeContext ctx)
|
public boolean updateChange(ChangeContext ctx)
|
||||||
throws RestApiException, OrmException, IOException {
|
throws RestApiException, OrmException, IOException {
|
||||||
|
if (!reviewers.isEmpty()) {
|
||||||
if (migration.readChanges() && state == CC) {
|
if (migration.readChanges() && state == CC) {
|
||||||
addedCCs =
|
addedCCs =
|
||||||
approvalsUtil.addCcs(
|
approvalsUtil.addCcs(
|
||||||
@@ -409,6 +477,12 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Address a : reviewersByEmail) {
|
||||||
|
ctx.getUpdate(ctx.getChange().currentPatchSetId())
|
||||||
|
.putReviewerByEmail(a, ReviewerStateInternal.fromReviewerState(state));
|
||||||
|
}
|
||||||
|
|
||||||
patchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
|
patchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
|
||||||
return true;
|
return true;
|
||||||
@@ -416,26 +490,19 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) throws Exception {
|
public void postUpdate(Context ctx) throws Exception {
|
||||||
if (addedReviewers != null || addedCCs != null) {
|
|
||||||
if (addedReviewers == null) {
|
|
||||||
addedReviewers = new ArrayList<>();
|
|
||||||
}
|
|
||||||
if (addedCCs == null) {
|
|
||||||
addedCCs = new ArrayList<>();
|
|
||||||
}
|
|
||||||
emailReviewers(
|
emailReviewers(
|
||||||
rsrc.getChange(),
|
rsrc.getChange(),
|
||||||
Lists.transform(addedReviewers, r -> r.getAccountId()),
|
Lists.transform(addedReviewers, r -> r.getAccountId()),
|
||||||
addedCCs,
|
addedCCs == null ? ImmutableList.of() : addedCCs,
|
||||||
|
reviewersByEmail,
|
||||||
|
addedCCsByEmail,
|
||||||
notify,
|
notify,
|
||||||
accountsToNotify);
|
accountsToNotify);
|
||||||
if (!addedReviewers.isEmpty()) {
|
if (!addedReviewers.isEmpty()) {
|
||||||
List<Account> reviewers =
|
List<Account> reviewers =
|
||||||
Lists.transform(
|
Lists.transform(
|
||||||
addedReviewers, psa -> accountCache.get(psa.getAccountId()).getAccount());
|
addedReviewers, psa -> accountCache.get(psa.getAccountId()).getAccount());
|
||||||
reviewerAdded.fire(
|
reviewerAdded.fire(rsrc.getChange(), patchSet, reviewers, ctx.getAccount(), ctx.getWhen());
|
||||||
rsrc.getChange(), patchSet, reviewers, ctx.getAccount(), ctx.getWhen());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,9 +511,11 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
Change change,
|
Change change,
|
||||||
Collection<Account.Id> added,
|
Collection<Account.Id> added,
|
||||||
Collection<Account.Id> copied,
|
Collection<Account.Id> copied,
|
||||||
|
Collection<Address> addedByEmail,
|
||||||
|
Collection<Address> copiedByEmail,
|
||||||
NotifyHandling notify,
|
NotifyHandling notify,
|
||||||
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
ListMultimap<RecipientType, Account.Id> accountsToNotify) {
|
||||||
if (added.isEmpty() && copied.isEmpty()) {
|
if (added.isEmpty() && copied.isEmpty() && addedByEmail.isEmpty() && copiedByEmail.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,7 +535,7 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
toCopy.add(id);
|
toCopy.add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toMail.isEmpty() && toCopy.isEmpty()) {
|
if (toMail.isEmpty() && toCopy.isEmpty() && addedByEmail.isEmpty() && copiedByEmail.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +547,9 @@ public class PostReviewers implements RestModifyView<ChangeResource, AddReviewer
|
|||||||
cm.setAccountsToNotify(accountsToNotify);
|
cm.setAccountsToNotify(accountsToNotify);
|
||||||
cm.setFrom(userId);
|
cm.setFrom(userId);
|
||||||
cm.addReviewers(toMail);
|
cm.addReviewers(toMail);
|
||||||
|
cm.addReviewersByEmail(addedByEmail);
|
||||||
cm.addExtraCC(toCopy);
|
cm.addExtraCC(toCopy);
|
||||||
|
cm.addExtraCCByEmail(copiedByEmail);
|
||||||
cm.send();
|
cm.send();
|
||||||
} catch (Exception err) {
|
} catch (Exception err) {
|
||||||
log.error("Cannot send email to new reviewers of change " + change.getId(), err);
|
log.error("Cannot send email to new reviewers of change " + change.getId(), err);
|
||||||
|
|||||||
@@ -14,11 +14,15 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.change;
|
package com.google.gerrit.server.change;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.Nullable;
|
||||||
import com.google.gerrit.extensions.restapi.RestResource;
|
import com.google.gerrit.extensions.restapi.RestResource;
|
||||||
import com.google.gerrit.extensions.restapi.RestView;
|
import com.google.gerrit.extensions.restapi.RestView;
|
||||||
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.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
import com.google.inject.assistedinject.Assisted;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
@@ -36,7 +40,8 @@ public class ReviewerResource implements RestResource {
|
|||||||
|
|
||||||
private final ChangeResource change;
|
private final ChangeResource change;
|
||||||
private final RevisionResource revision;
|
private final RevisionResource revision;
|
||||||
private final IdentifiedUser user;
|
@Nullable private final IdentifiedUser user;
|
||||||
|
@Nullable private final Address address;
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
ReviewerResource(
|
ReviewerResource(
|
||||||
@@ -44,8 +49,9 @@ public class ReviewerResource implements RestResource {
|
|||||||
@Assisted ChangeResource change,
|
@Assisted ChangeResource change,
|
||||||
@Assisted Account.Id id) {
|
@Assisted Account.Id id) {
|
||||||
this.change = change;
|
this.change = change;
|
||||||
this.revision = null;
|
|
||||||
this.user = userFactory.create(id);
|
this.user = userFactory.create(id);
|
||||||
|
this.revision = null;
|
||||||
|
this.address = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
@@ -56,6 +62,21 @@ public class ReviewerResource implements RestResource {
|
|||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
this.change = revision.getChangeResource();
|
this.change = revision.getChangeResource();
|
||||||
this.user = userFactory.create(id);
|
this.user = userFactory.create(id);
|
||||||
|
this.address = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReviewerResource(ChangeResource change, Address address) {
|
||||||
|
this.change = change;
|
||||||
|
this.address = address;
|
||||||
|
this.revision = null;
|
||||||
|
this.user = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReviewerResource(RevisionResource revision, Address address) {
|
||||||
|
this.revision = revision;
|
||||||
|
this.change = revision.getChangeResource();
|
||||||
|
this.address = address;
|
||||||
|
this.user = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeResource getChangeResource() {
|
public ChangeResource getChangeResource() {
|
||||||
@@ -75,10 +96,28 @@ public class ReviewerResource implements RestResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IdentifiedUser getReviewerUser() {
|
public IdentifiedUser getReviewerUser() {
|
||||||
|
checkArgument(user != null, "no user provided");
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Address getReviewerByEmail() {
|
||||||
|
checkArgument(address != null, "no address provided");
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Check if this resource was constructed by email or by {@code Account.Id}.
|
||||||
|
*
|
||||||
|
* @return true if the resource was constructed by providing an {@code Address}; false if the
|
||||||
|
* resource was constructed by providing an {@code Account.Id}.
|
||||||
|
*/
|
||||||
|
public boolean isByEmail() {
|
||||||
|
return user == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the control for the caller's user.
|
||||||
|
*
|
||||||
* @return the control for the caller's user (as opposed to the reviewer's user as returned by
|
* @return the control for the caller's user (as opposed to the reviewer's user as returned by
|
||||||
* {@link #getReviewerControl()}).
|
* {@link #getReviewerControl()}).
|
||||||
*/
|
*/
|
||||||
@@ -87,10 +126,13 @@ public class ReviewerResource implements RestResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Get the control for the reviewer's user.
|
||||||
|
*
|
||||||
* @return the control for the reviewer's user (as opposed to the caller's user as returned by
|
* @return the control for the reviewer's user (as opposed to the caller's user as returned by
|
||||||
* {@link #getControl()}).
|
* {@link #getControl()}).
|
||||||
*/
|
*/
|
||||||
public ChangeControl getReviewerControl() {
|
public ChangeControl getReviewerControl() {
|
||||||
|
checkArgument(user != null, "no user provided");
|
||||||
return change.getControl().forUser(user);
|
return change.getControl().forUser(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.google.gerrit.reviewdb.client.Account;
|
|||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
import com.google.gerrit.server.account.AccountsCollection;
|
import com.google.gerrit.server.account.AccountsCollection;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
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.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -69,12 +70,26 @@ public class Reviewers implements ChildCollection<ChangeResource, ReviewerResour
|
|||||||
@Override
|
@Override
|
||||||
public ReviewerResource parse(ChangeResource rsrc, IdString id)
|
public ReviewerResource parse(ChangeResource rsrc, IdString id)
|
||||||
throws OrmException, ResourceNotFoundException, AuthException {
|
throws OrmException, ResourceNotFoundException, AuthException {
|
||||||
Account.Id accountId = accounts.parse(TopLevelResource.INSTANCE, id).getUser().getAccountId();
|
Address address = Address.tryParse(id.get());
|
||||||
|
|
||||||
|
Account.Id accountId = null;
|
||||||
|
try {
|
||||||
|
accountId = accounts.parse(TopLevelResource.INSTANCE, id).getUser().getAccountId();
|
||||||
|
} catch (ResourceNotFoundException e) {
|
||||||
|
if (address == null) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
// See if the id exists as a reviewer for this change
|
// See if the id exists as a reviewer for this change
|
||||||
if (fetchAccountIds(rsrc).contains(accountId)) {
|
if (accountId != null && fetchAccountIds(rsrc).contains(accountId)) {
|
||||||
return resourceFactory.create(rsrc, accountId);
|
return resourceFactory.create(rsrc, accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See if the address exists as a reviewer on the change
|
||||||
|
if (address != null && rsrc.getNotes().getReviewersByEmail().all().contains(address)) {
|
||||||
|
return new ReviewerResource(rsrc, address);
|
||||||
|
}
|
||||||
|
|
||||||
throw new ResourceNotFoundException(id);
|
throw new ResourceNotFoundException(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import com.google.gerrit.reviewdb.client.Account;
|
|||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.ApprovalsUtil;
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
import com.google.gerrit.server.account.AccountsCollection;
|
import com.google.gerrit.server.account.AccountsCollection;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
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.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -73,14 +74,28 @@ public class RevisionReviewers implements ChildCollection<RevisionResource, Revi
|
|||||||
if (!rsrc.isCurrent()) {
|
if (!rsrc.isCurrent()) {
|
||||||
throw new MethodNotAllowedException("Cannot access on non-current patch set");
|
throw new MethodNotAllowedException("Cannot access on non-current patch set");
|
||||||
}
|
}
|
||||||
|
Address address = Address.tryParse(id.get());
|
||||||
|
|
||||||
Account.Id accountId = accounts.parse(TopLevelResource.INSTANCE, id).getUser().getAccountId();
|
Account.Id accountId = null;
|
||||||
|
try {
|
||||||
|
accountId = accounts.parse(TopLevelResource.INSTANCE, id).getUser().getAccountId();
|
||||||
|
} catch (ResourceNotFoundException e) {
|
||||||
|
if (address == null) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
Collection<Account.Id> reviewers =
|
Collection<Account.Id> reviewers =
|
||||||
approvalsUtil.getReviewers(dbProvider.get(), rsrc.getNotes()).all();
|
approvalsUtil.getReviewers(dbProvider.get(), rsrc.getNotes()).all();
|
||||||
|
// See if the id exists as a reviewer for this change
|
||||||
if (reviewers.contains(accountId)) {
|
if (reviewers.contains(accountId)) {
|
||||||
return resourceFactory.create(rsrc, accountId);
|
return resourceFactory.create(rsrc, accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See if the address exists as a reviewer on the change
|
||||||
|
if (address != null && rsrc.getNotes().getReviewersByEmail().all().contains(address)) {
|
||||||
|
return new ReviewerResource(rsrc, address);
|
||||||
|
}
|
||||||
|
|
||||||
throw new ResourceNotFoundException(id);
|
throw new ResourceNotFoundException(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,9 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
ImmutableSet.of(
|
ImmutableSet.of(
|
||||||
"MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp", "PatchSetLock");
|
"MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp", "PatchSetLock");
|
||||||
|
|
||||||
|
private static final String REVIEWER = "reviewer";
|
||||||
|
private static final String KEY_ENABLE_REVIEWER_BY_EMAIL = "enableByEmail";
|
||||||
|
|
||||||
private static final String LEGACY_PERMISSION_PUSH_TAG = "pushTag";
|
private static final String LEGACY_PERMISSION_PUSH_TAG = "pushTag";
|
||||||
private static final String LEGACY_PERMISSION_PUSH_SIGNED_TAG = "pushSignedTag";
|
private static final String LEGACY_PERMISSION_PUSH_SIGNED_TAG = "pushSignedTag";
|
||||||
|
|
||||||
@@ -182,6 +185,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
private boolean checkReceivedObjects;
|
private boolean checkReceivedObjects;
|
||||||
private Set<String> sectionsWithUnknownPermissions;
|
private Set<String> sectionsWithUnknownPermissions;
|
||||||
private boolean hasLegacyPermissions;
|
private boolean hasLegacyPermissions;
|
||||||
|
private boolean enableReviewerByEmail;
|
||||||
|
|
||||||
public static ProjectConfig read(MetaDataUpdate update)
|
public static ProjectConfig read(MetaDataUpdate update)
|
||||||
throws IOException, ConfigInvalidException {
|
throws IOException, ConfigInvalidException {
|
||||||
@@ -435,6 +439,16 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
return checkReceivedObjects;
|
return checkReceivedObjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return the enableReviewerByEmail for this project, default is false. */
|
||||||
|
public boolean getEnableReviewerByEmail() {
|
||||||
|
return enableReviewerByEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set enableReviewerByEmail for this project, default is false. */
|
||||||
|
public void setEnableReviewerByEmail(boolean val) {
|
||||||
|
enableReviewerByEmail = val;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check all GroupReferences use current group name, repairing stale ones.
|
* Check all GroupReferences use current group name, repairing stale ones.
|
||||||
*
|
*
|
||||||
@@ -526,6 +540,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
mimeTypes = new ConfiguredMimeTypes(projectName.get(), rc);
|
mimeTypes = new ConfiguredMimeTypes(projectName.get(), rc);
|
||||||
loadPluginSections(rc);
|
loadPluginSections(rc);
|
||||||
loadReceiveSection(rc);
|
loadReceiveSection(rc);
|
||||||
|
loadReviewerSection(rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAccountsSection(Config rc, Map<String, GroupReference> groupsByName) {
|
private void loadAccountsSection(Config rc, Map<String, GroupReference> groupsByName) {
|
||||||
@@ -933,6 +948,10 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
maxObjectSizeLimit = rc.getLong(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, 0);
|
maxObjectSizeLimit = rc.getLong(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadReviewerSection(Config rc) {
|
||||||
|
enableReviewerByEmail = rc.getBoolean(REVIEWER, null, KEY_ENABLE_REVIEWER_BY_EMAIL, false);
|
||||||
|
}
|
||||||
|
|
||||||
private void loadPluginSections(Config rc) {
|
private void loadPluginSections(Config rc) {
|
||||||
pluginConfigs = new HashMap<>();
|
pluginConfigs = new HashMap<>();
|
||||||
for (String plugin : rc.getSubsections(PLUGIN)) {
|
for (String plugin : rc.getSubsections(PLUGIN)) {
|
||||||
@@ -1067,6 +1086,7 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
saveAccessSections(rc, keepGroups);
|
saveAccessSections(rc, keepGroups);
|
||||||
saveNotifySections(rc, keepGroups);
|
saveNotifySections(rc, keepGroups);
|
||||||
savePluginSections(rc, keepGroups);
|
savePluginSections(rc, keepGroups);
|
||||||
|
saveReviewerSection(rc);
|
||||||
groupList.retainUUIDs(keepGroups);
|
groupList.retainUUIDs(keepGroups);
|
||||||
saveLabelSections(rc);
|
saveLabelSections(rc);
|
||||||
saveSubscribeSections(rc);
|
saveSubscribeSections(rc);
|
||||||
@@ -1288,40 +1308,55 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
|
|
||||||
setBooleanConfigKey(
|
setBooleanConfigKey(
|
||||||
rc,
|
rc,
|
||||||
|
LABEL,
|
||||||
name,
|
name,
|
||||||
KEY_ALLOW_POST_SUBMIT,
|
KEY_ALLOW_POST_SUBMIT,
|
||||||
label.allowPostSubmit(),
|
label.allowPostSubmit(),
|
||||||
LabelType.DEF_ALLOW_POST_SUBMIT);
|
LabelType.DEF_ALLOW_POST_SUBMIT);
|
||||||
setBooleanConfigKey(
|
setBooleanConfigKey(
|
||||||
rc, name, KEY_COPY_MIN_SCORE, label.isCopyMinScore(), LabelType.DEF_COPY_MIN_SCORE);
|
rc,
|
||||||
setBooleanConfigKey(
|
LABEL,
|
||||||
rc, name, KEY_COPY_MAX_SCORE, label.isCopyMaxScore(), LabelType.DEF_COPY_MAX_SCORE);
|
name,
|
||||||
|
KEY_COPY_MIN_SCORE,
|
||||||
|
label.isCopyMinScore(),
|
||||||
|
LabelType.DEF_COPY_MIN_SCORE);
|
||||||
setBooleanConfigKey(
|
setBooleanConfigKey(
|
||||||
rc,
|
rc,
|
||||||
|
LABEL,
|
||||||
|
name,
|
||||||
|
KEY_COPY_MAX_SCORE,
|
||||||
|
label.isCopyMaxScore(),
|
||||||
|
LabelType.DEF_COPY_MAX_SCORE);
|
||||||
|
setBooleanConfigKey(
|
||||||
|
rc,
|
||||||
|
LABEL,
|
||||||
name,
|
name,
|
||||||
KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE,
|
KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE,
|
||||||
label.isCopyAllScoresOnTrivialRebase(),
|
label.isCopyAllScoresOnTrivialRebase(),
|
||||||
LabelType.DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
|
LabelType.DEF_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
|
||||||
setBooleanConfigKey(
|
setBooleanConfigKey(
|
||||||
rc,
|
rc,
|
||||||
|
LABEL,
|
||||||
name,
|
name,
|
||||||
KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE,
|
KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE,
|
||||||
label.isCopyAllScoresIfNoCodeChange(),
|
label.isCopyAllScoresIfNoCodeChange(),
|
||||||
LabelType.DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
|
LabelType.DEF_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
|
||||||
setBooleanConfigKey(
|
setBooleanConfigKey(
|
||||||
rc,
|
rc,
|
||||||
|
LABEL,
|
||||||
name,
|
name,
|
||||||
KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
|
KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
|
||||||
label.isCopyAllScoresIfNoChange(),
|
label.isCopyAllScoresIfNoChange(),
|
||||||
LabelType.DEF_COPY_ALL_SCORES_IF_NO_CHANGE);
|
LabelType.DEF_COPY_ALL_SCORES_IF_NO_CHANGE);
|
||||||
setBooleanConfigKey(
|
setBooleanConfigKey(
|
||||||
rc,
|
rc,
|
||||||
|
LABEL,
|
||||||
name,
|
name,
|
||||||
KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE,
|
KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE,
|
||||||
label.isCopyAllScoresOnMergeFirstParentUpdate(),
|
label.isCopyAllScoresOnMergeFirstParentUpdate(),
|
||||||
LabelType.DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
|
LabelType.DEF_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
|
||||||
setBooleanConfigKey(
|
setBooleanConfigKey(
|
||||||
rc, name, KEY_CAN_OVERRIDE, label.canOverride(), LabelType.DEF_CAN_OVERRIDE);
|
rc, LABEL, name, KEY_CAN_OVERRIDE, label.canOverride(), LabelType.DEF_CAN_OVERRIDE);
|
||||||
List<String> values = Lists.newArrayListWithCapacity(label.getValues().size());
|
List<String> values = Lists.newArrayListWithCapacity(label.getValues().size());
|
||||||
for (LabelValue value : label.getValues()) {
|
for (LabelValue value : label.getValues()) {
|
||||||
values.add(value.format());
|
values.add(value.format());
|
||||||
@@ -1335,11 +1370,11 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void setBooleanConfigKey(
|
private static void setBooleanConfigKey(
|
||||||
Config rc, String name, String key, boolean value, boolean defaultValue) {
|
Config rc, String section, String name, String key, boolean value, boolean defaultValue) {
|
||||||
if (value == defaultValue) {
|
if (value == defaultValue) {
|
||||||
rc.unset(LABEL, name, key);
|
rc.unset(section, name, key);
|
||||||
} else {
|
} else {
|
||||||
rc.setBoolean(LABEL, name, key, value);
|
rc.setBoolean(section, name, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1367,6 +1402,11 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveReviewerSection(Config rc) {
|
||||||
|
setBooleanConfigKey(
|
||||||
|
rc, REVIEWER, null, KEY_ENABLE_REVIEWER_BY_EMAIL, enableReviewerByEmail, false);
|
||||||
|
}
|
||||||
|
|
||||||
private void saveGroupList() throws IOException {
|
private void saveGroupList() throws IOException {
|
||||||
saveUTF8(GroupList.FILE_NAME, groupList.asText());
|
saveUTF8(GroupList.FILE_NAME, groupList.asText());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ public class Address {
|
|||||||
throw new IllegalArgumentException("Invalid email address: " + in);
|
throw new IllegalArgumentException("Invalid email address: " + in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Address tryParse(String in) {
|
||||||
|
try {
|
||||||
|
return parse(in);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final String email;
|
final String email;
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,20 @@ public abstract class ChangeEmail extends NotificationEmail {
|
|||||||
setHeader("X-Gerrit-Change-Number", "" + change.getChangeId());
|
setHeader("X-Gerrit-Change-Number", "" + change.getChangeId());
|
||||||
setChangeUrlHeader();
|
setChangeUrlHeader();
|
||||||
setCommitIdHeader();
|
setCommitIdHeader();
|
||||||
|
|
||||||
|
if (notify.ordinal() >= NotifyHandling.OWNER_REVIEWERS.ordinal()) {
|
||||||
|
try {
|
||||||
|
// TODO(hiesel) Load from index instead
|
||||||
|
addByEmail(
|
||||||
|
RecipientType.CC,
|
||||||
|
changeData.notes().getReviewersByEmail().byState(ReviewerStateInternal.CC));
|
||||||
|
addByEmail(
|
||||||
|
RecipientType.TO,
|
||||||
|
changeData.notes().getReviewersByEmail().byState(ReviewerStateInternal.REVIEWER));
|
||||||
|
} catch (OrmException e) {
|
||||||
|
throw new EmailException("Failed to add unregistered CCs " + change.getChangeId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setChangeUrlHeader() {
|
private void setChangeUrlHeader() {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.server.account.WatchConfig.NotifyType;
|
import com.google.gerrit.server.account.WatchConfig.NotifyType;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
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;
|
||||||
@@ -32,6 +33,7 @@ import java.util.Set;
|
|||||||
/** Let users know that a reviewer and possibly her review have been removed. */
|
/** Let users know that a reviewer and possibly her review have been removed. */
|
||||||
public class DeleteReviewerSender extends ReplyToChangeSender {
|
public class DeleteReviewerSender extends ReplyToChangeSender {
|
||||||
private final Set<Account.Id> reviewers = new HashSet<>();
|
private final Set<Account.Id> reviewers = new HashSet<>();
|
||||||
|
private final Set<Address> reviewersByEmail = new HashSet<>();
|
||||||
|
|
||||||
public interface Factory extends ReplyToChangeSender.Factory<DeleteReviewerSender> {
|
public interface Factory extends ReplyToChangeSender.Factory<DeleteReviewerSender> {
|
||||||
@Override
|
@Override
|
||||||
@@ -49,6 +51,10 @@ public class DeleteReviewerSender extends ReplyToChangeSender {
|
|||||||
reviewers.addAll(cc);
|
reviewers.addAll(cc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addReviewersByEmail(Collection<Address> cc) {
|
||||||
|
reviewersByEmail.addAll(cc);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() throws EmailException {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
@@ -58,6 +64,7 @@ public class DeleteReviewerSender extends ReplyToChangeSender {
|
|||||||
ccExistingReviewers();
|
ccExistingReviewers();
|
||||||
includeWatchers(NotifyType.ALL_COMMENTS);
|
includeWatchers(NotifyType.ALL_COMMENTS);
|
||||||
add(RecipientType.TO, reviewers);
|
add(RecipientType.TO, reviewers);
|
||||||
|
addByEmail(RecipientType.TO, reviewersByEmail);
|
||||||
removeUsersThatIgnoredTheChange();
|
removeUsersThatIgnoredTheChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +77,16 @@ public class DeleteReviewerSender extends ReplyToChangeSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getReviewerNames() {
|
public List<String> getReviewerNames() {
|
||||||
if (reviewers.isEmpty()) {
|
if (reviewers.isEmpty() && reviewersByEmail.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
for (Account.Id id : reviewers) {
|
for (Account.Id id : reviewers) {
|
||||||
names.add(getNameFor(id));
|
names.add(getNameFor(id));
|
||||||
}
|
}
|
||||||
|
for (Address a : reviewersByEmail) {
|
||||||
|
names.add(a.toString());
|
||||||
|
}
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package com.google.gerrit.server.mail.send;
|
|||||||
import com.google.gerrit.common.errors.EmailException;
|
import com.google.gerrit.common.errors.EmailException;
|
||||||
import com.google.gerrit.extensions.api.changes.RecipientType;
|
import com.google.gerrit.extensions.api.changes.RecipientType;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.query.change.ChangeData;
|
import com.google.gerrit.server.query.change.ChangeData;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -28,7 +29,9 @@ import java.util.Set;
|
|||||||
/** Sends an email alerting a user to a new change for them to review. */
|
/** Sends an email alerting a user to a new change for them to review. */
|
||||||
public abstract class NewChangeSender extends ChangeEmail {
|
public abstract class NewChangeSender extends ChangeEmail {
|
||||||
private final Set<Account.Id> reviewers = new HashSet<>();
|
private final Set<Account.Id> reviewers = new HashSet<>();
|
||||||
|
private final Set<Address> reviewersByEmail = new HashSet<>();
|
||||||
private final Set<Account.Id> extraCC = new HashSet<>();
|
private final Set<Account.Id> extraCC = new HashSet<>();
|
||||||
|
private final Set<Address> extraCCByEmail = new HashSet<>();
|
||||||
|
|
||||||
protected NewChangeSender(EmailArguments ea, ChangeData cd) throws OrmException {
|
protected NewChangeSender(EmailArguments ea, ChangeData cd) throws OrmException {
|
||||||
super(ea, "newchange", cd);
|
super(ea, "newchange", cd);
|
||||||
@@ -38,10 +41,18 @@ public abstract class NewChangeSender extends ChangeEmail {
|
|||||||
reviewers.addAll(cc);
|
reviewers.addAll(cc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addReviewersByEmail(final Collection<Address> cc) {
|
||||||
|
reviewersByEmail.addAll(cc);
|
||||||
|
}
|
||||||
|
|
||||||
public void addExtraCC(final Collection<Account.Id> cc) {
|
public void addExtraCC(final Collection<Account.Id> cc) {
|
||||||
extraCC.addAll(cc);
|
extraCC.addAll(cc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addExtraCCByEmail(final Collection<Address> cc) {
|
||||||
|
extraCCByEmail.addAll(cc);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() throws EmailException {
|
protected void init() throws EmailException {
|
||||||
super.init();
|
super.init();
|
||||||
@@ -55,9 +66,11 @@ public abstract class NewChangeSender extends ChangeEmail {
|
|||||||
case ALL:
|
case ALL:
|
||||||
default:
|
default:
|
||||||
add(RecipientType.CC, extraCC);
|
add(RecipientType.CC, extraCC);
|
||||||
|
extraCCByEmail.stream().forEach(cc -> add(RecipientType.CC, cc));
|
||||||
//$FALL-THROUGH$
|
//$FALL-THROUGH$
|
||||||
case OWNER_REVIEWERS:
|
case OWNER_REVIEWERS:
|
||||||
add(RecipientType.TO, reviewers);
|
add(RecipientType.TO, reviewers);
|
||||||
|
addByEmail(RecipientType.TO, reviewersByEmail);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -441,6 +441,13 @@ public abstract class OutgoingEmail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Schedule this message for delivery to the listed address. */
|
||||||
|
protected void addByEmail(final RecipientType rt, final Collection<Address> list) {
|
||||||
|
for (final Address id : list) {
|
||||||
|
add(rt, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void add(final RecipientType rt, final UserIdentity who) {
|
protected void add(final RecipientType rt, final UserIdentity who) {
|
||||||
if (who != null && who.getAccount() != null) {
|
if (who != null && who.getAccount() != null) {
|
||||||
add(rt, who.getAccount());
|
add(rt, who.getAccount());
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import com.google.gerrit.reviewdb.client.RevId;
|
|||||||
import com.google.gerrit.reviewdb.client.RobotComment;
|
import com.google.gerrit.reviewdb.client.RobotComment;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
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.ReviewerByEmailSet;
|
||||||
import com.google.gerrit.server.ReviewerSet;
|
import com.google.gerrit.server.ReviewerSet;
|
||||||
import com.google.gerrit.server.ReviewerStatusUpdate;
|
import com.google.gerrit.server.ReviewerStatusUpdate;
|
||||||
import com.google.gerrit.server.git.RefCache;
|
import com.google.gerrit.server.git.RefCache;
|
||||||
@@ -428,6 +429,11 @@ public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
|
|||||||
return state.reviewers();
|
return state.reviewers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return reviewers that do not currently have a Gerrit account and were added by email. */
|
||||||
|
public ReviewerByEmailSet getReviewersByEmail() {
|
||||||
|
return state.reviewersByEmail();
|
||||||
|
}
|
||||||
|
|
||||||
public ImmutableList<ReviewerStatusUpdate> getReviewerUpdates() {
|
public ImmutableList<ReviewerStatusUpdate> getReviewerUpdates() {
|
||||||
return state.reviewerUpdates();
|
return state.reviewerUpdates();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,10 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
|
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
|
||||||
|
import com.google.gerrit.server.ReviewerByEmailSet;
|
||||||
import com.google.gerrit.server.ReviewerSet;
|
import com.google.gerrit.server.ReviewerSet;
|
||||||
import com.google.gerrit.server.ReviewerStatusUpdate;
|
import com.google.gerrit.server.ReviewerStatusUpdate;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
|
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
|
||||||
import com.google.gerrit.server.util.LabelVote;
|
import com.google.gerrit.server.util.LabelVote;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -128,6 +130,7 @@ class ChangeNotesParser {
|
|||||||
// Private final but mutable members initialized in the constructor and filled
|
// Private final but mutable members initialized in the constructor and filled
|
||||||
// in during the parsing process.
|
// in during the parsing process.
|
||||||
private final Table<Account.Id, ReviewerStateInternal, Timestamp> reviewers;
|
private final Table<Account.Id, ReviewerStateInternal, Timestamp> reviewers;
|
||||||
|
private final Table<Address, ReviewerStateInternal, Timestamp> reviewersByEmail;
|
||||||
private final List<Account.Id> allPastReviewers;
|
private final List<Account.Id> allPastReviewers;
|
||||||
private final List<ReviewerStatusUpdate> reviewerUpdates;
|
private final List<ReviewerStatusUpdate> reviewerUpdates;
|
||||||
private final List<SubmitRecord> submitRecords;
|
private final List<SubmitRecord> submitRecords;
|
||||||
@@ -174,6 +177,7 @@ class ChangeNotesParser {
|
|||||||
approvals = new LinkedHashMap<>();
|
approvals = new LinkedHashMap<>();
|
||||||
bufferedApprovals = new ArrayList<>();
|
bufferedApprovals = new ArrayList<>();
|
||||||
reviewers = HashBasedTable.create();
|
reviewers = HashBasedTable.create();
|
||||||
|
reviewersByEmail = HashBasedTable.create();
|
||||||
allPastReviewers = new ArrayList<>();
|
allPastReviewers = new ArrayList<>();
|
||||||
reviewerUpdates = new ArrayList<>();
|
reviewerUpdates = new ArrayList<>();
|
||||||
submitRecords = Lists.newArrayListWithExpectedSize(1);
|
submitRecords = Lists.newArrayListWithExpectedSize(1);
|
||||||
@@ -201,6 +205,7 @@ class ChangeNotesParser {
|
|||||||
parseNotes();
|
parseNotes();
|
||||||
allPastReviewers.addAll(reviewers.rowKeySet());
|
allPastReviewers.addAll(reviewers.rowKeySet());
|
||||||
pruneReviewers();
|
pruneReviewers();
|
||||||
|
pruneReviewersByEmail();
|
||||||
|
|
||||||
updatePatchSetStates();
|
updatePatchSetStates();
|
||||||
checkMandatoryFooters();
|
checkMandatoryFooters();
|
||||||
@@ -234,6 +239,7 @@ class ChangeNotesParser {
|
|||||||
patchSets,
|
patchSets,
|
||||||
buildApprovals(),
|
buildApprovals(),
|
||||||
ReviewerSet.fromTable(Tables.transpose(reviewers)),
|
ReviewerSet.fromTable(Tables.transpose(reviewers)),
|
||||||
|
ReviewerByEmailSet.fromTable(Tables.transpose(reviewersByEmail)),
|
||||||
allPastReviewers,
|
allPastReviewers,
|
||||||
buildReviewerUpdates(),
|
buildReviewerUpdates(),
|
||||||
submitRecords,
|
submitRecords,
|
||||||
@@ -374,6 +380,9 @@ class ChangeNotesParser {
|
|||||||
for (String line : commit.getFooterLineValues(state.getFooterKey())) {
|
for (String line : commit.getFooterLineValues(state.getFooterKey())) {
|
||||||
parseReviewer(ts, state, line);
|
parseReviewer(ts, state, line);
|
||||||
}
|
}
|
||||||
|
for (String line : commit.getFooterLineValues(state.getByEmailFooterKey())) {
|
||||||
|
parseReviewerByEmail(ts, state, line);
|
||||||
|
}
|
||||||
// Don't update timestamp when a reviewer was added, matching RevewDb
|
// Don't update timestamp when a reviewer was added, matching RevewDb
|
||||||
// behavior.
|
// behavior.
|
||||||
}
|
}
|
||||||
@@ -917,6 +926,19 @@ class ChangeNotesParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void parseReviewerByEmail(Timestamp ts, ReviewerStateInternal state, String line)
|
||||||
|
throws ConfigInvalidException {
|
||||||
|
Address adr;
|
||||||
|
try {
|
||||||
|
adr = Address.parse(line);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw invalidFooter(state.getByEmailFooterKey(), line);
|
||||||
|
}
|
||||||
|
if (!reviewersByEmail.containsRow(adr)) {
|
||||||
|
reviewersByEmail.put(adr, state, ts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void parseReadOnlyUntil(ChangeNotesCommit commit) throws ConfigInvalidException {
|
private void parseReadOnlyUntil(ChangeNotesCommit commit) throws ConfigInvalidException {
|
||||||
String raw = parseOneFooter(commit, FOOTER_READ_ONLY_UNTIL);
|
String raw = parseOneFooter(commit, FOOTER_READ_ONLY_UNTIL);
|
||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
@@ -956,6 +978,17 @@ class ChangeNotesParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void pruneReviewersByEmail() {
|
||||||
|
Iterator<Table.Cell<Address, ReviewerStateInternal, Timestamp>> rit =
|
||||||
|
reviewersByEmail.cellSet().iterator();
|
||||||
|
while (rit.hasNext()) {
|
||||||
|
Table.Cell<Address, ReviewerStateInternal, Timestamp> e = rit.next();
|
||||||
|
if (e.getColumnKey() == ReviewerStateInternal.REMOVED) {
|
||||||
|
rit.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updatePatchSetStates() {
|
private void updatePatchSetStates() {
|
||||||
Set<PatchSet.Id> missing = new TreeSet<>(ReviewDbUtil.intKeyOrdering());
|
Set<PatchSet.Id> missing = new TreeSet<>(ReviewDbUtil.intKeyOrdering());
|
||||||
for (Iterator<PatchSet> it = patchSets.values().iterator(); it.hasNext(); ) {
|
for (Iterator<PatchSet> it = patchSets.values().iterator(); it.hasNext(); ) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.google.gerrit.reviewdb.client.PatchSet;
|
|||||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.client.RevId;
|
import com.google.gerrit.reviewdb.client.RevId;
|
||||||
|
import com.google.gerrit.server.ReviewerByEmailSet;
|
||||||
import com.google.gerrit.server.ReviewerSet;
|
import com.google.gerrit.server.ReviewerSet;
|
||||||
import com.google.gerrit.server.ReviewerStatusUpdate;
|
import com.google.gerrit.server.ReviewerStatusUpdate;
|
||||||
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
|
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
|
||||||
@@ -65,6 +66,7 @@ public abstract class ChangeNotesState {
|
|||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
ReviewerSet.empty(),
|
ReviewerSet.empty(),
|
||||||
|
ReviewerByEmailSet.empty(),
|
||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
ImmutableList.of(),
|
ImmutableList.of(),
|
||||||
@@ -95,6 +97,7 @@ public abstract class ChangeNotesState {
|
|||||||
Map<PatchSet.Id, PatchSet> patchSets,
|
Map<PatchSet.Id, PatchSet> patchSets,
|
||||||
ListMultimap<PatchSet.Id, PatchSetApproval> approvals,
|
ListMultimap<PatchSet.Id, PatchSetApproval> approvals,
|
||||||
ReviewerSet reviewers,
|
ReviewerSet reviewers,
|
||||||
|
ReviewerByEmailSet reviewersByEmail,
|
||||||
List<Account.Id> allPastReviewers,
|
List<Account.Id> allPastReviewers,
|
||||||
List<ReviewerStatusUpdate> reviewerUpdates,
|
List<ReviewerStatusUpdate> reviewerUpdates,
|
||||||
List<SubmitRecord> submitRecords,
|
List<SubmitRecord> submitRecords,
|
||||||
@@ -128,6 +131,7 @@ public abstract class ChangeNotesState {
|
|||||||
ImmutableList.copyOf(patchSets.entrySet()),
|
ImmutableList.copyOf(patchSets.entrySet()),
|
||||||
ImmutableList.copyOf(approvals.entries()),
|
ImmutableList.copyOf(approvals.entries()),
|
||||||
reviewers,
|
reviewers,
|
||||||
|
reviewersByEmail,
|
||||||
ImmutableList.copyOf(allPastReviewers),
|
ImmutableList.copyOf(allPastReviewers),
|
||||||
ImmutableList.copyOf(reviewerUpdates),
|
ImmutableList.copyOf(reviewerUpdates),
|
||||||
ImmutableList.copyOf(submitRecords),
|
ImmutableList.copyOf(submitRecords),
|
||||||
@@ -204,6 +208,8 @@ public abstract class ChangeNotesState {
|
|||||||
|
|
||||||
abstract ReviewerSet reviewers();
|
abstract ReviewerSet reviewers();
|
||||||
|
|
||||||
|
abstract ReviewerByEmailSet reviewersByEmail();
|
||||||
|
|
||||||
abstract ImmutableList<Account.Id> allPastReviewers();
|
abstract ImmutableList<Account.Id> allPastReviewers();
|
||||||
|
|
||||||
abstract ImmutableList<ReviewerStatusUpdate> reviewerUpdates();
|
abstract ImmutableList<ReviewerStatusUpdate> reviewerUpdates();
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import com.google.gerrit.server.GerritPersonIdent;
|
|||||||
import com.google.gerrit.server.account.AccountCache;
|
import com.google.gerrit.server.account.AccountCache;
|
||||||
import com.google.gerrit.server.config.AnonymousCowardName;
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.project.ChangeControl;
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.project.ProjectCache;
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
import com.google.gerrit.server.util.LabelVote;
|
import com.google.gerrit.server.util.LabelVote;
|
||||||
@@ -128,6 +129,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
|
|
||||||
private final Table<String, Account.Id, Optional<Short>> approvals;
|
private final Table<String, Account.Id, Optional<Short>> approvals;
|
||||||
private final Map<Account.Id, ReviewerStateInternal> reviewers = new LinkedHashMap<>();
|
private final Map<Account.Id, ReviewerStateInternal> reviewers = new LinkedHashMap<>();
|
||||||
|
private final Map<Address, ReviewerStateInternal> reviewersByEmail = new LinkedHashMap<>();
|
||||||
private final List<Comment> comments = new ArrayList<>();
|
private final List<Comment> comments = new ArrayList<>();
|
||||||
|
|
||||||
private String commitSubject;
|
private String commitSubject;
|
||||||
@@ -471,6 +473,15 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
reviewers.put(reviewer, ReviewerStateInternal.REMOVED);
|
reviewers.put(reviewer, ReviewerStateInternal.REMOVED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void putReviewerByEmail(Address reviewer, ReviewerStateInternal type) {
|
||||||
|
checkArgument(type != ReviewerStateInternal.REMOVED, "invalid ReviewerType");
|
||||||
|
reviewersByEmail.put(reviewer, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeReviewerByEmail(Address reviewer) {
|
||||||
|
reviewersByEmail.put(reviewer, ReviewerStateInternal.REMOVED);
|
||||||
|
}
|
||||||
|
|
||||||
public void setPatchSetState(PatchSetState psState) {
|
public void setPatchSetState(PatchSetState psState) {
|
||||||
this.psState = psState;
|
this.psState = psState;
|
||||||
}
|
}
|
||||||
@@ -660,6 +671,10 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
addIdent(msg, e.getKey()).append('\n');
|
addIdent(msg, e.getKey()).append('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Address, ReviewerStateInternal> e : reviewersByEmail.entrySet()) {
|
||||||
|
addFooter(msg, e.getValue().getByEmailFooterKey(), e.getKey().toString());
|
||||||
|
}
|
||||||
|
|
||||||
for (Table.Cell<String, Account.Id, Optional<Short>> c : approvals.cellSet()) {
|
for (Table.Cell<String, Account.Id, Optional<Short>> c : approvals.cellSet()) {
|
||||||
addFooter(msg, FOOTER_LABEL);
|
addFooter(msg, FOOTER_LABEL);
|
||||||
// Label names/values are safe to append without sanitizing.
|
// Label names/values are safe to append without sanitizing.
|
||||||
@@ -749,6 +764,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
|
|||||||
&& changeMessage == null
|
&& changeMessage == null
|
||||||
&& comments.isEmpty()
|
&& comments.isEmpty()
|
||||||
&& reviewers.isEmpty()
|
&& reviewers.isEmpty()
|
||||||
|
&& reviewersByEmail.isEmpty()
|
||||||
&& changeId == null
|
&& changeId == null
|
||||||
&& branch == null
|
&& branch == null
|
||||||
&& status == null
|
&& status == null
|
||||||
|
|||||||
@@ -29,13 +29,17 @@ public enum ReviewerStateInternal {
|
|||||||
/** The user was previously a reviewer on the change, but was removed. */
|
/** The user was previously a reviewer on the change, but was removed. */
|
||||||
REMOVED(new FooterKey("Removed"), ReviewerState.REMOVED);
|
REMOVED(new FooterKey("Removed"), ReviewerState.REMOVED);
|
||||||
|
|
||||||
|
public static ReviewerStateInternal fromReviewerState(ReviewerState state) {
|
||||||
|
return ReviewerStateInternal.values()[state.ordinal()];
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
boolean ok = true;
|
boolean ok = true;
|
||||||
if (ReviewerStateInternal.values().length != ReviewerState.values().length) {
|
if (ReviewerStateInternal.values().length != ReviewerState.values().length) {
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
for (ReviewerStateInternal s : ReviewerStateInternal.values()) {
|
for (int i = 0; i < ReviewerStateInternal.values().length; i++) {
|
||||||
ok &= s.name().equals(s.state.name());
|
ok &= ReviewerState.values()[i].equals(ReviewerStateInternal.values()[i].state);
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
@@ -58,6 +62,10 @@ public enum ReviewerStateInternal {
|
|||||||
return footerKey;
|
return footerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FooterKey getByEmailFooterKey() {
|
||||||
|
return new FooterKey(footerKey.getName() + "-email");
|
||||||
|
}
|
||||||
|
|
||||||
public ReviewerState asReviewerState() {
|
public ReviewerState asReviewerState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ public class ConfigInfoImpl extends ConfigInfo {
|
|||||||
InheritedBooleanInfo enableSignedPush = new InheritedBooleanInfo();
|
InheritedBooleanInfo enableSignedPush = new InheritedBooleanInfo();
|
||||||
InheritedBooleanInfo requireSignedPush = new InheritedBooleanInfo();
|
InheritedBooleanInfo requireSignedPush = new InheritedBooleanInfo();
|
||||||
InheritedBooleanInfo rejectImplicitMerges = new InheritedBooleanInfo();
|
InheritedBooleanInfo rejectImplicitMerges = new InheritedBooleanInfo();
|
||||||
|
InheritedBooleanInfo enableReviewerByEmail = new InheritedBooleanInfo();
|
||||||
|
|
||||||
useContributorAgreements.value = projectState.isUseContributorAgreements();
|
useContributorAgreements.value = projectState.isUseContributorAgreements();
|
||||||
useSignedOffBy.value = projectState.isUseSignedOffBy();
|
useSignedOffBy.value = projectState.isUseSignedOffBy();
|
||||||
@@ -73,6 +74,7 @@ public class ConfigInfoImpl extends ConfigInfo {
|
|||||||
enableSignedPush.configuredValue = p.getEnableSignedPush();
|
enableSignedPush.configuredValue = p.getEnableSignedPush();
|
||||||
requireSignedPush.configuredValue = p.getRequireSignedPush();
|
requireSignedPush.configuredValue = p.getRequireSignedPush();
|
||||||
rejectImplicitMerges.configuredValue = p.getRejectImplicitMerges();
|
rejectImplicitMerges.configuredValue = p.getRejectImplicitMerges();
|
||||||
|
enableReviewerByEmail.configuredValue = p.getEnableReviewerByEmail();
|
||||||
|
|
||||||
ProjectState parentState = Iterables.getFirst(projectState.parents(), null);
|
ProjectState parentState = Iterables.getFirst(projectState.parents(), null);
|
||||||
if (parentState != null) {
|
if (parentState != null) {
|
||||||
@@ -85,6 +87,7 @@ public class ConfigInfoImpl extends ConfigInfo {
|
|||||||
enableSignedPush.inheritedValue = projectState.isEnableSignedPush();
|
enableSignedPush.inheritedValue = projectState.isEnableSignedPush();
|
||||||
requireSignedPush.inheritedValue = projectState.isRequireSignedPush();
|
requireSignedPush.inheritedValue = projectState.isRequireSignedPush();
|
||||||
rejectImplicitMerges.inheritedValue = projectState.isRejectImplicitMerges();
|
rejectImplicitMerges.inheritedValue = projectState.isRejectImplicitMerges();
|
||||||
|
enableReviewerByEmail.inheritedValue = projectState.isEnableReviewerByEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.useContributorAgreements = useContributorAgreements;
|
this.useContributorAgreements = useContributorAgreements;
|
||||||
|
|||||||
@@ -394,6 +394,10 @@ public class ProjectState {
|
|||||||
return getInheritableBoolean(Project::getRejectImplicitMerges);
|
return getInheritableBoolean(Project::getRejectImplicitMerges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnableReviewerByEmail() {
|
||||||
|
return getInheritableBoolean(Project::getEnableReviewerByEmail);
|
||||||
|
}
|
||||||
|
|
||||||
public LabelTypes getLabelTypes() {
|
public LabelTypes getLabelTypes() {
|
||||||
Map<String, LabelType> types = new LinkedHashMap<>();
|
Map<String, LabelType> types = new LinkedHashMap<>();
|
||||||
for (ProjectState s : treeInOrder()) {
|
for (ProjectState s : treeInOrder()) {
|
||||||
|
|||||||
@@ -105,7 +105,9 @@ public class ProjectConfigTest extends LocalDiskRepositoryTestCase {
|
|||||||
+ " accepted = group Developers\n" //
|
+ " accepted = group Developers\n" //
|
||||||
+ " accepted = group Staff\n" //
|
+ " accepted = group Staff\n" //
|
||||||
+ " autoVerify = group Developers\n" //
|
+ " autoVerify = group Developers\n" //
|
||||||
+ " agreementUrl = http://www.example.com/agree\n")) //
|
+ " agreementUrl = http://www.example.com/agree\n" //
|
||||||
|
+ "[reviewer]\n" //
|
||||||
|
+ " enableByEmail = true\n")) //
|
||||||
));
|
));
|
||||||
|
|
||||||
ProjectConfig cfg = read(rev);
|
ProjectConfig cfg = read(rev);
|
||||||
@@ -132,6 +134,8 @@ public class ProjectConfigTest extends LocalDiskRepositoryTestCase {
|
|||||||
assertThat(submit.getExclusiveGroup()).isTrue();
|
assertThat(submit.getExclusiveGroup()).isTrue();
|
||||||
assertThat(read.getExclusiveGroup()).isTrue();
|
assertThat(read.getExclusiveGroup()).isTrue();
|
||||||
assertThat(push.getExclusiveGroup()).isFalse();
|
assertThat(push.getExclusiveGroup()).isFalse();
|
||||||
|
|
||||||
|
assertThat(cfg.getEnableReviewerByEmail()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import com.google.gerrit.server.CurrentUser;
|
|||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.ReviewerSet;
|
import com.google.gerrit.server.ReviewerSet;
|
||||||
import com.google.gerrit.server.config.GerritServerId;
|
import com.google.gerrit.server.config.GerritServerId;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
|
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
|
||||||
import com.google.gerrit.server.util.RequestId;
|
import com.google.gerrit.server.util.RequestId;
|
||||||
import com.google.gerrit.testutil.TestChanges;
|
import com.google.gerrit.testutil.TestChanges;
|
||||||
@@ -3298,6 +3299,105 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
|
|||||||
assertThat(notes.isPrivate()).isFalse();
|
assertThat(notes.isPrivate()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void defaultReviewersByEmailIsEmpty() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getReviewersByEmail().all()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putReviewerByEmail() throws Exception {
|
||||||
|
Address adr = new Address("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getReviewersByEmail().all()).containsExactly(adr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putAndRemoveReviewerByEmail() throws Exception {
|
||||||
|
Address adr = new Address("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
update = newUpdate(c, changeOwner);
|
||||||
|
update.removeReviewerByEmail(adr);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getReviewersByEmail().all()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putRemoveAndAddBackReviewerByEmail() throws Exception {
|
||||||
|
Address adr = new Address("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
update = newUpdate(c, changeOwner);
|
||||||
|
update.removeReviewerByEmail(adr);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adr, ReviewerStateInternal.CC);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getReviewersByEmail().all()).containsExactly(adr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putReviewerByEmailAndCcByEmail() throws Exception {
|
||||||
|
Address adrReviewer = new Address("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
Address adrCc = new Address("Foo Bor", "foo.bar.2@gerritcodereview.com");
|
||||||
|
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adrReviewer, ReviewerStateInternal.REVIEWER);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adrCc, ReviewerStateInternal.CC);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.REVIEWER))
|
||||||
|
.containsExactly(adrReviewer);
|
||||||
|
assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.CC))
|
||||||
|
.containsExactly(adrCc);
|
||||||
|
assertThat(notes.getReviewersByEmail().all()).containsExactly(adrReviewer, adrCc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putReviewerByEmailAndChangeToCc() throws Exception {
|
||||||
|
Address adr = new Address("Foo Bar", "foo.bar@gerritcodereview.com");
|
||||||
|
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adr, ReviewerStateInternal.REVIEWER);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(adr, ReviewerStateInternal.CC);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
ChangeNotes notes = newNotes(c);
|
||||||
|
assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.REVIEWER)).isEmpty();
|
||||||
|
assertThat(notes.getReviewersByEmail().byState(ReviewerStateInternal.CC)).containsExactly(adr);
|
||||||
|
assertThat(notes.getReviewersByEmail().all()).containsExactly(adr);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean testJson() {
|
private boolean testJson() {
|
||||||
return noteUtil.getWriteJson();
|
return noteUtil.getWriteJson();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.google.gerrit.common.TimeUtil;
|
|||||||
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.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.util.RequestId;
|
import com.google.gerrit.server.util.RequestId;
|
||||||
import com.google.gerrit.testutil.ConfigSuite;
|
import com.google.gerrit.testutil.ConfigSuite;
|
||||||
import com.google.gerrit.testutil.TestChanges;
|
import com.google.gerrit.testutil.TestChanges;
|
||||||
@@ -382,6 +383,32 @@ public class CommitMessageOutputTest extends AbstractChangeNotesTest {
|
|||||||
assertBodyEquals("Update patch set 1\n\nPatch-set: 1\nCurrent: true\n", update.getResult());
|
assertBodyEquals("Update patch set 1\n\nPatch-set: 1\nCurrent: true\n", update.getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reviewerByEmail() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(
|
||||||
|
new Address("John Doe", "j.doe@gerritcodereview.com"), ReviewerStateInternal.REVIEWER);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
assertBodyEquals(
|
||||||
|
"Update patch set 1\n\nPatch-set: 1\n"
|
||||||
|
+ "Reviewer-email: John Doe <j.doe@gerritcodereview.com>\n",
|
||||||
|
update.getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ccByEmail() throws Exception {
|
||||||
|
Change c = newChange();
|
||||||
|
ChangeUpdate update = newUpdate(c, changeOwner);
|
||||||
|
update.putReviewerByEmail(new Address("j.doe@gerritcodereview.com"), ReviewerStateInternal.CC);
|
||||||
|
update.commit();
|
||||||
|
|
||||||
|
assertBodyEquals(
|
||||||
|
"Update patch set 1\n\nPatch-set: 1\nCC-email: j.doe@gerritcodereview.com\n",
|
||||||
|
update.getResult());
|
||||||
|
}
|
||||||
|
|
||||||
private RevCommit parseCommit(ObjectId id) throws Exception {
|
private RevCommit parseCommit(ObjectId id) throws Exception {
|
||||||
if (id instanceof RevCommit) {
|
if (id instanceof RevCommit) {
|
||||||
return (RevCommit) id;
|
return (RevCommit) id;
|
||||||
|
|||||||
Reference in New Issue
Block a user