Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15285225
D16196.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
37 KB
Referenced Files
None
Subscribers
None
D16196.id.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1420,6 +1420,7 @@
'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php',
'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php',
'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php',
+ 'ManiphestTaskCloseAsDuplicateRelationship' => 'applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php',
'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php',
'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php',
'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php',
@@ -1430,6 +1431,7 @@
'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
'ManiphestTaskHasCommitRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php',
+ 'ManiphestTaskHasDuplicateTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php',
'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php',
'ManiphestTaskHasMockRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php',
'ManiphestTaskHasParentRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasParentRelationship.php',
@@ -1438,10 +1440,12 @@
'ManiphestTaskHasSubtaskRelationship' => 'applications/maniphest/relationship/ManiphestTaskHasSubtaskRelationship.php',
'ManiphestTaskHeraldField' => 'applications/maniphest/herald/ManiphestTaskHeraldField.php',
'ManiphestTaskHeraldFieldGroup' => 'applications/maniphest/herald/ManiphestTaskHeraldFieldGroup.php',
+ 'ManiphestTaskIsDuplicateOfTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php',
'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php',
'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php',
'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php',
'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php',
+ 'ManiphestTaskMergeInRelationship' => 'applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php',
'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php',
'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php',
'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php',
@@ -3372,7 +3376,6 @@
'PhabricatorSearchApplication' => 'applications/search/application/PhabricatorSearchApplication.php',
'PhabricatorSearchApplicationSearchEngine' => 'applications/search/query/PhabricatorSearchApplicationSearchEngine.php',
'PhabricatorSearchApplicationStorageEnginePanel' => 'applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php',
- 'PhabricatorSearchAttachController' => 'applications/search/controller/PhabricatorSearchAttachController.php',
'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php',
'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php',
'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php',
@@ -3415,7 +3418,6 @@
'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php',
'PhabricatorSearchSchemaSpec' => 'applications/search/storage/PhabricatorSearchSchemaSpec.php',
'PhabricatorSearchScopeSetting' => 'applications/settings/setting/PhabricatorSearchScopeSetting.php',
- 'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php',
'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php',
'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php',
'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php',
@@ -5929,6 +5931,7 @@
'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField',
'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule',
+ 'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType',
@@ -5939,6 +5942,7 @@
'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasCommitRelationship' => 'ManiphestTaskRelationship',
+ 'ManiphestTaskHasDuplicateTaskEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskHasMockRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHasParentRelationship' => 'ManiphestTaskRelationship',
@@ -5947,10 +5951,12 @@
'ManiphestTaskHasSubtaskRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskHeraldField' => 'HeraldField',
'ManiphestTaskHeraldFieldGroup' => 'HeraldFieldGroup',
+ 'ManiphestTaskIsDuplicateOfTaskEdgeType' => 'PhabricatorEdgeType',
'ManiphestTaskListController' => 'ManiphestController',
'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType',
'ManiphestTaskListView' => 'ManiphestView',
'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver',
+ 'ManiphestTaskMergeInRelationship' => 'ManiphestTaskRelationship',
'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource',
'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver',
'ManiphestTaskPHIDType' => 'PhabricatorPHIDType',
@@ -8210,7 +8216,6 @@
'PhabricatorSearchApplication' => 'PhabricatorApplication',
'PhabricatorSearchApplicationSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorSearchApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel',
- 'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchBaseController' => 'PhabricatorController',
'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField',
'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions',
@@ -8253,7 +8258,6 @@
'PhabricatorSearchResultView' => 'AphrontView',
'PhabricatorSearchSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorSearchScopeSetting' => 'PhabricatorInternalSetting',
- 'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController',
'PhabricatorSearchSelectField' => 'PhabricatorSearchField',
'PhabricatorSearchStringListField' => 'PhabricatorSearchField',
'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField',
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -201,6 +201,8 @@
$parent_key = ManiphestTaskHasParentRelationship::RELATIONSHIPKEY;
$subtask_key = ManiphestTaskHasSubtaskRelationship::RELATIONSHIPKEY;
+ $merge_key = ManiphestTaskMergeInRelationship::RELATIONSHIPKEY;
+ $close_key = ManiphestTaskCloseAsDuplicateRelationship::RELATIONSHIPKEY;
$task_submenu[] = $relationship_list->getRelationship($parent_key)
->newAction($task);
@@ -208,12 +210,11 @@
$task_submenu[] = $relationship_list->getRelationship($subtask_key)
->newAction($task);
- $task_submenu[] = id(new PhabricatorActionView())
- ->setName(pht('Merge Duplicates In'))
- ->setHref("/search/attach/{$phid}/TASK/merge/")
- ->setIcon('fa-compress')
- ->setDisabled(!$can_edit)
- ->setWorkflow(true);
+ $task_submenu[] = $relationship_list->getRelationship($merge_key)
+ ->newAction($task);
+
+ $task_submenu[] = $relationship_list->getRelationship($close_key)
+ ->newAction($task);
$curtain->addAction(
id(new PhabricatorActionView())
diff --git a/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php b/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php
@@ -0,0 +1,16 @@
+<?php
+
+final class ManiphestTaskHasDuplicateTaskEdgeType
+ extends PhabricatorEdgeType {
+
+ const EDGECONST = 62;
+
+ public function getInverseEdgeConstant() {
+ return ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST;
+ }
+
+ public function shouldWriteInverseTransactions() {
+ return true;
+ }
+
+}
diff --git a/src/applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php b/src/applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php
@@ -0,0 +1,16 @@
+<?php
+
+final class ManiphestTaskIsDuplicateOfTaskEdgeType
+ extends PhabricatorEdgeType {
+
+ const EDGECONST = 63;
+
+ public function getInverseEdgeConstant() {
+ return ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST;
+ }
+
+ public function shouldWriteInverseTransactions() {
+ return true;
+ }
+
+}
diff --git a/src/applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php
@@ -0,0 +1,84 @@
+<?php
+
+final class ManiphestTaskCloseAsDuplicateRelationship
+ extends ManiphestTaskRelationship {
+
+ const RELATIONSHIPKEY = 'task.close-as-duplicate';
+
+ public function getEdgeConstant() {
+ return ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST;
+ }
+
+ protected function getActionName() {
+ return pht('Close As Duplicate');
+ }
+
+ protected function getActionIcon() {
+ return 'fa-times';
+ }
+
+ public function canRelateObjects($src, $dst) {
+ return ($dst instanceof ManiphestTask);
+ }
+
+ public function shouldAppearInActionMenu() {
+ return false;
+ }
+
+ public function getDialogTitleText() {
+ return pht('Close As Duplicate');
+ }
+
+ public function getDialogHeaderText() {
+ return pht('Close This Task As a Duplicate Of');
+ }
+
+ public function getDialogButtonText() {
+ return pht('Merge Into Selected Task');
+ }
+
+ protected function newRelationshipSource() {
+ return new ManiphestTaskRelationshipSource();
+ }
+
+ public function getRequiredRelationshipCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ public function canUndoRelationship() {
+ return false;
+ }
+
+ public function willUpdateRelationships($object, array $add, array $rem) {
+
+ // TODO: Communicate this in the UI before users hit this error.
+ if (count($add) > 1) {
+ throw new Exception(
+ pht(
+ 'A task can only be closed as a duplicate of exactly one other '.
+ 'task.'));
+ }
+
+ $task = head($add);
+ return $this->newMergeIntoTransactions($task);
+ }
+
+ public function didUpdateRelationships($object, array $add, array $rem) {
+ $viewer = $this->getViewer();
+ $content_source = $this->getContentSource();
+
+ $task = head($add);
+ $xactions = $this->newMergeFromTransactions(array($object));
+
+ $task->getApplicationTransactionEditor()
+ ->setActor($viewer)
+ ->setContentSource($content_source)
+ ->setContinueOnMissingFields(true)
+ ->setContinueOnNoEffect(true)
+ ->applyTransactions($task, $xactions);
+ }
+
+}
diff --git a/src/applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/relationship/ManiphestTaskMergeInRelationship.php
@@ -0,0 +1,75 @@
+<?php
+
+final class ManiphestTaskMergeInRelationship
+ extends ManiphestTaskRelationship {
+
+ const RELATIONSHIPKEY = 'task.merge-in';
+
+ public function getEdgeConstant() {
+ return ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST;
+ }
+
+ protected function getActionName() {
+ return pht('Merge Duplicates In');
+ }
+
+ protected function getActionIcon() {
+ return 'fa-compress';
+ }
+
+ public function canRelateObjects($src, $dst) {
+ return ($dst instanceof ManiphestTask);
+ }
+
+ public function shouldAppearInActionMenu() {
+ return false;
+ }
+
+ public function getDialogTitleText() {
+ return pht('Merge Duplicates Into This Task');
+ }
+
+ public function getDialogHeaderText() {
+ return pht('Tasks to Close and Merge');
+ }
+
+ public function getDialogButtonText() {
+ return pht('Close and Merge Selected Tasks');
+ }
+
+ protected function newRelationshipSource() {
+ return new ManiphestTaskRelationshipSource();
+ }
+
+ public function getRequiredRelationshipCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ public function canUndoRelationship() {
+ return false;
+ }
+
+ public function willUpdateRelationships($object, array $add, array $rem) {
+ return $this->newMergeFromTransactions($add);
+ }
+
+ public function didUpdateRelationships($object, array $add, array $rem) {
+ $viewer = $this->getViewer();
+ $content_source = $this->getContentSource();
+
+ foreach ($add as $task) {
+ $xactions = $this->newMergeIntoTransactions($object);
+
+ $task->getApplicationTransactionEditor()
+ ->setActor($viewer)
+ ->setContentSource($content_source)
+ ->setContinueOnMissingFields(true)
+ ->setContinueOnNoEffect(true)
+ ->applyTransactions($task, $xactions);
+ }
+ }
+
+}
diff --git a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php
--- a/src/applications/maniphest/relationship/ManiphestTaskRelationship.php
+++ b/src/applications/maniphest/relationship/ManiphestTaskRelationship.php
@@ -16,4 +16,52 @@
return ($object instanceof ManiphestTask);
}
+ protected function newMergeIntoTransactions(ManiphestTask $task) {
+ return array(
+ id(new ManiphestTransaction())
+ ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO)
+ ->setNewValue($task->getPHID()),
+ );
+ }
+
+ protected function newMergeFromTransactions(array $tasks) {
+ $xactions = array();
+
+ $subscriber_phids = $this->loadMergeSubscriberPHIDs($tasks);
+
+ $xactions[] = id(new ManiphestTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
+ ->setNewValue(array('+' => $subscriber_phids));
+
+ $xactions[] = id(new ManiphestTransaction())
+ ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM)
+ ->setNewValue(mpull($tasks, 'getPHID'));
+
+ return $xactions;
+ }
+
+ private function loadMergeSubscriberPHIDs(array $tasks) {
+ $phids = array();
+
+ foreach ($tasks as $task) {
+ $phids[] = $task->getAuthorPHID();
+ $phids[] = $task->getOwnerPHID();
+ }
+
+ $subscribers = id(new PhabricatorSubscribersQuery())
+ ->withObjectPHIDs(mpull($tasks, 'getPHID'))
+ ->execute();
+
+ foreach ($subscribers as $phid => $subscriber_list) {
+ foreach ($subscriber_list as $subscriber) {
+ $phids[] = $subscriber;
+ }
+ }
+
+ $phids = array_unique($phids);
+ $phids = array_filter($phids);
+
+ return $phids;
+ }
+
}
diff --git a/src/applications/search/application/PhabricatorSearchApplication.php b/src/applications/search/application/PhabricatorSearchApplication.php
--- a/src/applications/search/application/PhabricatorSearchApplication.php
+++ b/src/applications/search/application/PhabricatorSearchApplication.php
@@ -30,10 +30,6 @@
return array(
'/search/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorSearchController',
- 'attach/(?P<phid>[^/]+)/(?P<type>\w+)/(?:(?P<action>\w+)/)?'
- => 'PhabricatorSearchAttachController',
- 'select/(?P<type>\w+)/(?:(?P<action>\w+)/)?'
- => 'PhabricatorSearchSelectController',
'index/(?P<phid>[^/]+)/' => 'PhabricatorSearchIndexController',
'hovercard/'
=> 'PhabricatorSearchHovercardController',
diff --git a/src/applications/search/controller/PhabricatorSearchAttachController.php b/src/applications/search/controller/PhabricatorSearchAttachController.php
deleted file mode 100644
--- a/src/applications/search/controller/PhabricatorSearchAttachController.php
+++ /dev/null
@@ -1,330 +0,0 @@
-<?php
-
-final class PhabricatorSearchAttachController
- extends PhabricatorSearchBaseController {
-
- public function handleRequest(AphrontRequest $request) {
- $user = $request->getUser();
- $phid = $request->getURIData('phid');
- $attach_type = $request->getURIData('type');
- $action = $request->getURIData('action', self::ACTION_ATTACH);
-
- $handle = id(new PhabricatorHandleQuery())
- ->setViewer($user)
- ->withPHIDs(array($phid))
- ->executeOne();
-
- $object_type = $handle->getType();
-
- $object = id(new PhabricatorObjectQuery())
- ->setViewer($user)
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->withPHIDs(array($phid))
- ->executeOne();
-
- if (!$object) {
- return new Aphront404Response();
- }
-
- $edge_type = null;
- switch ($action) {
- case self::ACTION_EDGE:
- case self::ACTION_DEPENDENCIES:
- case self::ACTION_BLOCKS:
- case self::ACTION_ATTACH:
- $edge_type = $this->getEdgeType($object_type, $attach_type);
- break;
- }
-
- if ($request->isFormPost()) {
- $phids = explode(';', $request->getStr('phids'));
- $phids = array_filter($phids);
- $phids = array_values($phids);
-
- if ($edge_type) {
- if (!$object instanceof PhabricatorApplicationTransactionInterface) {
- throw new Exception(
- pht(
- 'Expected object ("%s") to implement interface "%s".',
- get_class($object),
- 'PhabricatorApplicationTransactionInterface'));
- }
-
- $old_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
- $phid,
- $edge_type);
- $add_phids = $phids;
- $rem_phids = array_diff($old_phids, $add_phids);
-
- $txn_editor = $object->getApplicationTransactionEditor()
- ->setActor($user)
- ->setContentSourceFromRequest($request)
- ->setContinueOnMissingFields(true)
- ->setContinueOnNoEffect(true);
-
- $txn_template = $object->getApplicationTransactionTemplate()
- ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
- ->setMetadataValue('edge:type', $edge_type)
- ->setNewValue(array(
- '+' => array_fuse($add_phids),
- '-' => array_fuse($rem_phids),
- ));
-
- try {
- $txn_editor->applyTransactions(
- $object->getApplicationTransactionObject(),
- array($txn_template));
- } catch (PhabricatorEdgeCycleException $ex) {
- $this->raiseGraphCycleException($ex);
- }
-
- return id(new AphrontReloadResponse())->setURI($handle->getURI());
- } else {
- return $this->performMerge($object, $handle, $phids);
- }
- } else {
- if ($edge_type) {
- $phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
- $phid,
- $edge_type);
- } else {
- // This is a merge.
- $phids = array();
- }
- }
-
- $strings = $this->getStrings($attach_type, $action);
-
- $handles = $this->loadViewerHandles($phids);
-
- $obj_dialog = new PhabricatorObjectSelectorDialog();
- $obj_dialog
- ->setUser($user)
- ->setHandles($handles)
- ->setFilters($this->getFilters($strings, $attach_type))
- ->setSelectedFilter($strings['selected'])
- ->setExcluded($phid)
- ->setCancelURI($handle->getURI())
- ->setSearchURI('/search/select/'.$attach_type.'/'.$action.'/')
- ->setTitle($strings['title'])
- ->setHeader($strings['header'])
- ->setButtonText($strings['button'])
- ->setInstructions($strings['instructions']);
-
- $dialog = $obj_dialog->buildDialog();
-
- return id(new AphrontDialogResponse())->setDialog($dialog);
- }
-
- private function performMerge(
- ManiphestTask $task,
- PhabricatorObjectHandle $handle,
- array $phids) {
-
- $user = $this->getRequest()->getUser();
- $response = id(new AphrontReloadResponse())->setURI($handle->getURI());
-
- $phids = array_fill_keys($phids, true);
- unset($phids[$task->getPHID()]); // Prevent merging a task into itself.
-
- if (!$phids) {
- return $response;
- }
-
- $targets = id(new ManiphestTaskQuery())
- ->setViewer($user)
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->withPHIDs(array_keys($phids))
- ->needSubscriberPHIDs(true)
- ->needProjectPHIDs(true)
- ->execute();
-
- if (empty($targets)) {
- return $response;
- }
-
- $editor = id(new ManiphestTransactionEditor())
- ->setActor($user)
- ->setContentSourceFromRequest($this->getRequest())
- ->setContinueOnNoEffect(true)
- ->setContinueOnMissingFields(true);
-
- $cc_vector = array();
- // since we loaded this via a generic object query, go ahead and get the
- // attach the subscriber and project phids now
- $task->attachSubscriberPHIDs(
- PhabricatorSubscribersQuery::loadSubscribersForPHID($task->getPHID()));
- $task->attachProjectPHIDs(
- PhabricatorEdgeQuery::loadDestinationPHIDs($task->getPHID(),
- PhabricatorProjectObjectHasProjectEdgeType::EDGECONST));
-
- $cc_vector[] = $task->getSubscriberPHIDs();
- foreach ($targets as $target) {
- $cc_vector[] = $target->getSubscriberPHIDs();
- $cc_vector[] = array(
- $target->getAuthorPHID(),
- $target->getOwnerPHID(),
- );
-
- $merged_into_txn = id(new ManiphestTransaction())
- ->setTransactionType(ManiphestTransaction::TYPE_MERGED_INTO)
- ->setNewValue($task->getPHID());
-
- $editor->applyTransactions(
- $target,
- array($merged_into_txn));
-
- }
- $all_ccs = array_mergev($cc_vector);
- $all_ccs = array_filter($all_ccs);
- $all_ccs = array_unique($all_ccs);
-
- $add_ccs = id(new ManiphestTransaction())
- ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)
- ->setNewValue(array('=' => $all_ccs));
-
- $merged_from_txn = id(new ManiphestTransaction())
- ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM)
- ->setNewValue(mpull($targets, 'getPHID'));
-
- $editor->applyTransactions(
- $task,
- array($add_ccs, $merged_from_txn));
-
- return $response;
- }
-
- private function getStrings($attach_type, $action) {
- switch ($attach_type) {
- case DifferentialRevisionPHIDType::TYPECONST:
- $noun = pht('Revisions');
- $selected = pht('created');
- break;
- case ManiphestTaskPHIDType::TYPECONST:
- $noun = pht('Tasks');
- $selected = pht('assigned');
- break;
- case PhabricatorRepositoryCommitPHIDType::TYPECONST:
- $noun = pht('Commits');
- $selected = pht('created');
- break;
- case PholioMockPHIDType::TYPECONST:
- $noun = pht('Mocks');
- $selected = pht('created');
- break;
- }
-
- switch ($action) {
- case self::ACTION_EDGE:
- case self::ACTION_ATTACH:
- $dialog_title = pht('Manage Attached %s', $noun);
- $header_text = pht('Currently Attached %s', $noun);
- $button_text = pht('Save %s', $noun);
- $instructions = null;
- break;
- case self::ACTION_MERGE:
- $dialog_title = pht('Merge Duplicate Tasks');
- $header_text = pht('Tasks To Merge');
- $button_text = pht('Merge %s', $noun);
- $instructions = pht(
- 'These tasks will be merged into the current task and then closed. '.
- 'The current task will grow stronger.');
- break;
- case self::ACTION_DEPENDENCIES:
- $dialog_title = pht('Edit Dependencies');
- $header_text = pht('Current Dependencies');
- $button_text = pht('Save Dependencies');
- $instructions = null;
- break;
- case self::ACTION_BLOCKS:
- $dialog_title = pht('Edit Blocking Tasks');
- $header_text = pht('Current Blocking Tasks');
- $button_text = pht('Save Blocking Tasks');
- $instructions = null;
- break;
- }
-
- return array(
- 'target_plural_noun' => $noun,
- 'selected' => $selected,
- 'title' => $dialog_title,
- 'header' => $header_text,
- 'button' => $button_text,
- 'instructions' => $instructions,
- );
- }
-
- private function getFilters(array $strings, $attach_type) {
- if ($attach_type == PholioMockPHIDType::TYPECONST) {
- $filters = array(
- 'created' => pht('Created By Me'),
- 'all' => pht('All %s', $strings['target_plural_noun']),
- );
- } else {
- $filters = array(
- 'assigned' => pht('Assigned to Me'),
- 'created' => pht('Created By Me'),
- 'open' => pht('All Open %s', $strings['target_plural_noun']),
- 'all' => pht('All %s', $strings['target_plural_noun']),
- );
- }
-
- return $filters;
- }
-
- private function getEdgeType($src_type, $dst_type) {
- $t_cmit = PhabricatorRepositoryCommitPHIDType::TYPECONST;
- $t_task = ManiphestTaskPHIDType::TYPECONST;
- $t_drev = DifferentialRevisionPHIDType::TYPECONST;
- $t_mock = PholioMockPHIDType::TYPECONST;
-
- $map = array(
- $t_cmit => array(
- $t_task => DiffusionCommitHasTaskEdgeType::EDGECONST,
- ),
- $t_task => array(
- $t_cmit => ManiphestTaskHasCommitEdgeType::EDGECONST,
- $t_task => ManiphestTaskDependsOnTaskEdgeType::EDGECONST,
- $t_drev => ManiphestTaskHasRevisionEdgeType::EDGECONST,
- $t_mock => ManiphestTaskHasMockEdgeType::EDGECONST,
- ),
- $t_drev => array(
- $t_drev => DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST,
- $t_task => DifferentialRevisionHasTaskEdgeType::EDGECONST,
- ),
- $t_mock => array(
- $t_task => PholioMockHasTaskEdgeType::EDGECONST,
- ),
- );
-
- if (empty($map[$src_type][$dst_type])) {
- return null;
- }
-
- return $map[$src_type][$dst_type];
- }
-
- private function raiseGraphCycleException(PhabricatorEdgeCycleException $ex) {
- $cycle = $ex->getCycle();
-
- $handles = $this->loadViewerHandles($cycle);
- $names = array();
- foreach ($cycle as $cycle_phid) {
- $names[] = $handles[$cycle_phid]->getFullName();
- }
- throw new Exception(
- pht(
- 'You can not create that dependency, because it would create a '.
- 'circular dependency: %s.',
- implode(" \xE2\x86\x92 ", $names)));
- }
-
-}
diff --git a/src/applications/search/controller/PhabricatorSearchRelationshipController.php b/src/applications/search/controller/PhabricatorSearchRelationshipController.php
--- a/src/applications/search/controller/PhabricatorSearchRelationshipController.php
+++ b/src/applications/search/controller/PhabricatorSearchRelationshipController.php
@@ -19,9 +19,16 @@
$src_phid = $object->getPHID();
$edge_type = $relationship->getEdgeConstant();
- $dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
- $src_phid,
- $edge_type);
+ // If this is a normal relationship, users can remove related objects. If
+ // it's a special relationship like a merge, we can't undo it, so we won't
+ // prefill the current related objects.
+ if ($relationship->canUndoRelationship()) {
+ $dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $src_phid,
+ $edge_type);
+ } else {
+ $dst_phids = array();
+ }
$all_phids = $dst_phids;
$all_phids[] = $src_phid;
@@ -44,12 +51,16 @@
// relationships at the same time don't race and overwrite one another.
$add_phids = array_diff($phids, $initial_phids);
$rem_phids = array_diff($initial_phids, $phids);
+ $all_phids = array_merge($add_phids, $rem_phids);
+
+ $capabilities = $relationship->getRequiredRelationshipCapabilities();
- if ($add_phids) {
+ if ($all_phids) {
$dst_objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
- ->withPHIDs($phids)
+ ->withPHIDs($all_phids)
->setRaisePolicyExceptions(true)
+ ->requireCapabilities($capabilities)
->execute();
$dst_objects = mpull($dst_objects, null, 'getPHID');
} else {
@@ -67,6 +78,14 @@
$add_phid));
}
+ if ($add_phid == $src_phid) {
+ throw new Exception(
+ pht(
+ 'You can not create a relationship to object "%s" because '.
+ 'objects can not be related to themselves.',
+ $add_phid));
+ }
+
if (!$relationship->canRelateObjects($object, $dst_object)) {
throw new Exception(
pht(
@@ -81,9 +100,12 @@
return $this->newUnrelatableObjectResponse($ex, $done_uri);
}
+ $content_source = PhabricatorContentSource::newFromRequest($request);
+ $relationship->setContentSource($content_source);
+
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
- ->setContentSourceFromRequest($request)
+ ->setContentSource($content_source)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true);
@@ -96,9 +118,29 @@
'-' => array_fuse($rem_phids),
));
+ $add_objects = array_select_keys($dst_objects, $add_phids);
+ $rem_objects = array_select_keys($dst_objects, $rem_phids);
+
+ if ($add_objects || $rem_objects) {
+ $more_xactions = $relationship->willUpdateRelationships(
+ $object,
+ $add_objects,
+ $rem_objects);
+ foreach ($more_xactions as $xaction) {
+ $xactions[] = $xaction;
+ }
+ }
+
try {
$editor->applyTransactions($object, $xactions);
+ if ($add_objects || $rem_objects) {
+ $relationship->didUpdateRelationships(
+ $object,
+ $add_objects,
+ $rem_objects);
+ }
+
return id(new AphrontRedirectResponse())->setURI($done_uri);
} catch (PhabricatorEdgeCycleException $ex) {
return $this->newGraphCycleResponse($ex, $done_uri);
diff --git a/src/applications/search/controller/PhabricatorSearchRelationshipSourceController.php b/src/applications/search/controller/PhabricatorSearchRelationshipSourceController.php
--- a/src/applications/search/controller/PhabricatorSearchRelationshipSourceController.php
+++ b/src/applications/search/controller/PhabricatorSearchRelationshipSourceController.php
@@ -58,7 +58,7 @@
->execute();
$phids = array_fill_keys(mpull($results, 'getPHID'), true);
- $phids += $this->queryObjectNames($query_str, $capabilities);
+ $phids = $this->queryObjectNames($query, $capabilities) + $phids;
$phids = array_keys($phids);
$handles = $viewer->loadHandles($phids);
@@ -72,15 +72,21 @@
return id(new AphrontAjaxResponse())->setContent($data);
}
- private function queryObjectNames($query, $capabilities) {
+ private function queryObjectNames(
+ PhabricatorSavedQuery $query,
+ array $capabilities) {
+
$request = $this->getRequest();
$viewer = $request->getUser();
+ $types = $query->getParameter('types');
+ $match = $query->getParameter('query');
+
$objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->requireCapabilities($capabilities)
- ->withTypes(array($request->getURIData('type')))
- ->withNames(array($query))
+ ->withTypes($query->getParameter('types'))
+ ->withNames(array($match))
->execute();
return mpull($objects, 'getPHID');
diff --git a/src/applications/search/controller/PhabricatorSearchSelectController.php b/src/applications/search/controller/PhabricatorSearchSelectController.php
deleted file mode 100644
--- a/src/applications/search/controller/PhabricatorSearchSelectController.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-
-final class PhabricatorSearchSelectController
- extends PhabricatorSearchBaseController {
-
- public function handleRequest(AphrontRequest $request) {
- $user = $request->getUser();
- $type = $request->getURIData('type');
- $action = $request->getURIData('action');
-
- $query = new PhabricatorSavedQuery();
- $query_str = $request->getStr('query');
-
- $query->setEngineClassName('PhabricatorSearchApplicationSearchEngine');
- $query->setParameter('query', $query_str);
- $query->setParameter('types', array($type));
-
- $status_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN;
-
- switch ($request->getStr('filter')) {
- case 'assigned':
- $query->setParameter('ownerPHIDs', array($user->getPHID()));
- $query->setParameter('statuses', array($status_open));
- break;
- case 'created';
- $query->setParameter('authorPHIDs', array($user->getPHID()));
- // TODO - if / when we allow pholio mocks to be archived, etc
- // update this
- if ($type != PholioMockPHIDType::TYPECONST) {
- $query->setParameter('statuses', array($status_open));
- }
- break;
- case 'open':
- $query->setParameter('statuses', array($status_open));
- break;
- }
-
- $query->setParameter('excludePHIDs', array($request->getStr('exclude')));
-
- $capabilities = array(PhabricatorPolicyCapability::CAN_VIEW);
- switch ($action) {
- case self::ACTION_MERGE:
- $capabilities[] = PhabricatorPolicyCapability::CAN_EDIT;
- break;
- default:
- break;
- }
-
- $results = id(new PhabricatorSearchDocumentQuery())
- ->setViewer($user)
- ->requireObjectCapabilities($capabilities)
- ->withSavedQuery($query)
- ->setOffset(0)
- ->setLimit(100)
- ->execute();
-
- $phids = array_fill_keys(mpull($results, 'getPHID'), true);
- $phids += $this->queryObjectNames($query_str, $capabilities);
-
- $phids = array_keys($phids);
- $handles = $this->loadViewerHandles($phids);
-
- $data = array();
- foreach ($handles as $handle) {
- $view = new PhabricatorHandleObjectSelectorDataView($handle);
- $data[] = $view->renderData();
- }
-
- return id(new AphrontAjaxResponse())->setContent($data);
- }
-
- private function queryObjectNames($query, $capabilities) {
- $request = $this->getRequest();
- $viewer = $request->getUser();
-
- $objects = id(new PhabricatorObjectQuery())
- ->setViewer($viewer)
- ->requireCapabilities($capabilities)
- ->withTypes(array($request->getURIData('type')))
- ->withNames(array($query))
- ->execute();
-
- return mpull($objects, 'getPHID');
- }
-
-}
diff --git a/src/applications/search/relationship/PhabricatorObjectRelationship.php b/src/applications/search/relationship/PhabricatorObjectRelationship.php
--- a/src/applications/search/relationship/PhabricatorObjectRelationship.php
+++ b/src/applications/search/relationship/PhabricatorObjectRelationship.php
@@ -3,6 +3,7 @@
abstract class PhabricatorObjectRelationship extends Phobject {
private $viewer;
+ private $contentSource;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@@ -13,6 +14,15 @@
return $this->viewer;
}
+ public function setContentSource(PhabricatorContentSource $content_source) {
+ $this->contentSource = $content_source;
+ return $this;
+ }
+
+ public function getContentSource() {
+ return $this->contentSource;
+ }
+
final public function getRelationshipConstant() {
return $this->getPhobjectClassConstant('RELATIONSHIPKEY');
}
@@ -94,4 +104,16 @@
return "/search/rel/{$type}/{$phid}/";
}
+ public function canUndoRelationship() {
+ return true;
+ }
+
+ public function willUpdateRelationships($object, array $add, array $rem) {
+ return array();
+ }
+
+ public function didUpdateRelationships($object, array $add, array $rem) {
+ return;
+ }
+
}
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -609,6 +609,8 @@
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObjectEdgeType::EDGECONST:
+ case ManiphestTaskHasDuplicateTaskEdgeType::EDGECONST:
+ case ManiphestTaskIsDuplicateOfTaskEdgeType::EDGECONST:
return true;
break;
case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST:
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mar 5 2025, 12:07 PM (5 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7225496
Default Alt Text
D16196.id.diff (37 KB)
Attached To
Mode
D16196: Convert Maniphest merge operations to modern Relationship code
Attached
Detach File
Event Timeline
Log In to Comment