2014-02-21 20:54:32 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class DifferentialReviewersField
|
|
|
|
extends DifferentialCoreCustomField {
|
|
|
|
|
|
|
|
public function getFieldKey() {
|
|
|
|
return 'differential:reviewers';
|
|
|
|
}
|
|
|
|
|
2014-03-08 02:05:00 +01:00
|
|
|
public function getFieldKeyForConduit() {
|
|
|
|
return 'reviewerPHIDs';
|
|
|
|
}
|
|
|
|
|
2014-02-21 20:54:32 +01:00
|
|
|
public function getFieldName() {
|
|
|
|
return pht('Reviewers');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFieldDescription() {
|
|
|
|
return pht('Manage reviewers.');
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function readValueFromRevision(
|
|
|
|
DifferentialRevision $revision) {
|
|
|
|
return $revision->getReviewerStatus();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getNewValueForApplicationTransactions() {
|
|
|
|
$specs = array();
|
|
|
|
foreach ($this->getValue() as $reviewer) {
|
|
|
|
$specs[$reviewer->getReviewerPHID()] = array(
|
|
|
|
'data' => $reviewer->getEdgeData(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return array('=' => $specs);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function readValueFromRequest(AphrontRequest $request) {
|
2016-05-17 02:10:13 +02:00
|
|
|
$datasource = id(new DifferentialBlockingReviewerDatasource())
|
|
|
|
->setViewer($request->getViewer());
|
2014-02-21 20:54:32 +01:00
|
|
|
|
|
|
|
$new_phids = $request->getArr($this->getFieldKey());
|
2016-05-17 02:10:13 +02:00
|
|
|
$new_phids = $datasource->evaluateTokens($new_phids);
|
|
|
|
|
2016-05-17 02:51:26 +02:00
|
|
|
$reviewers = array();
|
2016-05-17 02:10:13 +02:00
|
|
|
foreach ($new_phids as $spec) {
|
|
|
|
if (!is_array($spec)) {
|
2016-05-17 02:51:26 +02:00
|
|
|
$reviewers[$spec] = DifferentialReviewerStatus::STATUS_ADDED;
|
|
|
|
} else {
|
|
|
|
$reviewers[$spec['phid']] = $spec['type'];
|
2016-05-17 02:10:13 +02:00
|
|
|
}
|
|
|
|
}
|
2014-02-21 20:54:32 +01:00
|
|
|
|
2016-05-17 02:51:26 +02:00
|
|
|
$this->updateReviewers($this->getValue(), $reviewers);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function updateReviewers(array $old_reviewers, array $new_map) {
|
|
|
|
// Compute a new set of reviewer objects. We're going to respect the new
|
|
|
|
// reviewer order, add or remove any new or missing reviewers, and respect
|
|
|
|
// any blocking or unblocking changes. For reviewers who were there before
|
|
|
|
// and are still there, we're going to keep the old value because it
|
|
|
|
// may be something like "Accept", "Reject", etc.
|
|
|
|
|
|
|
|
$old_map = mpull($old_reviewers, 'getStatus', 'getReviewerPHID');
|
|
|
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
|
|
|
|
|
|
|
$new_reviewers = array();
|
|
|
|
foreach ($new_map as $phid => $new) {
|
|
|
|
$old = idx($old_map, $phid);
|
2016-05-17 02:10:13 +02:00
|
|
|
|
|
|
|
// If we have an old status and this didn't make the reviewer blocking
|
|
|
|
// or nonblocking, just retain the old status. This makes sure we don't
|
|
|
|
// throw away rejects, accepts, etc.
|
|
|
|
if ($old) {
|
|
|
|
$is_block = ($old !== $status_blocking && $new === $status_blocking);
|
|
|
|
$is_unblock = ($old === $status_blocking && $new !== $status_blocking);
|
|
|
|
if (!$is_block && !$is_unblock) {
|
2016-05-17 02:51:26 +02:00
|
|
|
$new_reviewers[$phid] = $old;
|
2016-05-17 02:10:13 +02:00
|
|
|
continue;
|
|
|
|
}
|
2014-02-21 20:54:32 +01:00
|
|
|
}
|
2016-05-17 02:10:13 +02:00
|
|
|
|
2016-05-17 02:51:26 +02:00
|
|
|
$new_reviewers[$phid] = $new;
|
2016-05-17 02:10:13 +02:00
|
|
|
}
|
|
|
|
|
2016-05-17 02:51:26 +02:00
|
|
|
foreach ($new_reviewers as $phid => $status) {
|
|
|
|
$new_reviewers[$phid] = new DifferentialReviewer(
|
2016-05-17 02:10:13 +02:00
|
|
|
$phid,
|
|
|
|
array(
|
|
|
|
'status' => $status,
|
|
|
|
));
|
2014-02-21 20:54:32 +01:00
|
|
|
}
|
|
|
|
|
2016-05-17 02:51:26 +02:00
|
|
|
$this->setValue($new_reviewers);
|
2014-02-21 20:54:32 +01:00
|
|
|
}
|
|
|
|
|
2015-03-31 23:10:55 +02:00
|
|
|
public function renderEditControl(array $handles) {
|
2016-05-17 02:10:13 +02:00
|
|
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
|
|
|
|
|
|
|
$value = array();
|
|
|
|
foreach ($this->getValue() as $reviewer) {
|
|
|
|
$phid = $reviewer->getReviewerPHID();
|
|
|
|
if ($reviewer->getStatus() == $status_blocking) {
|
|
|
|
$value[] = 'blocking('.$phid.')';
|
|
|
|
} else {
|
|
|
|
$value[] = $phid;
|
|
|
|
}
|
2014-10-13 21:57:08 +02:00
|
|
|
}
|
2014-02-21 20:54:32 +01:00
|
|
|
|
|
|
|
return id(new AphrontFormTokenizerControl())
|
2015-03-31 23:10:55 +02:00
|
|
|
->setUser($this->getViewer())
|
2014-02-21 20:54:32 +01:00
|
|
|
->setName($this->getFieldKey())
|
2016-05-17 02:10:13 +02:00
|
|
|
->setDatasource(new DifferentialReviewerDatasource())
|
|
|
|
->setValue($value)
|
2014-02-21 20:54:32 +01:00
|
|
|
->setError($this->getFieldError())
|
|
|
|
->setLabel($this->getFieldName());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionType() {
|
|
|
|
return PhabricatorTransactions::TYPE_EDGE;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getApplicationTransactionMetadata() {
|
|
|
|
return array(
|
2015-01-01 04:43:26 +01:00
|
|
|
'edge:type' => DifferentialRevisionHasReviewerEdgeType::EDGECONST,
|
2014-02-21 20:54:32 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-02-26 23:46:18 +01:00
|
|
|
public function shouldAppearInPropertyView() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderPropertyViewLabel() {
|
|
|
|
return $this->getFieldName();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRequiredHandlePHIDsForPropertyView() {
|
|
|
|
return mpull($this->getUserReviewers(), 'getReviewerPHID');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderPropertyViewValue(array $handles) {
|
|
|
|
$reviewers = $this->getUserReviewers();
|
|
|
|
if (!$reviewers) {
|
|
|
|
return phutil_tag('em', array(), pht('None'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$view = id(new DifferentialReviewersView())
|
|
|
|
->setUser($this->getViewer())
|
|
|
|
->setReviewers($reviewers)
|
|
|
|
->setHandles($handles);
|
|
|
|
|
|
|
|
// TODO: Active diff stuff.
|
|
|
|
|
|
|
|
return $view;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getUserReviewers() {
|
|
|
|
$reviewers = array();
|
|
|
|
foreach ($this->getObject()->getReviewerStatus() as $reviewer) {
|
|
|
|
if ($reviewer->isUser()) {
|
|
|
|
$reviewers[] = $reviewer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $reviewers;
|
|
|
|
}
|
2014-03-08 02:05:00 +01:00
|
|
|
|
|
|
|
public function shouldAppearInCommitMessage() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function shouldAppearInCommitMessageTemplate() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommitMessageLabels() {
|
|
|
|
return array(
|
|
|
|
'Reviewer',
|
|
|
|
'Reviewers',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function parseValueFromCommitMessage($value) {
|
2016-05-18 02:41:20 +02:00
|
|
|
$results = $this->parseObjectList(
|
2014-03-08 02:05:00 +01:00
|
|
|
$value,
|
|
|
|
array(
|
2014-07-24 00:05:46 +02:00
|
|
|
PhabricatorPeopleUserPHIDType::TYPECONST,
|
|
|
|
PhabricatorProjectProjectPHIDType::TYPECONST,
|
Allow monogrammed objects to be parsed from the `arc` command line in "Reviewers" and similar fields
Summary:
Ref T10939. This allows the CLI to parse reviewers and subscribers like this:
```Reviewers: epriestley, O123 Some Package Name```
The rule goes:
- If a reviewer or subscriber starts with a monogram (like `X111`), just look that up and ignore everything until the next comma.
- Otherwise, split it on spaces and look up each part.
This means that these are valid:
```
alincoln htaft
alincoln, htaft
#a #b epriestley
O123 Some Package, epriestley, #b
```
I think the only real downside is that this:
```
O123 Some Package epriestley
```
...ignores the "epriestley" part. However, I don't expect users to be typing package monograms manually -- they just need to be representable by `arc land` and `arc diff --edit` and such. Those flows will always add commas and make the parse unambiguous.
Test Plan:
- Added test coverage.
- `amend --show`'d a revision with a package subscriber (this isn't currently possible to produce using the web UI, it came from a future change) and saw `Subscribers: O123 package name, usera, userb`.
- Updated a revision with a package subscriber.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T10939
Differential Revision: https://secure.phabricator.com/D15911
2016-05-13 16:56:52 +02:00
|
|
|
PhabricatorOwnersPackagePHIDType::TYPECONST,
|
2016-05-17 02:51:26 +02:00
|
|
|
),
|
|
|
|
false,
|
|
|
|
array('!'));
|
2016-05-18 02:41:20 +02:00
|
|
|
|
|
|
|
return $this->flattenReviewers($results);
|
2014-03-08 02:05:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getRequiredHandlePHIDsForCommitMessage() {
|
|
|
|
return mpull($this->getValue(), 'getReviewerPHID');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function readValueFromCommitMessage($value) {
|
2016-05-18 02:41:20 +02:00
|
|
|
$value = $this->inflateReviewers($value);
|
|
|
|
|
2014-03-08 02:05:00 +01:00
|
|
|
$reviewers = array();
|
2016-05-17 02:51:26 +02:00
|
|
|
foreach ($value as $spec) {
|
|
|
|
$phid = $spec['phid'];
|
|
|
|
|
|
|
|
$is_blocking = isset($spec['suffixes']['!']);
|
|
|
|
if ($is_blocking) {
|
|
|
|
$status = DifferentialReviewerStatus::STATUS_BLOCKING;
|
2014-03-12 01:12:47 +01:00
|
|
|
} else {
|
2016-05-17 02:51:26 +02:00
|
|
|
$status = DifferentialReviewerStatus::STATUS_ADDED;
|
2014-03-12 01:12:47 +01:00
|
|
|
}
|
2016-05-17 02:51:26 +02:00
|
|
|
|
|
|
|
$reviewers[$phid] = $status;
|
2014-03-08 02:05:00 +01:00
|
|
|
}
|
2014-03-12 01:12:47 +01:00
|
|
|
|
2016-05-17 02:51:26 +02:00
|
|
|
$this->updateReviewers(
|
|
|
|
$this->getObject()->getReviewerStatus(),
|
|
|
|
$reviewers);
|
2014-03-08 02:05:00 +01:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function renderCommitMessageValue(array $handles) {
|
2016-05-17 02:51:26 +02:00
|
|
|
$suffixes = array();
|
|
|
|
|
|
|
|
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
|
|
|
|
|
|
|
|
foreach ($this->getValue() as $reviewer) {
|
|
|
|
if ($reviewer->getStatus() == $status_blocking) {
|
|
|
|
$phid = $reviewer->getReviewerPHID();
|
|
|
|
$suffixes[$phid] = '!';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->renderObjectList($handles, $suffixes);
|
2014-03-08 02:05:00 +01:00
|
|
|
}
|
|
|
|
|
2014-03-12 14:04:30 +01:00
|
|
|
public function validateCommitMessageValue($value) {
|
2016-05-24 02:04:27 +02:00
|
|
|
if (!$value) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-12 14:04:30 +01:00
|
|
|
$author_phid = $this->getObject()->getAuthorPHID();
|
|
|
|
|
|
|
|
$config_self_accept_key = 'differential.allow-self-accept';
|
|
|
|
$allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key);
|
|
|
|
|
2016-05-23 19:42:05 +02:00
|
|
|
$value = $this->inflateReviewers($value);
|
2016-05-17 02:51:26 +02:00
|
|
|
foreach ($value as $spec) {
|
|
|
|
$phid = $spec['phid'];
|
|
|
|
|
2014-03-12 14:04:30 +01:00
|
|
|
if (($phid == $author_phid) && !$allow_self_accept) {
|
|
|
|
throw new DifferentialFieldValidationException(
|
|
|
|
pht('The author of a revision can not be a reviewer.'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-02 13:58:51 +02:00
|
|
|
public function getRequiredHandlePHIDsForRevisionHeaderWarnings() {
|
|
|
|
return mpull($this->getValue(), 'getReviewerPHID');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getWarningsForRevisionHeader(array $handles) {
|
|
|
|
$revision = $this->getObject();
|
|
|
|
|
|
|
|
$status_needs_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
|
|
|
|
if ($revision->getStatus() != $status_needs_review) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->getValue() as $reviewer) {
|
|
|
|
if (!$handles[$reviewer->getReviewerPHID()]->isDisabled()) {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$warnings = array();
|
|
|
|
if ($this->getValue()) {
|
|
|
|
$warnings[] = pht(
|
|
|
|
'This revision needs review, but all specified reviewers are '.
|
|
|
|
'disabled or inactive.');
|
|
|
|
} else {
|
|
|
|
$warnings[] = pht(
|
|
|
|
'This revision needs review, but there are no reviewers specified.');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $warnings;
|
|
|
|
}
|
2014-03-12 14:04:30 +01:00
|
|
|
|
2016-05-17 02:51:26 +02:00
|
|
|
public function getProTips() {
|
|
|
|
return array(
|
|
|
|
pht(
|
|
|
|
'You can mark a reviewer as blocking by adding an exclamation '.
|
|
|
|
'mark ("!") after their name.'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-05-18 02:41:20 +02:00
|
|
|
private function flattenReviewers(array $values) {
|
|
|
|
// NOTE: For now, `arc` relies on this field returning only scalars, so we
|
|
|
|
// need to reduce the results into scalars. See T10981.
|
|
|
|
$result = array();
|
|
|
|
|
|
|
|
foreach ($values as $value) {
|
|
|
|
$result[] = $value['phid'].implode('', array_keys($value['suffixes']));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function inflateReviewers(array $values) {
|
|
|
|
$result = array();
|
|
|
|
|
|
|
|
foreach ($values as $value) {
|
|
|
|
if (substr($value, -1) == '!') {
|
|
|
|
$value = substr($value, 0, -1);
|
|
|
|
$suffixes = array('!' => '!');
|
|
|
|
} else {
|
|
|
|
$suffixes = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$result[] = array(
|
|
|
|
'phid' => $value,
|
|
|
|
'suffixes' => $suffixes,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2014-02-21 20:54:32 +01:00
|
|
|
}
|