diff --git a/src/applications/differential/field/specification/DifferentialFieldSpecification.php b/src/applications/differential/field/specification/DifferentialFieldSpecification.php index 0cec677549..4f4a2cace2 100644 --- a/src/applications/differential/field/specification/DifferentialFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialFieldSpecification.php @@ -779,6 +779,14 @@ abstract class DifferentialFieldSpecification { return $this->parseCommitMessageObjectList($value, $mailables = false); } + protected function parseCommitMessageUserOrProjectList($value) { + return $this->parseCommitMessageObjectList( + $value, + $mailables = false, + $allow_partial = false, + $projects = true); + } + /** * Parse a list of mailable objects into a canonical PHID list. * @@ -813,36 +821,66 @@ abstract class DifferentialFieldSpecification { $object_map = array(); - $users = id(new PhabricatorUser())->loadAllWhere( - '(username IN (%Ls))', - $value); - - $user_map = mpull($users, 'getPHID', 'getUsername'); - foreach ($user_map as $username => $phid) { - // Usernames may have uppercase letters in them. Put both names in the - // map so we can try the original case first, so that username *always* - // works in weird edge cases where some other mailable object collides. - $object_map[$username] = $phid; - $object_map[strtolower($username)] = $phid; + $project_names = array(); + $other_names = array(); + foreach ($value as $item) { + if (preg_match('/^#/', $item)) { + $project_names[$item] = ltrim(phutil_utf8_strtolower($item), '#').'/'; + } else { + $other_names[] = $item; + } } - if ($include_mailables) { - $mailables = id(new PhabricatorMetaMTAMailingList())->loadAllWhere( - '(email IN (%Ls)) OR (name IN (%Ls))', - $value, - $value); - $object_map += mpull($mailables, 'getPHID', 'getName'); - $object_map += mpull($mailables, 'getPHID', 'getEmail'); + if ($project_names) { + // TODO: (T603) This should probably be policy-aware, although maybe not, + // since we generally don't want to destroy data and it doesn't leak + // anything? + $projects = id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPhrictionSlugs($project_names) + ->execute(); + + $reverse_map = array_flip($project_names); + foreach ($projects as $project) { + $reverse_key = $project->getPhrictionSlug(); + if (isset($reverse_map[$reverse_key])) { + $object_map[$reverse_map[$reverse_key]] = $project->getPHID(); + } + } + } + + if ($other_names) { + $users = id(new PhabricatorUser())->loadAllWhere( + '(username IN (%Ls))', + $other_names); + + $user_map = mpull($users, 'getPHID', 'getUsername'); + foreach ($user_map as $username => $phid) { + // Usernames may have uppercase letters in them. Put both names in the + // map so we can try the original case first, so that username *always* + // works in weird edge cases where some other mailable object collides. + $object_map[$username] = $phid; + $object_map[strtolower($username)] = $phid; + } + + if ($include_mailables) { + $mailables = id(new PhabricatorMetaMTAMailingList())->loadAllWhere( + '(email IN (%Ls)) OR (name IN (%Ls))', + $other_names, + $other_names); + $object_map += mpull($mailables, 'getPHID', 'getName'); + $object_map += mpull($mailables, 'getPHID', 'getEmail'); + } } $invalid = array(); $results = array(); foreach ($value as $name) { if (empty($object_map[$name])) { - if (empty($object_map[strtolower($name)])) { + if (empty($object_map[phutil_utf8_strtolower($name)])) { $invalid[] = $name; } else { - $results[] = $object_map[strtolower($name)]; + $results[] = $object_map[phutil_utf8_strtolower($name)]; } } else { $results[] = $object_map[$name]; diff --git a/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php b/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php index 24860ea951..81ba72d150 100644 --- a/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php @@ -15,7 +15,7 @@ final class DifferentialReviewersFieldSpecification } public function renderLabelForRevisionView() { - return 'Reviewers:'; + return pht('Reviewers'); } public function renderValueForRevisionView() { @@ -144,7 +144,7 @@ final class DifferentialReviewersFieldSpecification ->setLabel(pht('Reviewers')) ->setName('reviewers') ->setUser($this->getUser()) - ->setDatasource('/typeahead/common/users/') + ->setDatasource('/typeahead/common/usersorprojects/') ->setValue($reviewer_map) ->setError($this->error); } @@ -179,9 +179,11 @@ final class DifferentialReviewersFieldSpecification return null; } + $project_type = PhabricatorProjectPHIDTypeProject::TYPECONST; + $names = array(); foreach ($this->reviewers as $phid) { - $names[] = $this->getHandle($phid)->getName(); + $names[] = $this->getHandle($phid)->getObjectName(); } return implode(', ', $names); @@ -195,7 +197,7 @@ final class DifferentialReviewersFieldSpecification } public function parseValueFromCommitMessage($value) { - return $this->parseCommitMessageUserList($value); + return $this->parseCommitMessageUserOrProjectList($value); } public function shouldAppearOnRevisionList() { @@ -242,7 +244,8 @@ final class DifferentialReviewersFieldSpecification $handles = array_select_keys( $handles, array($this->getRevision()->getPrimaryReviewer())) + $handles; - $names = mpull($handles, 'getName'); + + $names = mpull($handles, 'getObjectName'); return 'Reviewers: '.implode(', ', $names); } diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 3326c961ce..d455ddb19f 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -914,6 +914,7 @@ final class DifferentialRevisionQuery $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($revisions, 'getPHID')) ->withEdgeTypes(array($type_reviewer)) + ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) ->execute(); foreach ($revisions as $revision) { @@ -923,6 +924,7 @@ final class DifferentialRevisionQuery $data[] = array( 'relation' => DifferentialRevision::RELATION_REVIEWER, 'objectPHID' => $dst_phid, + 'reasonPHID' => null, ); } @@ -1024,11 +1026,11 @@ final class DifferentialRevisionQuery ->withSourcePHIDs(mpull($revisions, 'getPHID')) ->withEdgeTypes(array($edge_type)) ->needEdgeData(true) + ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) ->execute(); foreach ($revisions as $revision) { $revision_edges = $edges[$revision->getPHID()][$edge_type]; - $reviewers = array(); foreach ($revision_edges as $user_phid => $edge) { $data = $edge['data']; diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index b899848992..ef070f62bd 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -237,11 +237,13 @@ final class DifferentialRevision extends DifferentialDAO $reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getPHID(), PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER); + $reviewer_phids = array_reverse($reviewer_phids); foreach ($reviewer_phids as $phid) { $data[] = array( 'relation' => self::RELATION_REVIEWER, 'objectPHID' => $phid, + 'reasonPHID' => null, ); } diff --git a/src/applications/differential/view/DifferentialAddCommentView.php b/src/applications/differential/view/DifferentialAddCommentView.php index 76f2bffcb7..ceba6a2197 100644 --- a/src/applications/differential/view/DifferentialAddCommentView.php +++ b/src/applications/differential/view/DifferentialAddCommentView.php @@ -118,12 +118,12 @@ final class DifferentialAddCommentView extends AphrontView { 'add_reviewers' => 1, 'resign' => 1, ), - 'src' => '/typeahead/common/users/', + 'src' => '/typeahead/common/usersorprojects/', 'value' => $this->reviewers, 'row' => 'add-reviewers', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'labels' => $add_reviewers_labels, - 'placeholder' => pht('Type a user name...'), + 'placeholder' => pht('Type a user or project name...'), ), 'add-ccs-tokenizer' => array( 'actions' => array('add_ccs' => 1), diff --git a/src/applications/notification/PhabricatorNotificationQuery.php b/src/applications/notification/PhabricatorNotificationQuery.php index ec5ba3603b..736990e08e 100644 --- a/src/applications/notification/PhabricatorNotificationQuery.php +++ b/src/applications/notification/PhabricatorNotificationQuery.php @@ -107,4 +107,8 @@ final class PhabricatorNotificationQuery return $this->formatWhereClause($where); } + protected function getPagingValue($item) { + return $item->getChronologicalKey(); + } + } diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index 05eddbd592..2788fe9cfc 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -14,6 +14,19 @@ final class PhabricatorObjectHandle private $status = PhabricatorObjectHandleStatus::STATUS_OPEN; private $complete; private $disabled; + private $objectName; + + public function setObjectName($object_name) { + $this->objectName = $object_name; + return $this; + } + + public function getObjectName() { + if (!$this->objectName) { + return $this->getName(); + } + return $this->objectName; + } public function setURI($uri) { $this->uri = $uri; diff --git a/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php b/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php index e160caebcb..5b7bbbe936 100644 --- a/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php +++ b/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php @@ -39,6 +39,7 @@ final class PhabricatorProjectPHIDTypeProject extends PhabricatorPHIDType { $id = $project->getID(); $handle->setName($name); + $handle->setObjectName('#'.rtrim($project->getPhrictionSlug(), '/')); $handle->setURI("/project/view/{$id}/"); } } diff --git a/src/infrastructure/edges/query/PhabricatorEdgeQuery.php b/src/infrastructure/edges/query/PhabricatorEdgeQuery.php index 675354bedc..e46c65ad0e 100644 --- a/src/infrastructure/edges/query/PhabricatorEdgeQuery.php +++ b/src/infrastructure/edges/query/PhabricatorEdgeQuery.php @@ -26,6 +26,10 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { private $edgeTypes; private $resultSet; + const ORDER_OLDEST_FIRST = 'order:oldest'; + const ORDER_NEWEST_FIRST = 'order:newest'; + private $order = self::ORDER_NEWEST_FIRST; + private $needEdgeData; @@ -74,6 +78,20 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { } + /** + * Configure the order edge results are returned in. + * + * @param const Order constant. + * @return this + * + * @task config + */ + public function setOrder($order) { + $this->order = $order; + return $this; + } + + /** * When loading edges, also load edge data. * @@ -303,7 +321,11 @@ final class PhabricatorEdgeQuery extends PhabricatorQuery { * @task internal */ private function buildOrderClause($conn_r) { - return 'ORDER BY edge.dateCreated DESC, edge.seq ASC'; + if ($this->order == self::ORDER_NEWEST_FIRST) { + return 'ORDER BY edge.dateCreated DESC, edge.seq DESC'; + } else { + return 'ORDER BY edge.dateCreated ASC, edge.seq ASC'; + } } }