From 55373030df9e821b2ffb4a603a2d8aa42235c5a4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 26 Dec 2015 06:28:58 -0800 Subject: [PATCH 01/83] Fix a bad copy/paste in Conduit documentation Summary: This is linking to the wrong article. Test Plan: O.O Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14885 --- src/docs/user/userguide/conduit.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/user/userguide/conduit.diviner b/src/docs/user/userguide/conduit.diviner index 5292736f30..5784d8cd01 100644 --- a/src/docs/user/userguide/conduit.diviner +++ b/src/docs/user/userguide/conduit.diviner @@ -56,7 +56,7 @@ Creating and Editing Objects ============================ For information on creating, editing and updating objects, see -@{article:Conduit API: Using Search Endpoints}. +@{article:Conduit API: Using Edit Endpoints}. Next Steps From 367955f3fd9ce0109ffabc238bdbeafd16d82164 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 26 Dec 2015 12:20:21 -0800 Subject: [PATCH 02/83] Improve UX and messaging for certain errors when landing revisions Summary: Ref T9994. - Allow errors to be dismissed. - Tailor messaging for closed/abandoned revisions. - Reduce scare messaging on land dialog, since it's not really that scary anymore. Test Plan: - Dismissed errors. - Hit new warnings. - Wasn't as scared when landing. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9994 Differential Revision: https://secure.phabricator.com/D14886 --- resources/sql/autopatches/20151226.reop.1.sql | 2 + src/__phutil_library_map__.php | 2 + ...ifferentialRevisionOperationController.php | 11 +--- .../DifferentialRevisionViewController.php | 1 + .../PhabricatorDrydockApplication.php | 1 + ...ckRepositoryOperationDismissController.php | 56 +++++++++++++++++++ .../DrydockLandRepositoryOperation.php | 29 ++++++++-- .../query/DrydockRepositoryOperationQuery.php | 13 +++++ .../storage/DrydockRepositoryOperation.php | 28 +++++++++- .../DrydockRepositoryOperationStatusView.php | 11 ++++ 10 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 resources/sql/autopatches/20151226.reop.1.sql create mode 100644 src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php diff --git a/resources/sql/autopatches/20151226.reop.1.sql b/resources/sql/autopatches/20151226.reop.1.sql new file mode 100644 index 0000000000..4daca60aeb --- /dev/null +++ b/resources/sql/autopatches/20151226.reop.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_drydock.drydock_repositoryoperation + ADD isDismissed BOOL NOT NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d3d33adb15..a024d493e8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -911,6 +911,7 @@ phutil_register_library_map(array( 'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php', + 'DrydockRepositoryOperationDismissController' => 'applications/drydock/controller/DrydockRepositoryOperationDismissController.php', 'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php', 'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php', 'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php', @@ -4900,6 +4901,7 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), + 'DrydockRepositoryOperationDismissController' => 'DrydockController', 'DrydockRepositoryOperationListController' => 'DrydockController', 'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType', 'DrydockRepositoryOperationQuery' => 'DrydockQuery', diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php index 99067f9f2e..feeb6770b8 100644 --- a/src/applications/differential/controller/DifferentialRevisionOperationController.php +++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php @@ -94,8 +94,7 @@ final class DifferentialRevisionOperationController ->setUser($viewer) ->appendRemarkupInstructions( pht( - 'In theory, this will do approximately what `arc land` would do. '. - 'In practice, you will have a riveting adventure instead.')) + '(NOTE) This feature is new and experimental.')) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Onto Branch')) @@ -103,11 +102,7 @@ final class DifferentialRevisionOperationController ->setLimit(1) ->setError($e_ref) ->setValue($v_ref) - ->setDatasource($ref_datasource)) - ->appendRemarkupInstructions( - pht( - '(WARNING) THIS FEATURE IS EXPERIMENTAL AND DANGEROUS! USE IT AT '. - 'YOUR OWN RISK!')); + ->setDatasource($ref_datasource)); return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FORM) @@ -115,7 +110,7 @@ final class DifferentialRevisionOperationController ->setErrors($errors) ->appendForm($form) ->addCancelButton($detail_uri) - ->addSubmitButton(pht('Mutate Repository Unpredictably')); + ->addSubmitButton(pht('Land Revision')); } private function newRefQuery(PhabricatorRepository $repository) { diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 621464cc9b..71f4932fed 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1047,6 +1047,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $operations = id(new DrydockRepositoryOperationQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($revision->getPHID())) + ->withIsDismissed(false) ->withOperationTypes( array( DrydockLandRepositoryOperation::OPCONST, diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index 0b109e68da..6267c26f1e 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -96,6 +96,7 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { '(?P[1-9]\d*)/' => array( '' => 'DrydockRepositoryOperationViewController', 'status/' => 'DrydockRepositoryOperationStatusController', + 'dismiss/' => 'DrydockRepositoryOperationDismissController', ), ), ), diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php new file mode 100644 index 0000000000..28e14bcb90 --- /dev/null +++ b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php @@ -0,0 +1,56 @@ +getViewer(); + $id = $request->getURIData('id'); + + $operation = id(new DrydockRepositoryOperationQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$operation) { + return new Aphront404Response(); + } + + $object_phid = $operation->getObjectPHID(); + $handles = $viewer->loadHandles(array($object_phid)); + $done_uri = $handles[$object_phid]->getURI(); + + if ($operation->getIsDismissed()) { + return $this->newDialog() + ->setTitle(pht('Already Dismissed')) + ->appendParagraph( + pht( + 'This operation has already been dismissed, and can not be '. + 'dismissed any further.')) + ->addCancelButton($done_uri); + } + + + if ($request->isFormPost()) { + $operation + ->setIsDismissed(1) + ->save(); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + return $this->newDialog() + ->setTitle(pht('Dismiss Operation')) + ->appendParagraph( + pht( + 'Dismiss this operation? It will no longer be shown, but logs '. + 'can be found in Drydock.')) + ->addSubmitButton(pht('Dismiss')) + ->addCancelButton($done_uri); + } + +} diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 48a3e8075f..af2f9f271f 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -235,12 +235,29 @@ final class DrydockLandRepositoryOperation $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; if ($revision->getStatus() != $status_accepted) { - return array( - 'title' => pht('Revision Not Accepted'), - 'body' => pht( - 'This revision is still under review. Only revisions which have '. - 'been accepted may land.'), - ); + switch ($revision->getStatus()) { + case ArcanistDifferentialRevisionStatus::CLOSED: + return array( + 'title' => pht('Revision Closed'), + 'body' => pht( + 'This revision has already been closed. Only open, accepted '. + 'revisions may land.'), + ); + case ArcanistDifferentialRevisionStatus::ABANDONED: + return array( + 'title' => pht('Revision Abandoned'), + 'body' => pht( + 'This revision has been abandoned. Only accepted revisions '. + 'may land.'), + ); + default: + return array( + 'title' => pht('Revision Not Accepted'), + 'body' => pht( + 'This revision is still under review. Only revisions which '. + 'have been accepted may land.'), + ); + } } // Check for other operations. Eventually this should probably be more diff --git a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php index 38e1b4787b..570e68e825 100644 --- a/src/applications/drydock/query/DrydockRepositoryOperationQuery.php +++ b/src/applications/drydock/query/DrydockRepositoryOperationQuery.php @@ -8,6 +8,7 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { private $repositoryPHIDs; private $operationStates; private $operationTypes; + private $isDismissed; public function withIDs(array $ids) { $this->ids = $ids; @@ -39,6 +40,11 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { return $this; } + public function withIsDismissed($dismissed) { + $this->isDismissed = $dismissed; + return $this; + } + public function newResultObject() { return new DrydockRepositoryOperation(); } @@ -152,6 +158,13 @@ final class DrydockRepositoryOperationQuery extends DrydockQuery { $this->operationTypes); } + if ($this->isDismissed !== null) { + $where[] = qsprintf( + $conn, + 'isDismissed = %d', + (int)$this->isDismissed); + } + return $where; } diff --git a/src/applications/drydock/storage/DrydockRepositoryOperation.php b/src/applications/drydock/storage/DrydockRepositoryOperation.php index 83e660ffa9..87565edc86 100644 --- a/src/applications/drydock/storage/DrydockRepositoryOperation.php +++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php @@ -20,6 +20,7 @@ final class DrydockRepositoryOperation extends DrydockDAO protected $operationType; protected $operationState; protected $properties = array(); + protected $isDismissed; private $repository = self::ATTACHABLE; private $object = self::ATTACHABLE; @@ -30,7 +31,8 @@ final class DrydockRepositoryOperation extends DrydockDAO return id(new DrydockRepositoryOperation()) ->setOperationState(self::STATE_WAIT) - ->setOperationType($op->getOperationConstant()); + ->setOperationType($op->getOperationConstant()) + ->setIsDismissed(0); } protected function getConfiguration() { @@ -43,6 +45,7 @@ final class DrydockRepositoryOperation extends DrydockDAO 'repositoryTarget' => 'bytes', 'operationType' => 'text32', 'operationState' => 'text32', + 'isDismissed' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_object' => array( @@ -196,11 +199,17 @@ final class DrydockRepositoryOperation extends DrydockDAO } public function getPolicy($capability) { - return $this->getRepository()->getPolicy($capability); + $need_capability = $this->getRequiredRepositoryCapability($capability); + + return $this->getRepository() + ->getPolicy($need_capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getRepository()->hasAutomaticCapability($capability, $viewer); + $need_capability = $this->getRequiredRepositoryCapability($capability); + + return $this->getRepository() + ->hasAutomaticCapability($need_capability, $viewer); } public function describeAutomaticCapability($capability) { @@ -209,4 +218,17 @@ final class DrydockRepositoryOperation extends DrydockDAO 'affects.'); } + private function getRequiredRepositoryCapability($capability) { + // To edit a RepositoryOperation, require that the user be able to push + // to the repository. + + $map = array( + PhabricatorPolicyCapability::CAN_EDIT => + DiffusionPushCapability::CAPABILITY, + ); + + return idx($map, $capability, $capability); + } + + } diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php index bf1040afe6..07c5a7b706 100644 --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -67,6 +67,7 @@ final class DrydockRepositoryOperationStatusView $item = id(new PHUIObjectItemView()) ->setHref("/drydock/operation/{$id}/") + ->setObjectName(pht('Operation %d', $id)) ->setHeader($operation->getOperationDescription($viewer)) ->setStatusIcon($icon, $name); @@ -98,6 +99,16 @@ final class DrydockRepositoryOperationStatusView } else { $item->addAttribute(pht('Operation encountered an error.')); } + + $is_dismissed = $operation->getIsDismissed(); + + $item->addAction( + id(new PHUIListItemView()) + ->setName('Dismiss') + ->setIcon('fa-times') + ->setDisabled($is_dismissed) + ->setWorkflow(true) + ->setHref("/drydock/operation/{$id}/dismiss/")); } return id(new PHUIObjectItemListView()) From aa2089ba68045617a42d6df6ab5e64974cbe8c33 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 26 Dec 2015 13:00:01 -0800 Subject: [PATCH 03/83] Support field previews in EditEngine Summary: Ref T10004. This primarily supports moving Phame to EditEngine. Test Plan: {F1045166} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10004 Differential Revision: https://secure.phabricator.com/D14887 --- resources/celerity/map.php | 40 +++++++++---------- .../PhabricatorManiphestApplication.php | 7 ---- .../maniphest/editor/ManiphestEditEngine.php | 5 ++- ...onTransactionRemarkupPreviewController.php | 4 +- .../editengine/PhabricatorEditEngine.php | 25 +++++++++++- .../editfield/PhabricatorEditField.php | 22 ++++++++++ .../behavior-phabricator-remarkup-assist.js | 2 +- .../rsrc/js/core/behavior-remarkup-preview.js | 14 ++++++- 8 files changed, 85 insertions(+), 34 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d09165b13a..73daecb1fd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => 'a419cf4b', - 'core.pkg.js' => '400453e4', + 'core.pkg.js' => '57dff7df', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '64e69521', @@ -487,9 +487,9 @@ return array( 'rsrc/js/core/behavior-object-selector.js' => '49b73b36', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', - 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'ecddcbe2', + 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'b60b6d9b', 'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b', - 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', + 'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', @@ -640,7 +640,7 @@ return array( 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-oncopy' => '2926fff2', - 'javelin-behavior-phabricator-remarkup-assist' => 'ecddcbe2', + 'javelin-behavior-phabricator-remarkup-assist' => 'b60b6d9b', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '048330fa', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', @@ -662,7 +662,7 @@ return array( 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', 'javelin-behavior-releeph-request-state-change' => 'a0b57eb8', 'javelin-behavior-releeph-request-typeahead' => 'de2e896f', - 'javelin-behavior-remarkup-preview' => 'f7379f45', + 'javelin-behavior-remarkup-preview' => '4b700e9e', 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-repository-crossreference' => 'e5339c43', @@ -1125,6 +1125,12 @@ return array( 'javelin-request', 'javelin-util', ), + '4b700e9e' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'phabricator-shaped-request', + ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1721,6 +1727,15 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b60b6d9b' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'phabricator-phtize', + 'phabricator-textareautils', + 'javelin-workflow', + 'javelin-vector', + ), 'b65559c0' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1960,15 +1975,6 @@ return array( 'phabricator-phtize', 'javelin-dom', ), - 'ecddcbe2' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'phabricator-phtize', - 'phabricator-textareautils', - 'javelin-workflow', - 'javelin-vector', - ), 'edd1ba66' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2005,12 +2011,6 @@ return array( 'javelin-util', 'javelin-reactor', ), - 'f7379f45' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'phabricator-shaped-request', - ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index c0d0a4d7ef..3fcbbf49f0 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -52,13 +52,6 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication { 'task/' => array( $this->getEditRoutePattern('edit/') => 'ManiphestTaskEditController', - 'descriptionpreview/' - => 'PhabricatorMarkupPreviewController', - ), - 'transaction/' => array( - 'save/' => 'ManiphestTransactionSaveController', - 'preview/(?P[1-9]\d*)/' - => 'ManiphestTransactionPreviewController', ), 'export/(?P[^/]+)/' => 'ManiphestExportController', 'subpriority/' => 'ManiphestSubpriorityController', diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index aac21ea7ac..b7e3cee965 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -156,7 +156,10 @@ final class ManiphestEditEngine ->setConduitDescription(pht('Update the task description.')) ->setConduitTypeDescription(pht('New task description.')) ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) - ->setValue($object->getDescription()), + ->setValue($object->getDescription()) + ->setPreviewPanel( + id(new PHUIRemarkupPreviewPanel()) + ->setHeader(pht('Description Preview'))), ); } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php index 4ba8345d5c..2714be02c4 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionRemarkupPreviewController.php @@ -10,9 +10,9 @@ final class PhabricatorApplicationTransactionRemarkupPreviewController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); - $corpus = $request->getStr('corpus'); + $text = $request->getStr('text'); - $remarkup = new PHUIRemarkupView($viewer, $corpus); + $remarkup = new PHUIRemarkupView($viewer, $text); $content = array( 'content' => hsprintf('%s', $remarkup), diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 2e96e60dd0..041bb05393 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -962,6 +962,28 @@ abstract class PhabricatorEditEngine $header_text = $this->getObjectEditTitleText($object); } + $show_preview = !$request->isAjax(); + + if ($show_preview) { + $previews = array(); + foreach ($fields as $field) { + $preview = $field->getPreviewPanel(); + if (!$preview) { + continue; + } + + $control_id = $field->getControlID(); + + $preview + ->setControlID($control_id) + ->setPreviewURI('/transactions/remarkuppreview/'); + + $previews[] = $preview; + } + } else { + $previews = array(); + } + $form = $this->buildEditForm($object, $fields); if ($request->isAjax()) { @@ -998,7 +1020,8 @@ abstract class PhabricatorEditEngine return $controller->newPage() ->setTitle($header_text) ->setCrumbs($crumbs) - ->appendChild($box); + ->appendChild($box) + ->appendChild($previews); } protected function newEditResponse( diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 9d1b976299..1216370819 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -14,6 +14,8 @@ abstract class PhabricatorEditField extends Phobject { private $metadata = array(); private $editTypeKey; private $isRequired; + private $previewPanel; + private $controlID; private $description; private $conduitDescription; @@ -251,6 +253,15 @@ abstract class PhabricatorEditField extends Phobject { return $this->commentActionValue; } + public function setPreviewPanel(PHUIRemarkupPreviewPanel $preview_panel) { + $this->previewPanel = $preview_panel; + return $this; + } + + public function getPreviewPanel() { + return $this->previewPanel; + } + protected function newControl() { throw new PhutilMethodNotImplementedException(); } @@ -285,6 +296,13 @@ abstract class PhabricatorEditField extends Phobject { return $control; } + public function getControlID() { + if (!$this->controlID) { + $this->controlID = celerity_generate_unique_node_id(); + } + return $this->controlID; + } + protected function renderControl() { $control = $this->buildControl(); if ($control === null) { @@ -308,6 +326,10 @@ abstract class PhabricatorEditField extends Phobject { $control->setDisabled($disabled); + if ($this->controlID) { + $control->setID($this->controlID); + } + return $control; } diff --git a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js index 293c44ed9b..9dabffeb10 100644 --- a/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js +++ b/webroot/rsrc/js/core/behavior-phabricator-remarkup-assist.js @@ -254,7 +254,7 @@ JX.behavior('phabricator-remarkup-assist', function(config) { var value = area.value; var data = { - corpus: value + text: value }; var onupdate = function(r) { diff --git a/webroot/rsrc/js/core/behavior-remarkup-preview.js b/webroot/rsrc/js/core/behavior-remarkup-preview.js index f703e56266..4e79c48b59 100644 --- a/webroot/rsrc/js/core/behavior-remarkup-preview.js +++ b/webroot/rsrc/js/core/behavior-remarkup-preview.js @@ -8,16 +8,26 @@ JX.behavior('remarkup-preview', function(config) { + // Don't bother with any of this on mobile. + if (JX.Device.getDevice() !== 'desktop') { + return; + } + var preview = JX.$(config.previewID); var control = JX.$(config.controlID); var callback = function(r) { - JX.DOM.setContent(preview, JX.$H(r)); + // This currently accepts responses from two controllers: + // Old: PhabricatorMarkupPreviewController + // New: PhabricatorApplicationTransactionRemarkupPreviewController + // TODO: Swap everything to just the new controller. + + JX.DOM.setContent(preview, JX.$H(r.content || r)); }; var getdata = function() { return { - text : control.value + text: control.value }; }; From 211a6c0d55f36b0e21657f9f317e80b6511e5ab3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 02:28:36 -0800 Subject: [PATCH 04/83] Move project slug normalization inside project Query Summary: Ref T10010. We currently require `withSlugs()` to have properly formatted slugs, but this leads to similar code in several places. Instead: accept any slug, normalize slugs in the query, return a map so callers can figure out what happened if they want. This tends to do the right thing by default, while keeping enough information around to do more complex things if necessary. A similar approach for querying commits has worked well in Diffusion. Test Plan: Added and executed unit tests. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14888 --- .../PhabricatorProjectCoreTestCase.php | 84 ++++++++++++++ .../project/query/PhabricatorProjectQuery.php | 106 ++++++++++++++++-- 2 files changed, 178 insertions(+), 12 deletions(-) diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 5b8e797bc2..76f265735c 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -493,6 +493,90 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $this->assertFalse((bool)$this->refreshProject($child, $user2)); } + public function testSlugMaps() { + // When querying by slugs, slugs should be normalized and the mapping + // should be reported correctly. + $user = $this->createUser(); + $user->save(); + + $name = 'queryslugproject'; + $name2 = 'QUERYslugPROJECT'; + $slug = 'queryslugextra'; + $slug2 = 'QuErYSlUgExTrA'; + + $project = PhabricatorProject::initializeNewProject($user); + + $xactions = array(); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setNewValue($name); + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setNewValue(array($slug)); + + $this->applyTransactions($project, $user, $xactions); + + $project_query = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withSlugs(array($name)); + $project_query->execute(); + $map = $project_query->getSlugMap(); + + $this->assertEqual( + array( + $name => $project->getPHID(), + ), + ipull($map, 'projectPHID')); + + $project_query = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withSlugs(array($slug)); + $project_query->execute(); + $map = $project_query->getSlugMap(); + + $this->assertEqual( + array( + $slug => $project->getPHID(), + ), + ipull($map, 'projectPHID')); + + $project_query = id(new PhabricatorProjectQuery()) + ->setViewer($user) + ->withSlugs(array($name, $slug, $name2, $slug2)); + $project_query->execute(); + $map = $project_query->getSlugMap(); + + $expect = array( + $name => $project->getPHID(), + $slug => $project->getPHID(), + $name2 => $project->getPHID(), + $slug2 => $project->getPHID(), + ); + + $actual = ipull($map, 'projectPHID'); + + ksort($expect); + ksort($actual); + + $this->assertEqual($expect, $actual); + + $expect = array( + $name => $name, + $slug => $slug, + $name2 => $name, + $slug2 => $slug, + ); + + $actual = ipull($map, 'slug'); + + ksort($expect); + ksort($actual); + + $this->assertEqual($expect, $actual); + } + private function attemptProjectEdit( PhabricatorProject $proj, PhabricatorUser $user, diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index c975150fbc..02d4474777 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -7,6 +7,8 @@ final class PhabricatorProjectQuery private $phids; private $memberPHIDs; private $slugs; + private $slugNormals; + private $slugMap; private $names; private $nameTokens; private $icons; @@ -157,6 +159,24 @@ final class PhabricatorProjectQuery ); } + public function getSlugMap() { + if ($this->slugMap === null) { + throw new PhutilInvalidStateException('execute'); + } + return $this->slugMap; + } + + protected function willExecute() { + $this->slugMap = array(); + $this->slugNormals = array(); + if ($this->slugs) { + foreach ($this->slugs as $slug) { + $normal = PhabricatorSlug::normalizeProjectSlug($slug); + $this->slugNormals[$slug] = $normal; + } + } + } + protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } @@ -287,17 +307,7 @@ final class PhabricatorProjectQuery } } - if ($this->needSlugs) { - $slugs = id(new PhabricatorProjectSlug()) - ->loadAllWhere( - 'projectPHID IN (%Ls)', - mpull($projects, 'getPHID')); - $slugs = mgroup($slugs, 'getProjectPHID'); - foreach ($projects as $project) { - $project_slugs = idx($slugs, $project->getPHID(), array()); - $project->attachSlugs($project_slugs); - } - } + $this->loadSlugs($projects); return $projects; } @@ -356,7 +366,7 @@ final class PhabricatorProjectQuery $where[] = qsprintf( $conn, 'slug.slug IN (%Ls)', - $this->slugs); + $this->slugNormals); } if ($this->names !== null) { @@ -583,4 +593,76 @@ final class PhabricatorProjectQuery return $ancestors; } + private function loadSlugs(array $projects) { + // Build a map from primary slugs to projects. + $primary_map = array(); + foreach ($projects as $project) { + $primary_slug = $project->getPrimarySlug(); + if ($primary_slug === null) { + continue; + } + + $primary_map[$primary_slug] = $project; + } + + // Link up all of the queried slugs which correspond to primary + // slugs. If we can link up everything from this (no slugs were queried, + // or only primary slugs were queried) we don't need to load anything + // else. + $unknown = $this->slugNormals; + foreach ($unknown as $input => $normal) { + if (!isset($primary_map[$normal])) { + continue; + } + + $this->slugMap[$input] = array( + 'slug' => $normal, + 'projectPHID' => $primary_map[$normal]->getPHID(), + ); + + unset($unknown[$input]); + } + + // If we need slugs, we have to load everything. + // If we still have some queried slugs which we haven't mapped, we only + // need to look for them. + // If we've mapped everything, we don't have to do any work. + $project_phids = mpull($projects, 'getPHID'); + if ($this->needSlugs) { + $slugs = id(new PhabricatorProjectSlug())->loadAllWhere( + 'projectPHID IN (%Ls)', + $project_phids); + } else if ($unknown) { + $slugs = id(new PhabricatorProjectSlug())->loadAllWhere( + 'projectPHID IN (%Ls) AND slug IN (%Ls)', + $project_phids, + $unknown); + } else { + $slugs = array(); + } + + // Link up any slugs we were not able to link up earlier. + $extra_map = mpull($slugs, 'getProjectPHID', 'getSlug'); + foreach ($unknown as $input => $normal) { + if (!isset($extra_map[$normal])) { + continue; + } + + $this->slugMap[$input] = array( + 'slug' => $normal, + 'projectPHID' => $extra_map[$normal], + ); + + unset($unknown[$input]); + } + + if ($this->needSlugs) { + $slug_groups = mgroup($slugs, 'getProjectPHID'); + foreach ($projects as $project) { + $project_slugs = idx($slug_groups, $project->getPHID(), array()); + $project->attachSlugs($project_slugs); + } + } + } + } From d1f1d3ec33bbae010d2e5b6ecda95bde4daac976 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 02:44:07 -0800 Subject: [PATCH 05/83] Implement a basic project.search third-generation API method Summary: Ref T10010. This still needs support for attachments (to get members) and more constraints (like slugs), but mostly works. Test Plan: Ran query, saw basically sensible results. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14889 --- src/__phutil_library_map__.php | 3 ++ .../conduit/ProjectSearchConduitAPIMethod.php | 24 ++++++++++++++ .../project/storage/PhabricatorProject.php | 31 ++++++++++++++++++- .../PhabricatorApplicationSearchEngine.php | 11 ++++++- .../PhabricatorSearchEngineAPIMethod.php | 19 +++++++++++- 5 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/applications/project/conduit/ProjectSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a024d493e8..86c7b3156a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3747,6 +3747,7 @@ phutil_register_library_map(array( 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', + 'ProjectSearchConduitAPIMethod' => 'applications/project/conduit/ProjectSearchConduitAPIMethod.php', 'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php', 'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php', 'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php', @@ -7146,6 +7147,7 @@ phutil_register_library_map(array( 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorConduitResultInterface', ), 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectApplication' => 'PhabricatorApplication', @@ -8296,6 +8298,7 @@ phutil_register_library_map(array( 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'QueryFormattingTestCase' => 'PhabricatorTestCase', 'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification', 'ReleephBranch' => array( diff --git a/src/applications/project/conduit/ProjectSearchConduitAPIMethod.php b/src/applications/project/conduit/ProjectSearchConduitAPIMethod.php new file mode 100644 index 0000000000..c6b27d5288 --- /dev/null +++ b/src/applications/project/conduit/ProjectSearchConduitAPIMethod.php @@ -0,0 +1,24 @@ + $query->getSlugMap(), + ); + } + +} diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 223333809f..afb4a32e61 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -9,7 +9,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO PhabricatorSubscribableInterface, PhabricatorCustomFieldInterface, PhabricatorDestructibleInterface, - PhabricatorFulltextInterface { + PhabricatorFulltextInterface, + PhabricatorConduitResultInterface { protected $name; protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; @@ -525,4 +526,32 @@ final class PhabricatorProject extends PhabricatorProjectDAO return new PhabricatorProjectFulltextEngine(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('name') + ->setType('string') + ->setDescription(pht('The name of the project.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('slug') + ->setType('string') + ->setDescription(pht('Primary slug/hashtag.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'slug' => $this->getPrimarySlug(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index df79a4e0a5..702af44c40 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1081,7 +1081,9 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return $fields; } - public function buildConduitResponse(ConduitAPIRequest $request) { + public function buildConduitResponse( + ConduitAPIRequest $request, + ConduitAPIMethod $method) { $viewer = $this->requireViewer(); $query_key = $request->getValue('queryKey'); @@ -1172,6 +1174,12 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { $attachment_specs[$key]); } + // If this is empty, we still want to emit a JSON object, not a + // JSON list. + if (!$attachment_map) { + $attachment_map = (object)$attachment_map; + } + $id = (int)$object->getID(); $phid = $object->getPHID(); @@ -1187,6 +1195,7 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { return array( 'data' => $data, + 'maps' => $method->getQueryMaps($query), 'query' => array( 'queryKey' => $saved_query->getQueryKey(), ), diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index a299d90018..998a77126b 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -5,6 +5,23 @@ abstract class PhabricatorSearchEngineAPIMethod abstract public function newSearchEngine(); + final public function getQueryMaps($query) { + $maps = $this->getCustomQueryMaps($query); + + // Make sure we emit empty maps as objects, not lists. + foreach ($maps as $key => $map) { + if (!$map) { + $maps[$key] = (object)$map; + } + } + + return $maps; + } + + protected function getCustomQueryMaps($query) { + return array(); + } + public function getApplication() { $engine = $this->newSearchEngine(); $class = $engine->getApplicationClassName(); @@ -36,7 +53,7 @@ abstract class PhabricatorSearchEngineAPIMethod $engine = $this->newSearchEngine() ->setViewer($request->getUser()); - return $engine->buildConduitResponse($request); + return $engine->buildConduitResponse($request, $this); } final public function getMethodDescription() { From 5e715c1acac0811a7efd8036eae4ada45366a5ba Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 02:04:37 -0800 Subject: [PATCH 06/83] Simplify some logic in project controllers Summary: Ref T10010. Several controlers currently have similar logic for handling tags and slugs, loading projects, and canonicalizing URIs. Clean it up a bit. Test Plan: - Visited profile, boards, feed. - Visited by ID and by tag. - Visited by non-normal tag (redircted). - Visited by alternate tag (redirected). - Visited non-policy project by non-normal tag (redirected into policy error). Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14890 --- .../PhabricatorProjectController.php | 66 +++++++++++++++++++ .../PhabricatorProjectFeedController.php | 61 +++++------------ ...habricatorProjectMembersEditController.php | 4 -- .../PhabricatorProjectProfileController.php | 43 ++++-------- .../PhabricatorProjectViewController.php | 55 ++-------------- 5 files changed, 99 insertions(+), 130 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index ac406412ad..2fcb276576 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -13,6 +13,72 @@ abstract class PhabricatorProjectController extends PhabricatorController { return $this->project; } + protected function loadProject() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $id = $request->getURIData('id'); + $slug = $request->getURIData('slug'); + + if ($slug) { + $normal_slug = PhabricatorSlug::normalizeProjectSlug($slug); + $is_abnormal = ($slug !== $normal_slug); + $normal_uri = "/tag/{$normal_slug}/"; + } else { + $is_abnormal = false; + } + + $query = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->needMembers(true) + ->needWatchers(true) + ->needImages(true) + ->needSlugs(true); + + if ($slug) { + $query->withSlugs(array($slug)); + } else { + $query->withIDs(array($id)); + } + + $policy_exception = null; + try { + $project = $query->executeOne(); + } catch (PhabricatorPolicyException $ex) { + $policy_exception = $ex; + $project = null; + } + + if (!$project) { + // This project legitimately does not exist, so just 404 the user. + if (!$policy_exception) { + return new Aphront404Response(); + } + + // Here, the project exists but the user can't see it. If they are + // using a non-canonical slug to view the project, redirect to the + // canonical slug. If they're already using the canonical slug, rethrow + // the exception to give them the policy error. + if ($is_abnormal) { + return id(new AphrontRedirectResponse())->setURI($normal_uri); + } else { + throw $policy_exception; + } + } + + // The user can view the project, but is using a noncanonical slug. + // Redirect to the canonical slug. + $primary_slug = $project->getPrimarySlug(); + if ($slug && ($slug !== $primary_slug)) { + $primary_uri = "/tag/{$primary_slug}/"; + return id(new AphrontRedirectResponse())->setURI($primary_uri); + } + + $this->setProject($project); + + return null; + } + public function buildApplicationMenu() { return $this->buildSideNavView(true)->getMenu(); } diff --git a/src/applications/project/controller/PhabricatorProjectFeedController.php b/src/applications/project/controller/PhabricatorProjectFeedController.php index efd82d31d0..5cd74094fc 100644 --- a/src/applications/project/controller/PhabricatorProjectFeedController.php +++ b/src/applications/project/controller/PhabricatorProjectFeedController.php @@ -8,38 +8,25 @@ final class PhabricatorProjectFeedController } public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); + $viewer = $request->getUser(); - $query = id(new PhabricatorProjectQuery()) - ->setViewer($user) - ->needMembers(true) - ->needWatchers(true) - ->needImages(true) - ->needSlugs(true); - $id = $request->getURIData('id'); - $slug = $request->getURIData('slug'); - if ($slug) { - $query->withSlugs(array($slug)); - } else { - $query->withIDs(array($id)); - } - $project = $query->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - if ($slug && $slug != $project->getPrimarySlug()) { - return id(new AphrontRedirectResponse()) - ->setURI('/tag/'.$project->getPrimarySlug().'/'); + $response = $this->loadProject(); + if ($response) { + return $response; } - $query = new PhabricatorFeedQuery(); - $query->setFilterPHIDs( - array( - $project->getPHID(), - )); - $query->setLimit(50); - $query->setViewer($request->getUser()); - $stories = $query->execute(); + $project = $this->getProject(); + $id = $project->getID(); + + $stories = id(new PhabricatorFeedQuery()) + ->setViewer($viewer) + ->setFilterPHIDs( + array( + $project->getPHID(), + )) + ->setLimit(50) + ->execute(); + $feed = $this->renderStories($stories); $box = id(new PHUIObjectBoxView()) @@ -57,21 +44,6 @@ final class PhabricatorProjectFeedController )); } - private function renderFeedPage(PhabricatorProject $project) { - - $query = new PhabricatorFeedQuery(); - $query->setFilterPHIDs(array($project->getPHID())); - $query->setViewer($this->getRequest()->getUser()); - $query->setLimit(100); - $stories = $query->execute(); - - if (!$stories) { - return pht('There are no stories about this project.'); - } - - return $this->renderStories($stories); - } - private function renderStories(array $stories) { assert_instances_of($stories, 'PhabricatorFeedStory'); @@ -85,5 +57,4 @@ final class PhabricatorProjectFeedController $view->render()); } - } diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php index c7a9144188..f007650e4a 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php @@ -12,10 +12,6 @@ final class PhabricatorProjectMembersEditController ->withIDs(array($id)) ->needMembers(true) ->needImages(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - )) ->executeOne(); if (!$project) { return new Aphront404Response(); diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 89880bdd1a..cc246b5a2d 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -8,35 +8,20 @@ final class PhabricatorProjectProfileController } public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); + $viewer = $request->getUser(); - $query = id(new PhabricatorProjectQuery()) - ->setViewer($user) - ->needMembers(true) - ->needWatchers(true) - ->needImages(true) - ->needSlugs(true); - $id = $request->getURIData('id'); - $slug = $request->getURIData('slug'); - if ($slug) { - $query->withSlugs(array($slug)); - } else { - $query->withIDs(array($id)); - } - $project = $query->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - if ($slug && $slug != $project->getPrimarySlug()) { - return id(new AphrontRedirectResponse()) - ->setURI('/tag/'.$project->getPrimarySlug().'/'); + $response = $this->loadProject(); + if ($response) { + return $response; } + $project = $this->getProject(); + $id = $project->getID(); $picture = $project->getProfileImageURI(); $header = id(new PHUIHeaderView()) ->setHeader($project->getName()) - ->setUser($user) + ->setUser($viewer) ->setPolicyObject($project) ->setImage($picture); @@ -60,15 +45,13 @@ final class PhabricatorProjectProfileController $nav = $this->buildIconNavView($project); $nav->selectFilter("profile/{$id}/"); - $nav->appendChild($object_box); - $nav->appendChild($timeline); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $project->getName(), - 'pageObjects' => array($project->getPHID()), - )); + return $this->newPage() + ->setNavigation($nav) + ->setTitle($project->getName()) + ->setPageObjectPHIDs(array($project->getPHID())) + ->appendChild($object_box) + ->appendChild($timeline); } private function buildActionListView(PhabricatorProject $project) { diff --git a/src/applications/project/controller/PhabricatorProjectViewController.php b/src/applications/project/controller/PhabricatorProjectViewController.php index bfc9827ab5..087971878b 100644 --- a/src/applications/project/controller/PhabricatorProjectViewController.php +++ b/src/applications/project/controller/PhabricatorProjectViewController.php @@ -11,31 +11,11 @@ final class PhabricatorProjectViewController $request = $this->getRequest(); $viewer = $request->getViewer(); - $query = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->needMembers(true) - ->needWatchers(true) - ->needImages(true) - ->needSlugs(true); - $id = $request->getURIData('id'); - $slug = $request->getURIData('slug'); - if ($slug) { - $query->withSlugs(array($slug)); - } else { - $query->withIDs(array($id)); - } - $project = $query->executeOne(); - if (!$project) { - - // If this request corresponds to a project but just doesn't have the - // slug quite right, redirect to the proper URI. - $uri = $this->getNormalizedURI($slug); - if ($uri !== null) { - return id(new AphrontRedirectResponse())->setURI($uri); - } - - return new Aphront404Response(); + $response = $this->loadProject(); + if ($response) { + return $response; } + $project = $this->getProject(); $columns = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) @@ -60,31 +40,4 @@ final class PhabricatorProjectViewController return $this->delegateToController($controller_object); } - private function getNormalizedURI($slug) { - if (!strlen($slug)) { - return null; - } - - $normal = PhabricatorSlug::normalizeProjectSlug($slug); - if ($normal === $slug) { - return null; - } - - $viewer = $this->getViewer(); - - // Do execute() instead of executeOne() here so we canonicalize before - // raising a policy exception. This is a little more polished than letting - // the user hit the error on any variant of the slug. - - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withSlugs(array($normal)) - ->execute(); - if (!$projects) { - return null; - } - - return "/tag/{$normal}/"; - } - } From 2b5d4bca8a8f97c755d3be25d34ee5d77fdfc605 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 04:10:59 -0800 Subject: [PATCH 07/83] Put some crumbs on some project pages Summary: Ref T10010. This is primarily to make "Parent > Child > Grandchild" navigation more manageable for subprojects, at least for now. Test Plan: Viewed profile, members, feed; saw crumbs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14891 --- .../PhabricatorProjectController.php | 18 ++++++++++++++++++ .../PhabricatorProjectFeedController.php | 13 ++++++++----- ...PhabricatorProjectMembersEditController.php | 16 +++++++++------- .../PhabricatorProjectProfileController.php | 3 +++ .../project/storage/PhabricatorProject.php | 5 +++++ 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index 2fcb276576..2f90c7d35b 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -154,4 +154,22 @@ abstract class PhabricatorProjectController extends PhabricatorController { return $nav; } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $project = $this->getProject(); + if ($project) { + $ancestors = $project->getAncestorProjects(); + $ancestors = array_reverse($ancestors); + $ancestors[] = $project; + foreach ($ancestors as $ancestor) { + $crumbs->addTextCrumb( + $project->getName(), + $project->getURI()); + } + } + + return $crumbs; + } + } diff --git a/src/applications/project/controller/PhabricatorProjectFeedController.php b/src/applications/project/controller/PhabricatorProjectFeedController.php index 5cd74094fc..571148d2ce 100644 --- a/src/applications/project/controller/PhabricatorProjectFeedController.php +++ b/src/applications/project/controller/PhabricatorProjectFeedController.php @@ -37,11 +37,14 @@ final class PhabricatorProjectFeedController $nav->selectFilter("feed/{$id}/"); $nav->appendChild($box); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $project->getName(), - )); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Feed')); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), pht('Feed'))) + ->appendChild($box); } private function renderStories(array $stories) { diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php index f007650e4a..b018e9389d 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php @@ -95,14 +95,16 @@ final class PhabricatorProjectMembersEditController $nav = $this->buildIconNavView($project); $nav->selectFilter("members/{$id}/"); - $nav->appendChild($form_box); - $nav->appendChild($member_list); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Members')); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), $title)) + ->appendChild($form_box) + ->appendChild($member_list); } private function renderMemberList( diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index cc246b5a2d..fa443ebf6e 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -46,8 +46,11 @@ final class PhabricatorProjectProfileController $nav = $this->buildIconNavView($project); $nav->selectFilter("profile/{$id}/"); + $crumbs = $this->buildApplicationCrumbs(); + return $this->newPage() ->setNavigation($nav) + ->setCrumbs($crumbs) ->setTitle($project->getName()) ->setPageObjectPHIDs(array($project->getPHID())) ->appendChild($object_box) diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index afb4a32e61..6a4ed4afba 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -308,6 +308,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $this->color; } + public function getURI() { + $id = $this->getID(); + return "/project/view/{$id}/"; + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); From 77897ce862580f1b06d067db8425d78a50a7dca6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 04:15:39 -0800 Subject: [PATCH 08/83] Clean up ProjectQuery when viewer is logged-out or omnipotent Summary: Ref T10010. When the viewer is logged-out or omnipotent, we can skip this query. (Currently we issue a silly query like `src = X AND type = Y AND dst = ''`, which will never return results.) Test Plan: - Viewed projects as normal user and logged-out user. - Ran unit tests. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14892 --- .../project/query/PhabricatorProjectQuery.php | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 02d4474777..11a789ab31 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -226,11 +226,21 @@ final class PhabricatorProjectQuery // If we only need to know if the viewer is a member, we can restrict // the query to just their PHID. + $any_edges = true; if (!$this->needMembers && !$this->needWatchers) { - $edge_query->withDestinationPHIDs(array($viewer_phid)); + if ($viewer_phid) { + $edge_query->withDestinationPHIDs(array($viewer_phid)); + } else { + // If we don't need members or watchers and don't have a viewer PHID + // (viewer is logged-out or omnipotent), they'll never be a member + // so we don't need to issue this query at all. + $any_edges = false; + } } - $edge_query->execute(); + if ($any_edges) { + $edge_query->execute(); + } $membership_projects = array(); foreach ($projects as $project) { @@ -242,9 +252,13 @@ final class PhabricatorProjectQuery $source_phids = array($project_phid); } - $member_phids = $edge_query->getDestinationPHIDs( - $source_phids, - array($member_type)); + if ($any_edges) { + $member_phids = $edge_query->getDestinationPHIDs( + $source_phids, + array($member_type)); + } else { + $member_phids = array(); + } if (in_array($viewer_phid, $member_phids)) { $membership_projects[$project_phid] = $project; From 373ff7f9d465f35c32b278e6c8dc7d9f015f2d87 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 04:27:58 -0800 Subject: [PATCH 09/83] Read materialized project members instead of real members Summary: Ref T10010. This will allow us to find superprojects with `withMemberPHIDs(...)` queries. - Copy all the current real member edges to materialized member edges. - Redirect all reads to look at materialized members. - This table is already kept in sync by earlier work with indexing. Basically, flow is: - Writes (joining, leaving, adding/removing members) write to the real member edge type. - After a project's members change, they're copied to the materialized member edge type for that project and all of its superprojects. - Reads look at materialized members, so "Parent" sees the members of "Child" and "Grandchild" as its own members, but we still have the "real members" edge type to keep track of "natural" or "direct" members. Test Plan: - Ran migration. - Ran unit tests. - Saw the same projects as projects I was a member of. - Added some `var_dump()` stuff to verify the Owners changed. - Used `grep` to look for other readers of this edge type. - Made some project updates. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14893 --- .../20151227.proj.01.materialize.sql | 6 +++++ .../owners/storage/PhabricatorOwnersOwner.php | 26 +++++++++---------- .../PhabricatorProjectTransactionEditor.php | 22 +++++++++------- .../project/query/PhabricatorProjectQuery.php | 8 +++--- 4 files changed, 36 insertions(+), 26 deletions(-) create mode 100644 resources/sql/autopatches/20151227.proj.01.materialize.sql diff --git a/resources/sql/autopatches/20151227.proj.01.materialize.sql b/resources/sql/autopatches/20151227.proj.01.materialize.sql new file mode 100644 index 0000000000..ceac969e8f --- /dev/null +++ b/resources/sql/autopatches/20151227.proj.01.materialize.sql @@ -0,0 +1,6 @@ +/* PhabricatorProjectProjectHasMemberEdgeType::EDGECONST = 13 */ +/* PhabricatorProjectMaterializedMemberEdgeType::EDGECONST = 60 */ + +INSERT IGNORE INTO {$NAMESPACE}_project.edge (src, type, dst, dateCreated) + SELECT src, 60, dst, dateCreated FROM {$NAMESPACE}_project.edge + WHERE type = 13; diff --git a/src/applications/owners/storage/PhabricatorOwnersOwner.php b/src/applications/owners/storage/PhabricatorOwnersOwner.php index c3d1aaa605..07d5b59a20 100644 --- a/src/applications/owners/storage/PhabricatorOwnersOwner.php +++ b/src/applications/owners/storage/PhabricatorOwnersOwner.php @@ -40,6 +40,7 @@ final class PhabricatorOwnersOwner extends PhabricatorOwnersDAO { if (!$package_ids) { return array(); } + $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( 'packageID IN (%Ls)', $package_ids); @@ -50,20 +51,19 @@ final class PhabricatorOwnersOwner extends PhabricatorOwnersDAO { PhabricatorPeopleUserPHIDType::TYPECONST, array()); - $users_in_project_phids = array(); - $project_phids = idx( - $all_phids, - PhabricatorProjectProjectPHIDType::TYPECONST); - if ($project_phids) { - $query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($project_phids) - ->withEdgeTypes(array( - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST, - )); - $query->execute(); - $users_in_project_phids = $query->getDestinationPHIDs(); + if ($user_phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withMemberPHIDs($user_phids) + ->withIsMilestone(false) + ->execute(); + $project_phids = mpull($projects, 'getPHID'); + } else { + $project_phids = array(); } - return array_unique(array_merge($users_in_project_phids, $user_phids)); + $all_phids = array_fuse($user_phids) + array_fuse($project_phids); + + return array_values($all_phids); } } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 7626729b8c..72ddc1470c 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -509,12 +509,13 @@ final class PhabricatorProjectTransactionEditor } protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { - $member_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getPHID(), - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); - $object->attachMemberPHIDs($member_phids); - - return $object; + // NOTE: We're using the omnipotent user here because the original actor + // may no longer have permission to view the object. + return id(new PhabricatorProjectQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs(array($object->getPHID())) + ->needMembers(true) + ->executeOne(); } protected function shouldSendMail( @@ -719,9 +720,12 @@ final class PhabricatorProjectTransactionEditor $object_phid = $object->getPHID(); if ($object_phid) { - $members = PhabricatorEdgeQuery::loadDestinationPHIDs( - $object_phid, - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); + $project = id(new PhabricatorProjectQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($object_phid)) + ->needMembers(true) + ->executeOne(); + $members = $project->getMemberPHIDs(); } else { $members = array(); } diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 11a789ab31..bc14e93a88 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -201,11 +201,11 @@ final class PhabricatorProjectQuery $viewer_phid = $this->getViewer()->getPHID(); - $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + $material_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST; $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; $types = array(); - $types[] = $member_type; + $types[] = $material_type; if ($this->needWatchers) { $types[] = $watcher_type; } @@ -255,7 +255,7 @@ final class PhabricatorProjectQuery if ($any_edges) { $member_phids = $edge_query->getDestinationPHIDs( $source_phids, - array($member_type)); + array($material_type)); } else { $member_phids = array(); } @@ -488,7 +488,7 @@ final class PhabricatorProjectQuery $conn, 'JOIN %T e ON e.src = p.phid AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST); + PhabricatorProjectMaterializedMemberEdgeType::EDGECONST); } if ($this->slugs !== null) { From 11e53f294827f783961c3fa9987a33a11645286f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 04:47:08 -0800 Subject: [PATCH 10/83] Add empty subproject/milestone controllers Summary: Ref T10010. These do nothing yet. Test Plan: Clicked 'em. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14894 --- src/__phutil_library_map__.php | 4 +++ .../PhabricatorProjectApplication.php | 4 +++ .../PhabricatorProjectController.php | 6 ++++ .../PhabricatorProjectFeedController.php | 1 - ...PhabricatorProjectMilestonesController.php | 33 +++++++++++++++++++ ...habricatorProjectSubprojectsController.php | 33 +++++++++++++++++++ 6 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/applications/project/controller/PhabricatorProjectMilestonesController.php create mode 100644 src/applications/project/controller/PhabricatorProjectSubprojectsController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 86c7b3156a..333d618dae 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2864,6 +2864,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', 'PhabricatorProjectMembersPolicyRule' => 'applications/project/policyrule/PhabricatorProjectMembersPolicyRule.php', 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', + 'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', @@ -2883,6 +2884,7 @@ phutil_register_library_map(array( 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', + 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', 'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php', 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', @@ -7207,6 +7209,7 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', + 'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', @@ -7229,6 +7232,7 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectStatus' => 'Phobject', + 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index 4a4778cde5..b1bfd16f6e 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -60,6 +60,10 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { 'picture/(?P[1-9]\d*)/' => 'PhabricatorProjectEditPictureController', 'create/' => 'PhabricatorProjectEditDetailsController', + 'subprojects/(?P[1-9]\d*)/' + => 'PhabricatorProjectSubprojectsController', + 'milestones/(?P[1-9]\d*)/' + => 'PhabricatorProjectMilestonesController', 'board/(?P[1-9]\d*)/'. '(?Pfilter/)?'. '(?:query/(?P[^/]+)/)?' diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index 2f90c7d35b..c1aef3125e 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -151,6 +151,12 @@ abstract class PhabricatorProjectController extends PhabricatorController { $nav->addIcon("members/{$id}/", pht('Members'), 'fa-group'); $nav->addIcon("details/{$id}/", pht('Edit Details'), 'fa-pencil'); + if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + $nav->addIcon("subprojects/{$id}/", pht('Subprojects'), 'fa-sitemap'); + $nav->addIcon("milestones/{$id}/", pht('Milestones'), 'fa-map-marker'); + } + + return $nav; } diff --git a/src/applications/project/controller/PhabricatorProjectFeedController.php b/src/applications/project/controller/PhabricatorProjectFeedController.php index 571148d2ce..44c1c12926 100644 --- a/src/applications/project/controller/PhabricatorProjectFeedController.php +++ b/src/applications/project/controller/PhabricatorProjectFeedController.php @@ -35,7 +35,6 @@ final class PhabricatorProjectFeedController $nav = $this->buildIconNavView($project); $nav->selectFilter("feed/{$id}/"); - $nav->appendChild($box); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Feed')); diff --git a/src/applications/project/controller/PhabricatorProjectMilestonesController.php b/src/applications/project/controller/PhabricatorProjectMilestonesController.php new file mode 100644 index 0000000000..e7ce6a7640 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectMilestonesController.php @@ -0,0 +1,33 @@ +getViewer(); + + $response = $this->loadProject(); + if ($response) { + return $response; + } + + $project = $this->getProject(); + $id = $project->getID(); + + $nav = $this->buildIconNavView($project); + $nav->selectFilter("milestones/{$id}/"); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Milestones')); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), pht('Milestones'))); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php new file mode 100644 index 0000000000..e4014aaf49 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -0,0 +1,33 @@ +getViewer(); + + $response = $this->loadProject(); + if ($response) { + return $response; + } + + $project = $this->getProject(); + $id = $project->getID(); + + $nav = $this->buildIconNavView($project); + $nav->selectFilter("subprojects/{$id}/"); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Subprojects')); + + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle(array($project->getName(), pht('Subprojects'))); + } + +} From e8ddfad6dbd2ed9090827bd35ad4f84d366b4241 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 06:56:15 -0800 Subject: [PATCH 11/83] Move "Lock Project" to a separate action Summary: Ref T10010. Three motivations: - Primarily: this makes conversion to EditEngine easier since I don't have to convert this weird control. - This probably needs to have "Lock", "Unlock" and "Use Parent Project Setting" values after subprojects? But maybe just locking any parent locks all the children? Anyway, doesn't make sense to put it on the main edit form if it's weird like this, I think, since we'll want some kind of explanatory text. - I probably want to move this to the "Members" tab anyway, and this won't be available on milestone projects at all. Test Plan: Locked, unlocked, edited projects. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14895 --- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectApplication.php | 2 + ...habricatorProjectEditDetailsController.php | 24 +----- .../PhabricatorProjectLockController.php | 76 +++++++++++++++++++ .../PhabricatorProjectProfileController.php | 19 +++++ 5 files changed, 100 insertions(+), 23 deletions(-) create mode 100644 src/applications/project/controller/PhabricatorProjectLockController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 333d618dae..085d006bec 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2853,6 +2853,7 @@ phutil_register_library_map(array( 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', + 'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php', 'PhabricatorProjectLogicalAndDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php', 'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php', 'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php', @@ -7198,6 +7199,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', 'PhabricatorProjectListController' => 'PhabricatorProjectController', + 'PhabricatorProjectLockController' => 'PhabricatorProjectController', 'PhabricatorProjectLogicalAndDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index b1bfd16f6e..6936faa7fc 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -47,6 +47,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { => 'PhabricatorProjectEditDetailsController', 'archive/(?P[1-9]\d*)/' => 'PhabricatorProjectArchiveController', + 'lock/(?P[1-9]\d*)/' + => 'PhabricatorProjectLockController', 'members/(?P[1-9]\d*)/' => 'PhabricatorProjectMembersEditController', 'members/(?P[1-9]\d*)/remove/' diff --git a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php index d3a3fb6374..46287f3801 100644 --- a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php +++ b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php @@ -54,7 +54,6 @@ final class PhabricatorProjectEditDetailsController $v_slugs = $project_slugs; $v_color = $project->getColor(); $v_icon = $project->getIcon(); - $v_locked = $project->getIsMembershipLocked(); $validation_exception = null; @@ -69,14 +68,12 @@ final class PhabricatorProjectEditDetailsController $v_join = $request->getStr('can_join'); $v_color = $request->getStr('color'); $v_icon = $request->getStr('icon'); - $v_locked = $request->getInt('is_membership_locked', 0); $type_name = PhabricatorProjectTransaction::TYPE_NAME; $type_slugs = PhabricatorProjectTransaction::TYPE_SLUGS; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_icon = PhabricatorProjectTransaction::TYPE_ICON; $type_color = PhabricatorProjectTransaction::TYPE_COLOR; - $type_locked = PhabricatorProjectTransaction::TYPE_LOCKED; $xactions = array(); @@ -108,10 +105,6 @@ final class PhabricatorProjectEditDetailsController ->setTransactionType($type_color) ->setNewValue($v_color); - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_locked) - ->setNewValue($v_locked); - $xactions = array_merge( $xactions, $field_list->buildFieldTransactionsFromRequest( @@ -190,11 +183,6 @@ final class PhabricatorProjectEditDetailsController $shades = PhabricatorProjectIconSet::getColorMap(); - list($can_lock, $lock_message) = $this->explainApplicationCapability( - ProjectCanLockProjectsCapability::CAPABILITY, - pht('You can update the Lock Project setting.'), - pht('You can not update the Lock Project setting.')); - $form ->appendChild( id(new PHUIFormIconSetControl()) @@ -249,17 +237,7 @@ final class PhabricatorProjectEditDetailsController pht('Users who can edit a project can always join a project.')) ->setPolicyObject($project) ->setPolicies($policies) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN)) - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Lock Project')) - ->setDisabled(!$can_lock) - ->addCheckbox( - 'is_membership_locked', - 1, - pht('Prevent members from leaving this project.'), - $v_locked) - ->setCaption($lock_message)); + ->setCapability(PhabricatorPolicyCapability::CAN_JOIN)); if ($request->isAjax()) { $errors = array(); diff --git a/src/applications/project/controller/PhabricatorProjectLockController.php b/src/applications/project/controller/PhabricatorProjectLockController.php new file mode 100644 index 0000000000..744be32f99 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectLockController.php @@ -0,0 +1,76 @@ +getViewer(); + + $this->requireApplicationCapability( + ProjectCanLockProjectsCapability::CAPABILITY); + + $id = $request->getURIData('id'); + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + + $done_uri = $project->getURI(); + $is_locked = $project->getIsMembershipLocked(); + + if ($request->isFormPost()) { + $xactions = array(); + + if ($is_locked) { + $new_value = 0; + } else { + $new_value = 1; + } + + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_LOCKED) + ->setNewValue($new_value); + + $editor = id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($project, $xactions); + + return id(new AphrontRedirectResponse())->setURI($done_uri); + } + + if ($project->getIsMembershipLocked()) { + $title = pht('Unlock Project'); + $body = pht( + 'If you unlock this project, members will be free to leave.'); + $button = pht('Unlock Project'); + } else { + $title = pht('Lock Project'); + $body = pht( + 'If you lock this project, members will be prevented from '. + 'leaving it.'); + $button = pht('Lock Project'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addSubmitbutton($button) + ->addCancelButton($done_uri); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index fa443ebf6e..409fd49202 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -106,6 +106,25 @@ final class PhabricatorProjectProfileController ->setWorkflow(true)); } + $can_lock = $can_edit && $this->hasApplicationCapability( + ProjectCanLockProjectsCapability::CAPABILITY); + + if ($project->getIsMembershipLocked()) { + $lock_name = pht('Unlock Project'); + $lock_icon = 'fa-unlock'; + } else { + $lock_name = pht('Lock Project'); + $lock_icon = 'fa-lock'; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($lock_name) + ->setIcon($lock_icon) + ->setHref($this->getApplicationURI("lock/{$id}/")) + ->setDisabled(!$can_lock) + ->setWorkflow(true)); + $action = null; if (!$project->isUserMember($viewer->getPHID())) { $can_join = PhabricatorPolicyFilter::hasCapability( From 6fe882e50a3e92b5f7f8d0b8de07b1f5ad8f987e Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 06:45:53 -0800 Subject: [PATCH 12/83] Convert projects to EditEngine Summary: Ref T10010. This is pretty straightforward with a couple of very minor new behaviors, like the icon selector edit field. Test Plan: - Created projects. - Edited projects. - Saw "Create Project" in quick create menu. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14896 --- src/__phutil_library_map__.php | 12 +- .../PhabricatorProjectApplication.php | 23 +- .../conduit/ProjectEditConduitAPIMethod.php | 19 ++ .../PhabricatorProjectEditController.php | 12 + ...habricatorProjectEditDetailsController.php | 282 ------------------ .../PhabricatorProjectListController.php | 13 +- .../PhabricatorProjectProfileController.php | 2 +- .../PhabricatorProjectDescriptionField.php | 3 +- .../PhabricatorProjectTransactionEditor.php | 34 +++ .../engine/PhabricatorProjectEditEngine.php | 126 ++++++++ .../storage/PhabricatorProjectTransaction.php | 8 +- .../editfield/PhabricatorIconSetEditField.php | 30 ++ .../PhabricatorStringListEditField.php | 18 ++ .../PhabricatorStandardCustomField.php | 23 +- 14 files changed, 288 insertions(+), 317 deletions(-) create mode 100644 src/applications/project/conduit/ProjectEditConduitAPIMethod.php create mode 100644 src/applications/project/controller/PhabricatorProjectEditController.php delete mode 100644 src/applications/project/controller/PhabricatorProjectEditDetailsController.php create mode 100644 src/applications/project/engine/PhabricatorProjectEditEngine.php create mode 100644 src/applications/transactions/editfield/PhabricatorIconSetEditField.php create mode 100644 src/applications/transactions/editfield/PhabricatorStringListEditField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 085d006bec..7f9999df42 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2381,6 +2381,7 @@ phutil_register_library_map(array( 'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php', 'PhabricatorIconRemarkupRule' => 'applications/macro/markup/PhabricatorIconRemarkupRule.php', 'PhabricatorIconSet' => 'applications/files/iconset/PhabricatorIconSet.php', + 'PhabricatorIconSetEditField' => 'applications/transactions/editfield/PhabricatorIconSetEditField.php', 'PhabricatorIconSetIcon' => 'applications/files/iconset/PhabricatorIconSetIcon.php', 'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php', 'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php', @@ -2845,7 +2846,8 @@ phutil_register_library_map(array( 'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php', 'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', - 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', + 'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php', + 'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', @@ -3187,6 +3189,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', 'PhabricatorStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php', + 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php', 'PhabricatorSubscribedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorSubscribedToObjectEdgeType.php', 'PhabricatorSubscribersEditField' => 'applications/transactions/editfield/PhabricatorSubscribersEditField.php', @@ -3746,6 +3749,7 @@ phutil_register_library_map(array( 'ProjectDefaultEditCapability' => 'applications/project/capability/ProjectDefaultEditCapability.php', 'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php', 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', + 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', @@ -6642,6 +6646,7 @@ phutil_register_library_map(array( 'PhabricatorIRCProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorIconRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorIconSet' => 'Phobject', + 'PhabricatorIconSetEditField' => 'PhabricatorEditField', 'PhabricatorIconSetIcon' => 'Phobject', 'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorImageTransformer' => 'Phobject', @@ -7192,7 +7197,8 @@ phutil_register_library_map(array( 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', - 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', + 'PhabricatorProjectEditController' => 'PhabricatorProjectController', + 'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectFeedController' => 'PhabricatorProjectController', 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', @@ -7594,6 +7600,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorStreamingProtocolAdapter' => 'PhabricatorProtocolAdapter', + 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorSubscribersEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorSubscribersQuery' => 'PhabricatorQuery', @@ -8300,6 +8307,7 @@ phutil_register_library_map(array( 'ProjectDefaultEditCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index 6936faa7fc..954c46c170 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -43,8 +43,6 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { '/project/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorProjectListController', 'filter/(?P[^/]+)/' => 'PhabricatorProjectListController', - 'details/(?P[1-9]\d*)/' - => 'PhabricatorProjectEditDetailsController', 'archive/(?P[1-9]\d*)/' => 'PhabricatorProjectArchiveController', 'lock/(?P[1-9]\d*)/' @@ -61,7 +59,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { => 'PhabricatorProjectViewController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorProjectEditPictureController', - 'create/' => 'PhabricatorProjectEditDetailsController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorProjectEditController', 'subprojects/(?P[1-9]\d*)/' => 'PhabricatorProjectSubprojectsController', 'milestones/(?P[1-9]\d*)/' @@ -97,21 +96,9 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { } public function getQuickCreateItems(PhabricatorUser $viewer) { - $can_create = PhabricatorPolicyFilter::hasCapability( - $viewer, - $this, - ProjectCreateProjectsCapability::CAPABILITY); - - $items = array(); - if ($can_create) { - $item = id(new PHUIListItemView()) - ->setName(pht('Project')) - ->setIcon('fa-briefcase') - ->setHref($this->getBaseURI().'create/'); - $items[] = $item; - } - - return $items; + return id(new PhabricatorProjectEditEngine()) + ->setViewer($viewer) + ->loadQuickCreateItems(); } protected function getCustomCapabilities() { diff --git a/src/applications/project/conduit/ProjectEditConduitAPIMethod.php b/src/applications/project/conduit/ProjectEditConduitAPIMethod.php new file mode 100644 index 0000000000..acdcf4c1bc --- /dev/null +++ b/src/applications/project/conduit/ProjectEditConduitAPIMethod.php @@ -0,0 +1,19 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php b/src/applications/project/controller/PhabricatorProjectEditDetailsController.php deleted file mode 100644 index 46287f3801..0000000000 --- a/src/applications/project/controller/PhabricatorProjectEditDetailsController.php +++ /dev/null @@ -1,282 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $id = $request->getURIData('id'); - $is_new = false; - - $project = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->needSlugs(true) - ->needImages(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - - } else { - $is_new = true; - - $this->requireApplicationCapability( - ProjectCreateProjectsCapability::CAPABILITY); - - $project = PhabricatorProject::initializeNewProject($viewer); - } - - $field_list = PhabricatorCustomField::getObjectFields( - $project, - PhabricatorCustomField::ROLE_EDIT); - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($project); - - $e_name = true; - $e_slugs = false; - $e_edit = null; - - $v_name = $project->getName(); - $project_slugs = $project->getSlugs(); - $project_slugs = mpull($project_slugs, 'getSlug', 'getSlug'); - $v_primary_slug = $project->getPrimarySlug(); - unset($project_slugs[$v_primary_slug]); - $v_slugs = $project_slugs; - $v_color = $project->getColor(); - $v_icon = $project->getIcon(); - - $validation_exception = null; - - if ($request->isFormPost()) { - $e_name = null; - $e_slugs = null; - - $v_name = $request->getStr('name'); - $v_slugs = $request->getStrList('slugs'); - $v_view = $request->getStr('can_view'); - $v_edit = $request->getStr('can_edit'); - $v_join = $request->getStr('can_join'); - $v_color = $request->getStr('color'); - $v_icon = $request->getStr('icon'); - - $type_name = PhabricatorProjectTransaction::TYPE_NAME; - $type_slugs = PhabricatorProjectTransaction::TYPE_SLUGS; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - $type_icon = PhabricatorProjectTransaction::TYPE_ICON; - $type_color = PhabricatorProjectTransaction::TYPE_COLOR; - - $xactions = array(); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_slugs) - ->setNewValue($v_slugs); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_JOIN_POLICY) - ->setNewValue($v_join); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_icon) - ->setNewValue($v_icon); - - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType($type_color) - ->setNewValue($v_color); - - $xactions = array_merge( - $xactions, - $field_list->buildFieldTransactionsFromRequest( - new PhabricatorProjectTransaction(), - $request)); - - $editor = id(new PhabricatorProjectTransactionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - if ($is_new) { - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue( - 'edge:type', - PhabricatorProjectProjectHasMemberEdgeType::EDGECONST) - ->setNewValue( - array( - '+' => array($viewer->getPHID() => $viewer->getPHID()), - )); - } - - try { - - $editor->applyTransactions($project, $xactions); - - if ($request->isAjax()) { - return id(new AphrontAjaxResponse()) - ->setContent(array( - 'phid' => $project->getPHID(), - 'name' => $project->getName(), - )); - } - - $redirect_uri = - $this->getApplicationURI('profile/'.$project->getID().'/'); - - return id(new AphrontRedirectResponse())->setURI($redirect_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - $e_slugs = $ex->getShortMessage($type_slugs); - $e_edit = $ex->getShortMessage($type_edit); - - $project->setViewPolicy($v_view); - $project->setEditPolicy($v_edit); - $project->setJoinPolicy($v_join); - } - } - - if ($is_new) { - $header_name = pht('Create a New Project'); - $title = pht('Create Project'); - } else { - $header_name = pht('Edit Project'); - $title = pht('Edit Project'); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($project) - ->execute(); - $v_slugs = implode(', ', $v_slugs); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)); - $field_list->appendFieldsToForm($form); - - $shades = PhabricatorProjectIconSet::getColorMap(); - - $form - ->appendChild( - id(new PHUIFormIconSetControl()) - ->setLabel(pht('Icon')) - ->setName('icon') - ->setIconSet(new PhabricatorProjectIconSet()) - ->setValue($v_icon)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Color')) - ->setName('color') - ->setValue($v_color) - ->setOptions($shades)); - - if (!$is_new) { - $form->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Primary Hashtag')) - ->setCaption(pht('The primary hashtag is derived from the name.')) - ->setValue($v_primary_slug)); - } - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Additional Hashtags')) - ->setCaption(pht( - 'Specify a comma-separated list of additional hashtags.')) - ->setName('slugs') - ->setValue($v_slugs) - ->setError($e_slugs)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setName('can_view') - ->setPolicyObject($project) - ->setPolicies($policies) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setName('can_edit') - ->setPolicyObject($project) - ->setPolicies($policies) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setError($e_edit)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setName('can_join') - ->setCaption( - pht('Users who can edit a project can always join a project.')) - ->setPolicyObject($project) - ->setPolicies($policies) - ->setCapability(PhabricatorPolicyCapability::CAN_JOIN)); - - if ($request->isAjax()) { - $errors = array(); - if ($validation_exception) { - $errors = mpull($ex->getErrors(), 'getMessage'); - } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setWidth(AphrontDialogView::WIDTH_FULL) - ->setTitle($header_name) - ->setErrors($errors) - ->appendForm($form) - ->addSubmitButton($title) - ->addCancelButton('/project/'); - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($this->getApplicationURI()) - ->setValue(pht('Save'))); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setValidationException($validation_exception) - ->setForm($form); - - if (!$is_new) { - $nav = $this->buildIconNavView($project); - $nav->selectFilter("details/{$id}/"); - $nav->appendChild($form_box); - } else { - $nav = array($form_box); - } - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, - )); - } -} diff --git a/src/applications/project/controller/PhabricatorProjectListController.php b/src/applications/project/controller/PhabricatorProjectListController.php index ea56036c7b..db4ef5b9f5 100644 --- a/src/applications/project/controller/PhabricatorProjectListController.php +++ b/src/applications/project/controller/PhabricatorProjectListController.php @@ -26,16 +26,9 @@ final class PhabricatorProjectListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - ProjectCreateProjectsCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Project')) - ->setHref($this->getApplicationURI('create/')) - ->setIcon('fa-plus-square') - ->setWorkflow(!$can_create) - ->setDisabled(!$can_create)); + id(new PhabricatorProjectEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 409fd49202..9262bf26c1 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -76,7 +76,7 @@ final class PhabricatorProjectProfileController id(new PhabricatorActionView()) ->setName(pht('Edit Details')) ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("details/{$id}/")) + ->setHref($this->getApplicationURI("edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); diff --git a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php index 0c18398293..5e4e561523 100644 --- a/src/applications/project/customfield/PhabricatorProjectDescriptionField.php +++ b/src/applications/project/customfield/PhabricatorProjectDescriptionField.php @@ -13,7 +13,8 @@ final class PhabricatorProjectDescriptionField 'description' => pht('Short project description.'), 'fulltext' => PhabricatorSearchDocumentFieldType::FIELD_BODY, ), - )); + ), + $internal = true); } } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 72ddc1470c..a5ef9b840d 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -745,4 +745,38 @@ final class PhabricatorProjectTransactionEditor return $copy; } + protected function expandTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + $actor = $this->getActor(); + $actor_phid = $actor->getPHID(); + + $results = parent::expandTransactions($object, $xactions); + + // Automatically add the author as a member when they create a project + // if they're using the web interface. + + $content_source = $this->getContentSource(); + $source_web = PhabricatorContentSource::SOURCE_WEB; + $is_web = ($content_source->getSource() === $source_web); + + if ($this->getIsNewObject() && $is_web) { + if ($actor_phid) { + $type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + + $results[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $type_member) + ->setNewValue( + array( + '+' => array($actor_phid => $actor_phid), + )); + } + } + + return $results; + } + + } diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php new file mode 100644 index 0000000000..976a65b78c --- /dev/null +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -0,0 +1,126 @@ +getViewer()); + } + + protected function newObjectQuery() { + return id(new PhabricatorProjectQuery()) + ->needSlugs(true); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Project'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Project'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + ProjectCreateProjectsCapability::CAPABILITY); + } + + protected function newBuiltinEngineConfigurations() { + $configuration = head(parent::newBuiltinEngineConfigurations()); + + // TODO: This whole method is clumsy, and the ordering for the custom + // field is especially clumsy. Maybe try to make this more natural to + // express. + + $configuration + ->setFieldOrder( + array( + 'name', + 'std:project:internal:description', + 'icon', + 'color', + 'slugs', + 'subscriberPHIDs', + )); + + return array( + $configuration, + ); + } + + protected function buildCustomEditFields($object) { + $slugs = mpull($object->getSlugs(), 'getSlug'); + $slugs = array_fuse($slugs); + unset($slugs[$object->getPrimarySlug()]); + $slugs = array_values($slugs); + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setDescription(pht('Project name.')) + ->setConduitDescription(pht('Rename the project')) + ->setConduitTypeDescription(pht('New project name.')) + ->setValue($object->getName()), + id(new PhabricatorIconSetEditField()) + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON) + ->setIconSet(new PhabricatorProjectIconSet()) + ->setDescription(pht('Project icon.')) + ->setConduitDescription(pht('Change the project icon.')) + ->setConduitTypeDescription(pht('New project icon.')) + ->setValue($object->getIcon()), + id(new PhabricatorSelectEditField()) + ->setKey('color') + ->setLabel(pht('Color')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR) + ->setOptions(PhabricatorProjectIconSet::getColorMap()) + ->setDescription(pht('Project tag color.')) + ->setConduitDescription(pht('Change the project tag color.')) + ->setConduitTypeDescription(pht('New project tag color.')) + ->setValue($object->getColor()), + id(new PhabricatorStringListEditField()) + ->setKey('slugs') + ->setLabel(pht('Additional Hashtags')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS) + ->setDescription(pht('Additional project slugs.')) + ->setConduitDescription(pht('Change project slugs.')) + ->setConduitTypeDescription(pht('New list of slugs.')) + ->setValue($slugs), + ); + } + +} diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index b928d43a91..b16a4e30ca 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -99,9 +99,15 @@ final class PhabricatorProjectTransaction public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); - $author_handle = $this->renderHandleLink($this->getAuthorPHID()); + $author_phid = $this->getAuthorPHID(); + $author_handle = $this->renderHandleLink($author_phid); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this project.', + $this->renderHandleLink($author_phid)); + case self::TYPE_NAME: if ($old === null) { return pht( diff --git a/src/applications/transactions/editfield/PhabricatorIconSetEditField.php b/src/applications/transactions/editfield/PhabricatorIconSetEditField.php new file mode 100644 index 0000000000..a500da5430 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorIconSetEditField.php @@ -0,0 +1,30 @@ +iconSet = $icon_set; + return $this; + } + + public function getIconSet() { + return $this->iconSet; + } + + protected function newControl() { + return id(new PHUIFormIconSetControl()) + ->setIconSet($this->getIconSet()); + } + + protected function newConduitParameterType() { + return new ConduitStringParameterType(); + } + + protected function newHTTPParameterType() { + return new AphrontStringHTTPParameterType(); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorStringListEditField.php b/src/applications/transactions/editfield/PhabricatorStringListEditField.php new file mode 100644 index 0000000000..64cb1fe08c --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorStringListEditField.php @@ -0,0 +1,18 @@ +setAncestorClass(__CLASS__) @@ -48,6 +50,10 @@ abstract class PhabricatorStandardCustomField ->setFieldConfig($value) ->setApplicationField($template); + if ($builtin) { + $standard->setIsBuiltin(true); + } + $field = $template->setProxy($standard); $fields[] = $field; } @@ -93,6 +99,15 @@ abstract class PhabricatorStandardCustomField return $this; } + public function setIsBuiltin($is_builtin) { + $this->isBuiltin = $is_builtin; + return $this; + } + + public function getIsBuiltin() { + return $this->isBuiltin; + } + public function setFieldConfig(array $config) { foreach ($config as $key => $value) { switch ($key) { @@ -470,7 +485,11 @@ abstract class PhabricatorStandardCustomField } public function getModernFieldKey() { - return 'custom.'.$this->getRawStandardFieldKey(); + if ($this->getIsBuiltin()) { + return $this->getRawStandardFieldKey(); + } else { + return 'custom.'.$this->getRawStandardFieldKey(); + } } public function getConduitDictionaryValue() { From 10ed33052361be82cbc09884118d65ec0601bd55 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 27 Dec 2015 17:38:25 -0800 Subject: [PATCH 13/83] Update PhamePost to EditEngine Summary: Allows create and edit workflows through EditEngine. Not sure I did the 'blog' stuff correct. Test Plan: Create a new post, edit a post, move a post. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14802 --- src/__phutil_library_map__.php | 2 + .../phame/controller/PhameHomeController.php | 18 +- .../post/PhamePostEditController.php | 180 ++---------------- .../phame/editor/PhamePostEditEngine.php | 110 +++++++++++ 4 files changed, 141 insertions(+), 169 deletions(-) create mode 100644 src/applications/phame/editor/PhamePostEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7f9999df42..43079c5c11 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3430,6 +3430,7 @@ phutil_register_library_map(array( 'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', + 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', 'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php', 'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php', @@ -7890,6 +7891,7 @@ phutil_register_library_map(array( 'PhamePostCommentController' => 'PhamePostController', 'PhamePostController' => 'PhameController', 'PhamePostEditController' => 'PhamePostController', + 'PhamePostEditEngine' => 'PhabricatorEditEngine', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', 'PhamePostHistoryController' => 'PhamePostController', 'PhamePostListController' => 'PhamePostController', diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index c547071e67..75051acef6 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -28,10 +28,18 @@ final class PhameHomeController extends PhamePostController { ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) ->executeWithCursorPager($pager); - $post_list = id(new PhamePostListView()) - ->setPosts($posts) - ->setViewer($viewer) - ->showBlog(true); + if ($posts) { + $post_list = id(new PhamePostListView()) + ->setPosts($posts) + ->setViewer($viewer) + ->showBlog(true); + } else { + $post_list = id(new PHUIBigInfoView()) + ->setIcon('fa-star') + ->setTitle('No Visible Posts') + ->setDescription( + pht('There aren\'t any visible blog posts.')); + } } else { $create_button = id(new PHUIButtonView()) ->setTag('a') @@ -43,7 +51,7 @@ final class PhameHomeController extends PhamePostController { ->setIcon('fa-star') ->setTitle('Welcome to Phame') ->setDescription( - pht('There aren\'t any visible Blog Posts.')) + pht('There aren\'t any visible blog posts.')) ->addAction($create_button); } diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php index ff8855be69..73676c8813 100644 --- a/src/applications/phame/controller/post/PhamePostEditController.php +++ b/src/applications/phame/controller/post/PhamePostEditController.php @@ -6,10 +6,6 @@ final class PhamePostEditController extends PhamePostController { $viewer = $request->getViewer(); $id = $request->getURIData('id'); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - pht('Blogs'), - $this->getApplicationURI('blog/')); if ($id) { $post = id(new PhamePostQuery()) ->setViewer($viewer) @@ -22,173 +18,29 @@ final class PhamePostEditController extends PhamePostController { if (!$post) { return new Aphront404Response(); } - - $cancel_uri = $this->getApplicationURI('/post/view/'.$id.'/'); - $submit_button = pht('Save Changes'); - $page_title = pht('Edit Post'); - - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $post->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( - $post->getPHID()); - $blog = $post->getBlog(); - - + $blog_id = $post->getBlog()->getID(); } else { - $blog = id(new PhameBlogQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getInt('blog'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$blog) { - return new Aphront404Response(); - } - $v_projects = array(); - $v_cc = array(); - - $post = PhamePost::initializePost($viewer, $blog); - $cancel_uri = $this->getApplicationURI('/blog/view/'.$blog->getID().'/'); - - $submit_button = pht('Create Post'); - $page_title = pht('Create Post'); + $blog_id = $request->getInt('blog'); } - $title = $post->getTitle(); - $body = $post->getBody(); - $visibility = $post->getVisibility(); - - $e_title = true; - $validation_exception = null; - if ($request->isFormPost()) { - $title = $request->getStr('title'); - $body = $request->getStr('body'); - $v_projects = $request->getArr('projects'); - $v_cc = $request->getArr('cc'); - $visibility = $request->getInt('visibility'); - - $xactions = array( - id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_TITLE) - ->setNewValue($title), - id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_BODY) - ->setNewValue($body), - id(new PhamePostTransaction()) - ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) - ->setNewValue($visibility), - id(new PhamePostTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) - ->setNewValue(array('=' => $v_cc)), - - ); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new PhamePostEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($post, $xactions); - - $uri = $post->getViewURI(); - return id(new AphrontRedirectResponse())->setURI($uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $e_title = $validation_exception->getShortMessage( - PhamePostTransaction::TYPE_TITLE); - } - } - - $handle = id(new PhabricatorHandleQuery()) + $blog = id(new PhameBlogQuery()) ->setViewer($viewer) - ->withPHIDs(array($post->getBlogPHID())) + ->withIDs(array($blog_id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->executeOne(); - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('blog', $request->getInt('blog')) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Blog')) - ->setValue($handle->renderLink())) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Title')) - ->setName('title') - ->setValue($title) - ->setID('post-title') - ->setError($e_title)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Visibility')) - ->setName('visibility') - ->setValue($visibility) - ->setOptions(PhameConstants::getPhamePostStatusMap())) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setLabel(pht('Body')) - ->setName('body') - ->setValue($body) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setID('post-body') - ->setUser($viewer) - ->setDisableMacros(true)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Subscribers')) - ->setName('cc') - ->setValue($v_cc) - ->setUser($viewer) - ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Projects')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($submit_button)); + if (!$blog) { + return new Aphront404Response(); + } - $preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader($post->getTitle()) - ->setPreviewURI($this->getApplicationURI('post/preview/')) - ->setControlID('post-body') - ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($page_title) - ->setValidationException($validation_exception) - ->setForm($form); - - $crumbs->addTextCrumb( - $blog->getName(), - $blog->getViewURI()); - $crumbs->addTextCrumb( - $page_title, - $cancel_uri); - - return $this->newPage() - ->setTitle($page_title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $form_box, - $preview, - )); + return id(new PhamePostEditEngine()) + ->setController($this) + ->setBlog($blog) + ->buildResponse(); } } diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php new file mode 100644 index 0000000000..22e4f50922 --- /dev/null +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -0,0 +1,110 @@ +blog = $blog; + return $this; + } + + public function getEngineApplicationClass() { + return 'PhabricatorPhameApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + + if ($this->blog) { + $blog = $this->blog; + } else { + $blog = PhameBlog::initializeNewBlog($viewer); + } + + return PhamePost::initializePost($viewer, $blog); + } + + protected function newObjectQuery() { + return new PhamePostQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Post'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s', $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getTitle(); + } + + protected function getObjectCreateShortText() { + return pht('Create Post'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + + if ($this->blog) { + $blog_title = pht('Blog: %s', $this->blog->getName()); + } else { + $blog_title = pht('Sample Blog Title'); + } + + return array( + id(new PhabricatorInstructionsEditField()) + ->setValue($blog_title), + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Title')) + ->setDescription(pht('Post title.')) + ->setConduitDescription(pht('Retitle the post.')) + ->setConduitTypeDescription(pht('New post title.')) + ->setTransactionType(PhamePostTransaction::TYPE_TITLE) + ->setValue($object->getTitle()), + id(new PhabricatorSelectEditField()) + ->setKey('visibility') + ->setLabel(pht('Visibility')) + ->setDescription(pht('Post visibility.')) + ->setConduitDescription(pht('Change post visibility.')) + ->setConduitTypeDescription(pht('New post visibility constant.')) + ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) + ->setValue($object->getVisibility()) + ->setOptions(PhameConstants::getPhamePostStatusMap()), + id(new PhabricatorRemarkupEditField()) + ->setKey('body') + ->setLabel(pht('Body')) + ->setDescription(pht('Post body.')) + ->setConduitDescription(pht('Change post body.')) + ->setConduitTypeDescription(pht('New post body.')) + ->setTransactionType(PhamePostTransaction::TYPE_BODY) + ->setValue($object->getBody()) + ->setPreviewPanel( + id(new PHUIRemarkupPreviewPanel()) + ->setHeader(pht('Blog Post')) + ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT)), + ); + } + +} From 6cb01374a59063b19b61ec1bdefd9785032c4d2b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Dec 2015 03:50:22 -0800 Subject: [PATCH 14/83] Remove previous-generation Phame Conduit API methods Summary: Ref T9897. We can now provide modern `search` and `edit` endpoints (I'll do this next). Test Plan: Grepped for removed methods. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9897 Differential Revision: https://secure.phabricator.com/D14898 --- src/__phutil_library_map__.php | 8 -- .../phame/conduit/PhameConduitAPIMethod.php | 9 -- .../PhameCreatePostConduitAPIMethod.php | 97 ------------------- .../conduit/PhameQueryConduitAPIMethod.php | 78 --------------- .../PhameQueryPostsConduitAPIMethod.php | 97 ------------------- 5 files changed, 289 deletions(-) delete mode 100644 src/applications/phame/conduit/PhameConduitAPIMethod.php delete mode 100644 src/applications/phame/conduit/PhameCreatePostConduitAPIMethod.php delete mode 100644 src/applications/phame/conduit/PhameQueryConduitAPIMethod.php delete mode 100644 src/applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 43079c5c11..6772f00113 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3417,10 +3417,8 @@ phutil_register_library_map(array( 'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php', 'PhameBlogTransactionQuery' => 'applications/phame/query/PhameBlogTransactionQuery.php', 'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php', - 'PhameConduitAPIMethod' => 'applications/phame/conduit/PhameConduitAPIMethod.php', 'PhameConstants' => 'applications/phame/constants/PhameConstants.php', 'PhameController' => 'applications/phame/controller/PhameController.php', - 'PhameCreatePostConduitAPIMethod' => 'applications/phame/conduit/PhameCreatePostConduitAPIMethod.php', 'PhameDAO' => 'applications/phame/storage/PhameDAO.php', 'PhameDescriptionView' => 'applications/phame/view/PhameDescriptionView.php', 'PhameDraftListView' => 'applications/phame/view/PhameDraftListView.php', @@ -3445,8 +3443,6 @@ phutil_register_library_map(array( 'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php', 'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php', 'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php', - 'PhameQueryConduitAPIMethod' => 'applications/phame/conduit/PhameQueryConduitAPIMethod.php', - 'PhameQueryPostsConduitAPIMethod' => 'applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php', 'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php', 'PhameSite' => 'applications/phame/site/PhameSite.php', 'PhluxController' => 'applications/phlux/controller/PhluxController.php', @@ -7868,10 +7864,8 @@ phutil_register_library_map(array( 'PhameBlogTransaction' => 'PhabricatorApplicationTransaction', 'PhameBlogTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhameBlogViewController' => 'PhameLiveController', - 'PhameConduitAPIMethod' => 'ConduitAPIMethod', 'PhameConstants' => 'Phobject', 'PhameController' => 'PhabricatorController', - 'PhameCreatePostConduitAPIMethod' => 'PhameConduitAPIMethod', 'PhameDAO' => 'PhabricatorLiskDAO', 'PhameDescriptionView' => 'AphrontTagView', 'PhameDraftListView' => 'AphrontTagView', @@ -7906,8 +7900,6 @@ phutil_register_library_map(array( 'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhamePostViewController' => 'PhameLiveController', - 'PhameQueryConduitAPIMethod' => 'PhameConduitAPIMethod', - 'PhameQueryPostsConduitAPIMethod' => 'PhameConduitAPIMethod', 'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhameSite' => 'PhabricatorSite', 'PhluxController' => 'PhabricatorController', diff --git a/src/applications/phame/conduit/PhameConduitAPIMethod.php b/src/applications/phame/conduit/PhameConduitAPIMethod.php deleted file mode 100644 index 6f83c7777f..0000000000 --- a/src/applications/phame/conduit/PhameConduitAPIMethod.php +++ /dev/null @@ -1,9 +0,0 @@ - 'required phid', - 'title' => 'required string', - 'body' => 'required string', - 'bloggerPHID' => 'optional phid', - 'isDraft' => 'optional bool', - ); - } - - protected function defineReturnType() { - return 'list'; - } - - protected function defineErrorTypes() { - return array( - 'ERR-INVALID-PARAMETER' => - pht('Missing or malformed parameter.'), - 'ERR-INVALID-BLOG' => - pht('Invalid blog PHID or user can not post to blog.'), - ); - } - - protected function execute(ConduitAPIRequest $request) { - $user = $request->getUser(); - $blog_phid = $request->getValue('blogPHID'); - $title = $request->getValue('title'); - $body = $request->getValue('body'); - $exception_description = array(); - if (!$blog_phid) { - $exception_description[] = pht('No blog phid.'); - } - if (!strlen($title)) { - $exception_description[] = pht('No post title.'); - } - if (!strlen($body)) { - $exception_description[] = pht('No post body.'); - } - if ($exception_description) { - throw id(new ConduitException('ERR-INVALID-PARAMETER')) - ->setErrorDescription(implode("\n", $exception_description)); - } - - $blogger_phid = $request->getValue('bloggerPHID'); - if ($blogger_phid) { - $blogger = id(new PhabricatorPeopleQuery()) - ->setViewer($user) - ->withPHIDs(array($blogger_phid)) - ->executeOne(); - } else { - $blogger = $user; - } - - $blog = id(new PhameBlogQuery()) - ->setViewer($blogger) - ->withPHIDs(array($blog_phid)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - - if (!$blog) { - throw new ConduitException('ERR-INVALID-BLOG'); - } - - $post = PhamePost::initializePost($blogger, $blog); - $is_draft = $request->getValue('isDraft', false); - if (!$is_draft) { - $post->setDatePublished(time()); - $post->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); - } - $post->setTitle($title); - $post->setBody($body); - $post->save(); - - return $post->toDictionary(); - } - -} diff --git a/src/applications/phame/conduit/PhameQueryConduitAPIMethod.php b/src/applications/phame/conduit/PhameQueryConduitAPIMethod.php deleted file mode 100644 index d82afc2faf..0000000000 --- a/src/applications/phame/conduit/PhameQueryConduitAPIMethod.php +++ /dev/null @@ -1,78 +0,0 @@ - 'optional list', - 'phids' => 'optional list', - 'after' => 'optional int', - 'before' => 'optional int', - 'limit' => 'optional int', - ); - } - - protected function defineReturnType() { - return 'list'; - } - - protected function execute(ConduitAPIRequest $request) { - $query = new PhameBlogQuery(); - - $query->setViewer($request->getUser()); - - $ids = $request->getValue('ids', array()); - if ($ids) { - $query->withIDs($ids); - } - - $phids = $request->getValue('phids', array()); - if ($phids) { - $query->withPHIDs($phids); - } - - $after = $request->getValue('after', null); - if ($after !== null) { - $query->setAfterID($after); - } - - $before = $request->getValue('before', null); - if ($before !== null) { - $query->setBeforeID($before); - } - - $limit = $request->getValue('limit', null); - if ($limit !== null) { - $query->setLimit($limit); - } - - $blogs = $query->execute(); - - $results = array(); - foreach ($blogs as $blog) { - $results[] = array( - 'id' => $blog->getID(), - 'phid' => $blog->getPHID(), - 'name' => $blog->getName(), - 'description' => $blog->getDescription(), - 'domain' => $blog->getDomain(), - 'creatorPHID' => $blog->getCreatorPHID(), - ); - } - - return $results; - } - -} diff --git a/src/applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php b/src/applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php deleted file mode 100644 index fba2536140..0000000000 --- a/src/applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php +++ /dev/null @@ -1,97 +0,0 @@ - 'optional list', - 'phids' => 'optional list', - 'blogPHIDs' => 'optional list', - 'bloggerPHIDs' => 'optional list', - 'published' => 'optional bool', - 'publishedAfter' => 'optional date', - 'before' => 'optional int', - 'after' => 'optional int', - 'limit' => 'optional int', - ); - } - - protected function defineReturnType() { - return 'list'; - } - - protected function execute(ConduitAPIRequest $request) { - $query = new PhamePostQuery(); - - $query->setViewer($request->getUser()); - - $ids = $request->getValue('ids', array()); - if ($ids) { - $query->withIDs($ids); - } - - $phids = $request->getValue('phids', array()); - if ($phids) { - $query->withPHIDs($phids); - } - - $blog_phids = $request->getValue('blogPHIDs', array()); - if ($blog_phids) { - $query->withBlogPHIDs($blog_phids); - } - - $blogger_phids = $request->getValue('bloggerPHIDs', array()); - if ($blogger_phids) { - $query->withBloggerPHIDs($blogger_phids); - } - - $published = $request->getValue('published', null); - if ($published === true) { - $query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED); - } else if ($published === false) { - $query->withVisibility(PhameConstants::VISIBILITY_DRAFT); - } - - $published_after = $request->getValue('publishedAfter', null); - if ($published_after !== null) { - $query->withPublishedAfter($published_after); - } - - $after = $request->getValue('after', null); - if ($after !== null) { - $query->setAfterID($after); - } - - $before = $request->getValue('before', null); - if ($before !== null) { - $query->setBeforeID($before); - } - - $limit = $request->getValue('limit', null); - if ($limit !== null) { - $query->setLimit($limit); - } - - $posts = $query->execute(); - - $results = array(); - foreach ($posts as $post) { - $results[] = $post->toDictionary(); - } - - return $results; - } - -} From 3335bcbfc9f2c8bda8e0ea69df39cd0b4d91081d Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Dec 2015 03:57:03 -0800 Subject: [PATCH 15/83] Add a phame.blog.edit Conduit API endpoint Summary: Ref T9897. Test Plan: Used API to make a few changes to a blog. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9897 Differential Revision: https://secure.phabricator.com/D14899 --- src/__phutil_library_map__.php | 4 +++- .../ConduitStringParameterType.php | 2 +- .../conduit/PhameBlogEditConduitAPIMethod.php | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/applications/phame/conduit/PhameBlogEditConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6772f00113..4898e6874c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3402,6 +3402,7 @@ phutil_register_library_map(array( 'PhameBlogArchiveController' => 'applications/phame/controller/blog/PhameBlogArchiveController.php', 'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php', 'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php', + 'PhameBlogEditConduitAPIMethod' => 'applications/phame/conduit/PhameBlogEditConduitAPIMethod.php', 'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php', 'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php', 'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', @@ -4164,7 +4165,7 @@ phutil_register_library_map(array( 'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow', 'ConduitStringListParameterType' => 'ConduitListParameterType', - 'ConduitStringParameterType' => 'ConduitListParameterType', + 'ConduitStringParameterType' => 'ConduitParameterType', 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'ConduitUserListParameterType' => 'ConduitListParameterType', 'ConduitWildParameterType' => 'ConduitListParameterType', @@ -7849,6 +7850,7 @@ phutil_register_library_map(array( 'PhameBlogArchiveController' => 'PhameBlogController', 'PhameBlogController' => 'PhameController', 'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability', + 'PhameBlogEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhameBlogEditController' => 'PhameBlogController', 'PhameBlogEditEngine' => 'PhabricatorEditEngine', 'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/conduit/parametertype/ConduitStringParameterType.php b/src/applications/conduit/parametertype/ConduitStringParameterType.php index cb4e71ff7e..b93490fc73 100644 --- a/src/applications/conduit/parametertype/ConduitStringParameterType.php +++ b/src/applications/conduit/parametertype/ConduitStringParameterType.php @@ -1,7 +1,7 @@ Date: Mon, 28 Dec 2015 03:58:43 -0800 Subject: [PATCH 16/83] Add phame.blog.search Conduit API endpoint Summary: Ref T9897. Adds basic blog query support. Test Plan: Ran some queries. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9897 Differential Revision: https://secure.phabricator.com/D14900 --- src/__phutil_library_map__.php | 3 ++ .../PhameBlogSearchConduitAPIMethod.php | 18 ++++++++++ src/applications/phame/storage/PhameBlog.php | 36 ++++++++++++++++++- .../PhabricatorSearchEngineAPIMethod.php | 4 +++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4898e6874c..19f1e74f05 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3413,6 +3413,7 @@ phutil_register_library_map(array( 'PhameBlogProfilePictureController' => 'applications/phame/controller/blog/PhameBlogProfilePictureController.php', 'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php', 'PhameBlogReplyHandler' => 'applications/phame/mail/PhameBlogReplyHandler.php', + 'PhameBlogSearchConduitAPIMethod' => 'applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php', 'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php', 'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php', 'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php', @@ -7846,6 +7847,7 @@ phutil_register_library_map(array( 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorConduitResultInterface', ), 'PhameBlogArchiveController' => 'PhameBlogController', 'PhameBlogController' => 'PhameController', @@ -7861,6 +7863,7 @@ phutil_register_library_map(array( 'PhameBlogProfilePictureController' => 'PhameBlogController', 'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhameBlogReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'PhameBlogSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhameBlogSite' => 'PhameSite', 'PhameBlogTransaction' => 'PhabricatorApplicationTransaction', diff --git a/src/applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php b/src/applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php new file mode 100644 index 0000000000..e122f8a7a3 --- /dev/null +++ b/src/applications/phame/conduit/PhameBlogSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +setKey('name') + ->setType('string') + ->setDescription(pht('The name of the blog.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('description') + ->setType('string') + ->setDescription(pht('Blog description.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('string') + ->setDescription(pht('Archived or active status.')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'name' => $this->getName(), + 'description' => $this->getDescription(), + 'status' => $this->getStatus(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php index 998a77126b..f411c208bf 100644 --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -15,6 +15,10 @@ abstract class PhabricatorSearchEngineAPIMethod } } + if (!$maps) { + $maps = (object)$maps; + } + return $maps; } From 00f1389f72db4d08065940220b47031c75edabbb Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Dec 2015 04:25:01 -0800 Subject: [PATCH 17/83] Add phame.post.search Conduit API endpoint Summary: Ref T9897. Mostly straightforward, but also modernize/fixup the Query a little so that posts never load with no blog. Test Plan: Queried posts via API. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9897 Differential Revision: https://secure.phabricator.com/D14901 --- src/__phutil_library_map__.php | 3 + .../PhamePostSearchConduitAPIMethod.php | 18 ++++ .../phame/query/PhamePostQuery.php | 64 +++++++-------- .../phame/query/PhamePostSearchEngine.php | 21 ++--- src/applications/phame/storage/PhamePost.php | 82 ++++++++++++++----- 5 files changed, 125 insertions(+), 63 deletions(-) create mode 100644 src/applications/phame/conduit/PhamePostSearchConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 19f1e74f05..b43ecd7be5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3440,6 +3440,7 @@ phutil_register_library_map(array( 'PhamePostPublishController' => 'applications/phame/controller/post/PhamePostPublishController.php', 'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php', 'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php', + 'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php', 'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php', 'PhamePostTransaction' => 'applications/phame/storage/PhamePostTransaction.php', 'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php', @@ -7886,6 +7887,7 @@ phutil_register_library_map(array( 'PhabricatorSubscribableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorTokenReceiverInterface', + 'PhabricatorConduitResultInterface', ), 'PhamePostCommentController' => 'PhamePostController', 'PhamePostController' => 'PhameController', @@ -7900,6 +7902,7 @@ phutil_register_library_map(array( 'PhamePostPublishController' => 'PhamePostController', 'PhamePostQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', + 'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhamePostTransaction' => 'PhabricatorApplicationTransaction', 'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment', diff --git a/src/applications/phame/conduit/PhamePostSearchConduitAPIMethod.php b/src/applications/phame/conduit/PhamePostSearchConduitAPIMethod.php new file mode 100644 index 0000000000..3dce92b6cf --- /dev/null +++ b/src/applications/phame/conduit/PhamePostSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +establishConnection('r'); + return $this->loadStandardPage($this->newResultObject()); + } - $where_clause = $this->buildWhereClause($conn_r); - $order_clause = $this->buildOrderClause($conn_r); - $limit_clause = $this->buildLimitClause($conn_r); + protected function willFilterPage(array $posts) { + // We require blogs to do visibility checks, so load them unconditionally. + $blog_phids = mpull($posts, 'getBlogPHID'); - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T p %Q %Q %Q', - $table->getTableName(), - $where_clause, - $order_clause, - $limit_clause); + $blogs = id(new PhameBlogQuery()) + ->setViewer($this->getViewer()) + ->needProfileImage(true) + ->withPHIDs($blog_phids) + ->execute(); - $posts = $table->loadAllFromArray($data); + $blogs = mpull($blogs, null, 'getPHID'); + foreach ($posts as $key => $post) { + $blog_phid = $post->getBlogPHID(); - if ($posts) { - // We require these to do visibility checks, so load them unconditionally. - $blog_phids = mpull($posts, 'getBlogPHID'); - $blogs = id(new PhameBlogQuery()) - ->setViewer($this->getViewer()) - ->needProfileImage(true) - ->withPHIDs($blog_phids) - ->execute(); - $blogs = mpull($blogs, null, 'getPHID'); - foreach ($posts as $post) { - if (isset($blogs[$post->getBlogPHID()])) { - $post->setBlog($blogs[$post->getBlogPHID()]); - } + $blog = idx($blogs, $blog_phid); + if (!$blog) { + $this->didRejectResult($post); + unset($posts[$key]); + continue; } + + $post->attachBlog($blog); } return $posts; @@ -82,42 +80,42 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery { if ($this->ids) { $where[] = qsprintf( $conn, - 'p.id IN (%Ld)', + 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn, - 'p.phid IN (%Ls)', + 'phid IN (%Ls)', $this->phids); } if ($this->bloggerPHIDs) { $where[] = qsprintf( $conn, - 'p.bloggerPHID IN (%Ls)', + 'bloggerPHID IN (%Ls)', $this->bloggerPHIDs); } if ($this->visibility !== null) { $where[] = qsprintf( $conn, - 'p.visibility = %d', + 'visibility = %d', $this->visibility); } if ($this->publishedAfter !== null) { $where[] = qsprintf( $conn, - 'p.datePublished > %d', + 'datePublished > %d', $this->publishedAfter); } - if ($this->blogPHIDs) { + if ($this->blogPHIDs !== null) { $where[] = qsprintf( $conn, - 'p.blogPHID in (%Ls)', + 'blogPHID in (%Ls)', $this->blogPHIDs); } diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index 5002c84e0d..a7a331fbdf 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -30,10 +30,11 @@ final class PhamePostSearchEngine id(new PhabricatorSearchSelectField()) ->setKey('visibility') ->setLabel(pht('Visibility')) - ->setOptions(array( - '' => pht('All'), - PhameConstants::VISIBILITY_PUBLISHED => pht('Published'), - PhameConstants::VISIBILITY_DRAFT => pht('Draft'), + ->setOptions( + array( + '' => pht('All'), + PhameConstants::VISIBILITY_PUBLISHED => pht('Published'), + PhameConstants::VISIBILITY_DRAFT => pht('Draft'), )), ); } @@ -68,6 +69,8 @@ final class PhamePostSearchEngine return parent::buildSavedQueryFromBuiltin($query_key); } + + protected function renderResultList( array $posts, PhabricatorSavedQuery $query, @@ -82,12 +85,10 @@ final class PhamePostSearchEngine foreach ($posts as $post) { $id = $post->getID(); $blog = $post->getBlog(); - if ($blog) { - $blog_name = $viewer->renderHandle($post->getBlogPHID())->render(); - $blog_name = pht('Blog: %s', $blog_name); - } else { - $blog_name = pht('[No Blog]'); - } + + $blog_name = $viewer->renderHandle($post->getBlogPHID())->render(); + $blog_name = pht('Blog: %s', $blog_name); + $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($post) diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 94e9c63f4a..57519bd2f1 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -9,7 +9,8 @@ final class PhamePost extends PhameDAO PhabricatorApplicationTransactionInterface, PhabricatorSubscribableInterface, PhabricatorDestructibleInterface, - PhabricatorTokenReceiverInterface { + PhabricatorTokenReceiverInterface, + PhabricatorConduitResultInterface { const MARKUP_FIELD_BODY = 'markup:body'; const MARKUP_FIELD_SUMMARY = 'markup:summary'; @@ -24,7 +25,7 @@ final class PhamePost extends PhameDAO protected $blogPHID; protected $mailKey; - private $blog; + private $blog = self::ATTACHABLE; public static function initializePost( PhabricatorUser $blogger, @@ -33,19 +34,20 @@ final class PhamePost extends PhameDAO $post = id(new PhamePost()) ->setBloggerPHID($blogger->getPHID()) ->setBlogPHID($blog->getPHID()) - ->setBlog($blog) + ->attachBlog($blog) ->setDatePublished(PhabricatorTime::getNow()) ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); + return $post; } - public function setBlog(PhameBlog $blog) { + public function attachBlog(PhameBlog $blog) { $this->blog = $blog; return $this; } public function getBlog() { - return $this->blog; + return $this->assertAttached($this->blog); } public function getLiveURI() { @@ -146,21 +148,6 @@ final class PhamePost extends PhameDAO return PhabricatorSlug::normalizeProjectSlug($this->getTitle(), true); } - public function toDictionary() { - return array( - 'id' => $this->getID(), - 'phid' => $this->getPHID(), - 'blogPHID' => $this->getBlogPHID(), - 'bloggerPHID' => $this->getBloggerPHID(), - 'viewURI' => $this->getViewURI(), - 'title' => $this->getTitle(), - 'body' => $this->getBody(), - 'summary' => PhabricatorMarkupEngine::summarize($this->getBody()), - 'datePublished' => $this->getDatePublished(), - 'published' => !$this->isDraft(), - ); - } - /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ @@ -303,4 +290,59 @@ final class PhamePost extends PhameDAO return true; } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('title') + ->setType('string') + ->setDescription(pht('Title of the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('slug') + ->setType('string') + ->setDescription(pht('Slug for the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('blogPHID') + ->setType('phid') + ->setDescription(pht('PHID of the blog that the post belongs to.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('authorPHID') + ->setType('phid') + ->setDescription(pht('PHID of the author of the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('body') + ->setType('string') + ->setDescription(pht('Body of the post.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('datePublished') + ->setType('epoch?') + ->setDescription(pht('Publish date, if the post has been published.')), + + ); + } + + public function getFieldValuesForConduit() { + if ($this->isDraft()) { + $date_published = null; + } else { + $date_published = (int)$this->getDatePublished(); + } + + return array( + 'title' => $this->getTitle(), + 'slug' => $this->getSlug(), + 'blogPHID' => $this->getBlogPHID(), + 'authorPHID' => $this->getBloggerPHID(), + 'body' => $this->getBody(), + 'datePublished' => $date_published, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + } From e0a97c88dbecf6872afd2072bf2d17d7ba495cbc Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Dec 2015 05:46:22 -0800 Subject: [PATCH 18/83] Provide phame.post.edit Conduit API method Summary: Ref T9897. This one is a little more involved because of how getting a post on a blog works. I also changed moving posts to be a real transaction (which shows up in history, now). Test Plan: Created posts from web UI and conduit. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9897 Differential Revision: https://secure.phabricator.com/D14902 --- src/__phutil_library_map__.php | 4 +- .../ConduitPHIDParameterType.php | 2 +- .../conduit/PhamePostEditConduitAPIMethod.php | 18 +++++++ .../post/PhamePostEditController.php | 28 +++++++++- .../post/PhamePostMoveController.php | 48 ++++++++--------- .../phame/editor/PhamePostEditEngine.php | 24 ++++++--- .../phame/editor/PhamePostEditor.php | 54 +++++++++++++++++++ .../phame/storage/PhamePostTransaction.php | 40 ++++++++++++++ .../editengine/PhabricatorEditEngine.php | 11 +++- .../PhabricatorPHIDListEditField.php | 6 ++- 10 files changed, 197 insertions(+), 38 deletions(-) create mode 100644 src/applications/phame/conduit/PhamePostEditConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b43ecd7be5..00c9712668 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3429,6 +3429,7 @@ phutil_register_library_map(array( 'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', + 'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', @@ -4159,7 +4160,7 @@ phutil_register_library_map(array( 'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException', 'ConduitMethodNotFoundException' => 'ConduitException', 'ConduitPHIDListParameterType' => 'ConduitListParameterType', - 'ConduitPHIDParameterType' => 'ConduitListParameterType', + 'ConduitPHIDParameterType' => 'ConduitParameterType', 'ConduitParameterType' => 'Phobject', 'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitProjectListParameterType' => 'ConduitListParameterType', @@ -7891,6 +7892,7 @@ phutil_register_library_map(array( ), 'PhamePostCommentController' => 'PhamePostController', 'PhamePostController' => 'PhameController', + 'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhamePostEditController' => 'PhamePostController', 'PhamePostEditEngine' => 'PhabricatorEditEngine', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/conduit/parametertype/ConduitPHIDParameterType.php b/src/applications/conduit/parametertype/ConduitPHIDParameterType.php index c10aa2a791..f182758071 100644 --- a/src/applications/conduit/parametertype/ConduitPHIDParameterType.php +++ b/src/applications/conduit/parametertype/ConduitPHIDParameterType.php @@ -1,7 +1,7 @@ blog = $blog; + return $this; + } + + public function getBlog() { + return $this->blog; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); @@ -12,6 +23,7 @@ final class PhamePostEditController extends PhamePostController { ->withIDs(array($id)) ->requireCapabilities( array( + PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); @@ -32,15 +44,29 @@ final class PhamePostEditController extends PhamePostController { PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); - if (!$blog) { return new Aphront404Response(); } + $this->setBlog($blog); + return id(new PhamePostEditEngine()) ->setController($this) ->setBlog($blog) ->buildResponse(); } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $blog = $this->getBlog(); + + $crumbs->addTextCrumb( + $blog->getName(), + $blog->getViewURI()); + + return $crumbs; + } + + } diff --git a/src/applications/phame/controller/post/PhamePostMoveController.php b/src/applications/phame/controller/post/PhamePostMoveController.php index 3ce2586e58..3e09a9ba2d 100644 --- a/src/applications/phame/controller/post/PhamePostMoveController.php +++ b/src/applications/phame/controller/post/PhamePostMoveController.php @@ -20,59 +20,57 @@ final class PhamePostMoveController extends PhamePostController { return new Aphront404Response(); } - $view_uri = '/post/view/'.$post->getID().'/'; - $view_uri = $this->getApplicationURI($view_uri); + $view_uri = $post->getViewURI(); + $v_blog = $post->getBlog()->getPHID(); if ($request->isFormPost()) { - $blog = id(new PhameBlogQuery()) - ->setViewer($viewer) - ->withIDs(array($request->getInt('blog'))) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); + $v_blog = $request->getStr('blogPHID'); - if ($blog) { - $post->setBlogPHID($blog->getPHID()); - $post->save(); + $xactions = array(); + $xactions[] = id(new PhamePostTransaction()) + ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setNewValue($v_blog); - return id(new AphrontRedirectResponse()) - ->setURI($view_uri.'?moved=1'); - } + $editor = id(new PhamePostEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($post, $xactions); + + $view_uri = $post->getViewURI(); + + return id(new AphrontRedirectResponse()) + ->setURI($view_uri.'?moved=1'); } $blogs = id(new PhameBlogQuery()) ->setViewer($viewer) ->requireCapabilities( array( + PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); - $options = mpull($blogs, 'getName', 'getID'); + $options = mpull($blogs, 'getName', 'getPHID'); asort($options); - $selected_value = null; - if ($post && $post->getBlog()) { - $selected_value = $post->getBlog()->getID(); - } - $form = id(new PHUIFormLayoutView()) ->setUser($viewer) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Blog')) - ->setName('blog') + ->setName('blogPHID') ->setOptions($options) - ->setValue($selected_value)); + ->setValue($v_blog)); return $this->newDialog() ->setTitle(pht('Move Post')) ->appendChild($form) ->addSubmitButton(pht('Move Post')) ->addCancelButton($view_uri); - } } diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index 22e4f50922..652922ea56 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -65,16 +65,24 @@ final class PhamePostEditEngine } protected function buildCustomEditFields($object) { - - if ($this->blog) { - $blog_title = pht('Blog: %s', $this->blog->getName()); - } else { - $blog_title = pht('Sample Blog Title'); - } + $blog_phid = $object->getBlog()->getPHID(); return array( - id(new PhabricatorInstructionsEditField()) - ->setValue($blog_title), + id(new PhabricatorHandlesEditField()) + ->setKey('blog') + ->setLabel(pht('Blog')) + ->setDescription(pht('Blog to publish this post to.')) + ->setConduitDescription( + pht('Choose a blog to create a post on (or move a post to).')) + ->setConduitTypeDescription(pht('PHID of the blog.')) + ->setAliases(array('blogPHID')) + ->setTransactionType(PhamePostTransaction::TYPE_BLOG) + ->setHandleParameterType(new AphrontPHIDListHTTPParameterType()) + ->setSingleValue($blog_phid) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false) + ->setIsLocked(true), id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 4ca1710340..8978be89b6 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -14,6 +14,7 @@ final class PhamePostEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); + $types[] = PhamePostTransaction::TYPE_BLOG; $types[] = PhamePostTransaction::TYPE_TITLE; $types[] = PhamePostTransaction::TYPE_BODY; $types[] = PhamePostTransaction::TYPE_VISIBILITY; @@ -27,6 +28,8 @@ final class PhamePostEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhamePostTransaction::TYPE_BLOG: + return $object->getBlogPHID(); case PhamePostTransaction::TYPE_TITLE: return $object->getTitle(); case PhamePostTransaction::TYPE_BODY: @@ -44,6 +47,7 @@ final class PhamePostEditor case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: + case PhamePostTransaction::TYPE_BLOG: return $xaction->getNewValue(); } } @@ -57,6 +61,8 @@ final class PhamePostEditor return $object->setTitle($xaction->getNewValue()); case PhamePostTransaction::TYPE_BODY: return $object->setBody($xaction->getNewValue()); + case PhamePostTransaction::TYPE_BLOG: + return $object->setBlogPHID($xaction->getNewValue()); case PhamePostTransaction::TYPE_VISIBILITY: if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { $object->setDatePublished(0); @@ -77,6 +83,7 @@ final class PhamePostEditor case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: + case PhamePostTransaction::TYPE_BLOG: return; } @@ -106,6 +113,53 @@ final class PhamePostEditor $error->setIsMissingFieldError(true); $errors[] = $error; } + break; + case PhamePostTransaction::TYPE_BLOG: + if ($this->getIsNewObject()) { + if (!$xactions) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht( + 'When creating a post, you must specify which blog it '. + 'should belong to.'), + null); + + $error->setIsMissingFieldError(true); + + $errors[] = $error; + break; + } + } + + foreach ($xactions as $xaction) { + $new_phid = $xaction->getNewValue(); + + $blog = id(new PhameBlogQuery()) + ->setViewer($this->getActor()) + ->withPHIDs(array($new_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->execute(); + + if ($blog) { + continue; + } + + $errors[] = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'The specified blog PHID ("%s") is not valid. You can only '. + 'create a post on (or move a post into) a blog which you '. + 'have permission to see and edit.', + $new_phid), + $xaction); + } + break; } return $errors; diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index ed341e17db..fcdb7265f8 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -7,6 +7,7 @@ final class PhamePostTransaction const TYPE_PHAME_TITLE = 'phame.post.phame.title'; const TYPE_BODY = 'phame.post.body'; const TYPE_VISIBILITY = 'phame.post.visibility'; + const TYPE_BLOG = 'phame.post.blog'; const MAILTAG_CONTENT = 'phame-post-content'; const MAILTAG_SUBSCRIBERS = 'phame-post-subscribers'; @@ -47,6 +48,28 @@ final class PhamePostTransaction return parent::shouldHide(); } + public function getRequiredHandlePHIDs() { + $phids = parent::getRequiredHandlePHIDs(); + + switch ($this->getTransactionType()) { + case self::TYPE_BLOG: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if ($old) { + $phids[] = $old; + } + + if ($new) { + $phids[] = $new; + } + break; + } + + return $phids; + } + + public function getIcon() { $old = $this->getOldValue(); switch ($this->getTransactionType()) { @@ -98,6 +121,16 @@ final class PhamePostTransaction $type = $this->getTransactionType(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this post.', + $this->renderHandleLink($author_phid)); + case self::TYPE_BLOG: + return pht( + '%s moved this post from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); case self::TYPE_TITLE: if ($old === null) { return pht( @@ -146,6 +179,13 @@ final class PhamePostTransaction $type = $this->getTransactionType(); switch ($type) { + case self::TYPE_BLOG: + return pht( + '%s moved post "%s" from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); case self::TYPE_TITLE: if ($old === null) { return pht( diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 041bb05393..e44a64f66e 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1688,7 +1688,16 @@ abstract class PhabricatorEditEngine // Let the parameter type interpret the value. This allows you to // use usernames in list fields, for example. $parameter_type = $type->getConduitParameterType(); - $xaction['value'] = $parameter_type->getValue($xaction, 'value'); + + try { + $xaction['value'] = $parameter_type->getValue($xaction, 'value'); + } catch (Exception $ex) { + throw new PhutilProxyException( + pht( + 'Exception when processing transaction of type "%s".', + $xaction['type']), + $ex); + } $type_xactions = $type->generateTransactions( clone $template, diff --git a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php index b0eee2d465..4968aef5b2 100644 --- a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php +++ b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php @@ -35,7 +35,11 @@ abstract class PhabricatorPHIDListEditField } protected function newConduitParameterType() { - return new ConduitPHIDListParameterType(); + if ($this->getIsSingleValue()) { + return new ConduitPHIDParameterType(); + } else { + return new ConduitPHIDListParameterType(); + } } protected function getValueFromRequest(AphrontRequest $request, $key) { From abd60eeee0e697090168b7ba59fd57ffd9c8431b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Dec 2015 06:30:25 -0800 Subject: [PATCH 19/83] Rough data fetch for previous/next posts on a blog Summary: Ref T9897. Not pretty, but pulls data. Test Plan: {F1046464} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9897 Differential Revision: https://secure.phabricator.com/D14903 --- .../post/PhamePostViewController.php | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 240f8f3a69..0472db20bd 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -120,15 +120,29 @@ final class PhamePostViewController $add_comment = phutil_tag_div('mlb mlt', $add_comment); } + list($prev, $next) = $this->loadAdjacentPosts($post); + $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($post); + if ($next) { + $properties->addProperty( + pht('Later Posts'), + $viewer->renderHandleList(mpull($next, 'getPHID'))); + } + + if ($prev) { + $properties->addProperty( + pht('Earlier Posts'), + $viewer->renderHandleList(mpull($prev, 'getPHID'))); + } + $properties->invokeWillRenderEvent(); $crumbs = $this->buildApplicationCrumbs(); - $page = $this->newPage() + $page = $this->newPage() ->setTitle($post->getTitle()) ->setPageObjectPHIDs(array($post->getPHID())) ->setCrumbs($crumbs) @@ -236,4 +250,24 @@ final class PhamePostViewController return phutil_tag_div('phui-document-view-pro-box', $box); } + private function loadAdjacentPosts(PhamePost $post) { + $viewer = $this->getViewer(); + + $query = id(new PhamePostQuery()) + ->setViewer($viewer) + ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) + ->withBlogPHIDs(array($post->getBlog()->getPHID())) + ->setLimit(2); + + $prev = id(clone $query) + ->setAfterID($post->getID()) + ->execute(); + + $next = id(clone $query) + ->setBeforeID($post->getID()) + ->execute(); + + return array($prev, $next); + } + } From 33384abff78a77876dc687659311ab972304787a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Dec 2015 12:59:38 -0800 Subject: [PATCH 20/83] Fix an exception in Tokens if a bad object was given a token Summary: Fixes T10057. Root issue is: - In the past, you could give tokens to objects of type X (here, Ponder answers). - Now, you can't. - If you try to load a token on an object of type X, we do a bad call to attach it and fatal. Instead, make sure objects implement the proper interface before we attach them, and just pretend the token does not exist otherwise. Test Plan: Faked the exception in T10057, applied patch, got clean tokens page. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10057 Differential Revision: https://secure.phabricator.com/D14905 --- .../query/PhabricatorTokenGivenQuery.php | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php index c16eaa2168..224efff198 100644 --- a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php @@ -22,68 +22,62 @@ final class PhabricatorTokenGivenQuery return $this; } - protected function loadPage() { - $table = new PhabricatorTokenGiven(); - $conn_r = $table->establishConnection('r'); - - $rows = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($rows); + public function newResultObject() { + return new PhabricatorTokenGiven(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } - if ($this->authorPHIDs) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'authorPHID IN (%Ls)', $this->authorPHIDs); } - if ($this->objectPHIDs) { + if ($this->objectPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } - if ($this->tokenPHIDs) { + if ($this->tokenPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'tokenPHID IN (%Ls)', $this->tokenPHIDs); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function willFilterPage(array $results) { - $object_phids = array_filter(mpull($results, 'getObjectPHID')); - if (!$object_phids) { - return array(); - } + $object_phids = mpull($results, 'getObjectPHID'); $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($object_phids) ->execute(); + $objects = mpull($objects, null, 'getPHID'); foreach ($results as $key => $result) { - $phid = $result->getObjectPHID(); - if (empty($objects[$phid])) { - unset($results[$key]); - } else { - $result->attachObject($objects[$phid]); + $object = idx($objects, $result->getObjectPHID()); + + if ($object) { + if ($object instanceof PhabricatorTokenReceiverInterface) { + $result->attachObject($object); + continue; + } } + + $this->didRejectResult($result); + unset($results[$key]); } return $results; From 1443e4b13dd42bed22dc4480e071d37cd53dee55 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Dec 2015 13:30:07 -0800 Subject: [PATCH 21/83] Fix an excessively aggressive transaction check in Owners Summary: Fixes T10058. We don't need to continue on this check if no path changes are being applied. Test Plan: Archived an owners package. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10058 Differential Revision: https://secure.phabricator.com/D14906 --- .../editor/PhabricatorOwnersPackageTransactionEditor.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 539bdffa62..f2cfc5151a 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -204,6 +204,10 @@ final class PhabricatorOwnersPackageTransactionEditor } break; case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + if (!$xactions) { + continue; + } + $old = mpull($object->getPaths(), 'getRef'); foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); From 7732f9c03c84e60aa44e76efbdb28b0634ad43de Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 29 Dec 2015 15:24:06 +0000 Subject: [PATCH 22/83] Fix bad query on PhameHome with no Blogs Summary: We're checking for drafts even though we already know there are no blogs, just skip the query. Test Plan: trucate phame_blogs; See proper blank state. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14908 --- src/applications/phame/controller/PhameHomeController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index 75051acef6..4028f98e50 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -85,7 +85,7 @@ final class PhameHomeController extends PhamePostController { ->setViewer($viewer); $draft_list = null; - if ($viewer->isLoggedIn()) { + if ($viewer->isLoggedIn() && $blogs) { $drafts = id(new PhamePostQuery()) ->setViewer($viewer) ->withBloggerPHIDs(array($viewer->getPHID())) From 7c5ad63fd1d613a62727624ec31436d7933b0bbd Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 27 Dec 2015 05:16:36 -0800 Subject: [PATCH 23/83] Add very basic UI for creating milestones and subprojects Summary: Ref T10010. This has a lot of UI/UX problems but I think it: - technically allows subproject creation; - technically allows milestone creation; - doesn't let users unwittingly destroy their installs (probably). Test Plan: - Created milestones. - Created subprojects. - Created and edited normal projects. - Observed some reasonable interactions (e.g., you can't create milestones for a milestone or edit a superproject's members). - Observed plenty of silly/confusing interactions that need additional work. {F1046657} {F1046658} {F1046655} {F1046656} {F1046654} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14904 --- resources/celerity/map.php | 6 +- src/__phutil_library_map__.php | 2 + .../PhabricatorProjectCoreTestCase.php | 18 +- .../PhabricatorProjectController.php | 29 +++- .../PhabricatorProjectEditController.php | 107 +++++++++++- ...habricatorProjectMembersEditController.php | 4 +- ...PhabricatorProjectMilestonesController.php | 61 ++++++- ...habricatorProjectSubprojectsController.php | 60 ++++++- .../PhabricatorProjectTransactionEditor.php | 160 +++++++++++++++--- .../engine/PhabricatorProjectEditEngine.php | 84 +++++++++ .../query/PhabricatorProjectSearchEngine.php | 41 +---- .../project/storage/PhabricatorProject.php | 38 +++++ .../view/PhabricatorProjectListView.php | 53 ++++++ .../css/layout/phabricator-side-menu-view.css | 4 + 14 files changed, 583 insertions(+), 84 deletions(-) create mode 100644 src/applications/project/view/PhabricatorProjectListView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 73daecb1fd..bd9c2f2217 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'a419cf4b', + 'core.pkg.css' => '3ea6dc33', 'core.pkg.js' => '57dff7df', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -114,7 +114,7 @@ return array( 'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52', - 'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e', + 'rsrc/css/layout/phabricator-side-menu-view.css' => '91b7a42c', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', @@ -762,7 +762,7 @@ return array( 'phabricator-remarkup-css' => '7afb543c', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', - 'phabricator-side-menu-view-css' => 'bec2458e', + 'phabricator-side-menu-view-css' => '91b7a42c', 'phabricator-slowvote-css' => 'da0afb1b', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => '3c99cdf4', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 00c9712668..0e06a9198a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2855,6 +2855,7 @@ phutil_register_library_map(array( 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', + 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php', 'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php', 'PhabricatorProjectLogicalAndDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php', 'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php', @@ -7206,6 +7207,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', 'PhabricatorProjectListController' => 'PhabricatorProjectController', + 'PhabricatorProjectListView' => 'AphrontView', 'PhabricatorProjectLockController' => 'PhabricatorProjectController', 'PhabricatorProjectLogicalAndDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 76f265735c..c31c69fda9 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -748,15 +748,15 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { ->setNewValue($name); if ($parent) { - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) - ->setNewValue($parent->getPHID()); - } - - if ($is_milestone) { - $xactions[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) - ->setNewValue(true); + if ($is_milestone) { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setNewValue($parent->getPHID()); + } else { + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setNewValue($parent->getPHID()); + } } $this->applyTransactions($project, $user, $xactions); diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index c1aef3125e..ae668e451b 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -99,7 +99,6 @@ abstract class PhabricatorProjectController extends PhabricatorController { $nav->addFilter("board/{$id}/", pht('Workboard')); $nav->addFilter("members/{$id}/", pht('Members')); $nav->addFilter("feed/{$id}/", pht('Feed')); - $nav->addFilter("details/{$id}/", pht('Edit Details')); } $nav->addFilter('create', pht('Create Project')); } @@ -149,11 +148,29 @@ abstract class PhabricatorProjectController extends PhabricatorController { $nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o'); $nav->addIcon("members/{$id}/", pht('Members'), 'fa-group'); - $nav->addIcon("details/{$id}/", pht('Edit Details'), 'fa-pencil'); if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { - $nav->addIcon("subprojects/{$id}/", pht('Subprojects'), 'fa-sitemap'); - $nav->addIcon("milestones/{$id}/", pht('Milestones'), 'fa-map-marker'); + if ($project->supportsSubprojects()) { + $subprojects_icon = 'fa-sitemap'; + } else { + $subprojects_icon = 'fa-sitemap grey'; + } + + if ($project->supportsMilestones()) { + $milestones_icon = 'fa-map-marker'; + } else { + $milestones_icon = 'fa-map-marker grey'; + } + + $nav->addIcon( + "subprojects/{$id}/", + pht('Subprojects'), + $subprojects_icon); + + $nav->addIcon( + "milestones/{$id}/", + pht('Milestones'), + $milestones_icon); } @@ -170,8 +187,8 @@ abstract class PhabricatorProjectController extends PhabricatorController { $ancestors[] = $project; foreach ($ancestors as $ancestor) { $crumbs->addTextCrumb( - $project->getName(), - $project->getURI()); + $ancestor->getName(), + $ancestor->getURI()); } } diff --git a/src/applications/project/controller/PhabricatorProjectEditController.php b/src/applications/project/controller/PhabricatorProjectEditController.php index 7c2497e731..d87910462c 100644 --- a/src/applications/project/controller/PhabricatorProjectEditController.php +++ b/src/applications/project/controller/PhabricatorProjectEditController.php @@ -3,10 +3,111 @@ final class PhabricatorProjectEditController extends PhabricatorProjectController { + private $engine; + + public function setEngine(PhabricatorProjectEditEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function getEngine() { + return $this->engine; + } + public function handleRequest(AphrontRequest $request) { - return id(new PhabricatorProjectEditEngine()) - ->setController($this) - ->buildResponse(); + $viewer = $this->getViewer(); + + $engine = id(new PhabricatorProjectEditEngine()) + ->setController($this); + + $this->setEngine($engine); + + $id = $request->getURIData('id'); + if (!$id) { + $parent_id = head($request->getArr('parent')); + if (!$parent_id) { + $parent_id = $request->getStr('parent'); + } + + if ($parent_id) { + $is_milestone = false; + } else { + $parent_id = head($request->getArr('milestone')); + if (!$parent_id) { + $parent_id = $request->getStr('milestone'); + } + $is_milestone = true; + } + + if ($parent_id) { + $query = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )); + + if (ctype_digit($parent_id)) { + $query->withIDs(array($parent_id)); + } else { + $query->withPHIDs(array($parent_id)); + } + + $parent = $query->executeOne(); + + if ($is_milestone) { + if (!$parent->supportsMilestones()) { + $cancel_uri = "/project/milestones/{$parent_id}/"; + return $this->newDialog() + ->setTitle(pht('No Milestones')) + ->appendParagraph( + pht('You can not add milestones to this project.')) + ->addCancelButton($cancel_uri); + } + $engine->setMilestoneProject($parent); + } else { + if (!$parent->supportsSubprojects()) { + $cancel_uri = "/project/subprojects/{$parent_id}/"; + return $this->newDialog() + ->setTitle(pht('No Subprojects')) + ->appendParagraph( + pht('You can not add subprojects to this project.')) + ->addCancelButton($cancel_uri); + } + $engine->setParentProject($parent); + } + + $this->setProject($parent); + } + } + + return $engine->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $engine = $this->getEngine(); + if ($engine) { + $parent = $engine->getParentProject(); + if ($parent) { + $id = $parent->getID(); + $crumbs->addTextCrumb( + pht('Subprojects'), + $this->getApplicationURI("subprojects/{$id}/")); + } + + $milestone = $engine->getMilestoneProject(); + if ($milestone) { + $id = $milestone->getID(); + $crumbs->addTextCrumb( + pht('Milestones'), + $this->getApplicationURI("milestones/{$id}/")); + } + } + + return $crumbs; } } diff --git a/src/applications/project/controller/PhabricatorProjectMembersEditController.php b/src/applications/project/controller/PhabricatorProjectMembersEditController.php index b018e9389d..099443fa66 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersEditController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersEditController.php @@ -68,9 +68,11 @@ final class PhabricatorProjectMembersEditController $project, PhabricatorPolicyCapability::CAN_EDIT); + $supports_edit = $project->supportsEditMembers(); + $form_box = null; $title = pht('Add Members'); - if ($can_edit) { + if ($can_edit && $supports_edit) { $header_name = pht('Edit Members'); $view_uri = $this->getApplicationURI('profile/'.$project->getID().'/'); diff --git a/src/applications/project/controller/PhabricatorProjectMilestonesController.php b/src/applications/project/controller/PhabricatorProjectMilestonesController.php index e7ce6a7640..532e448a18 100644 --- a/src/applications/project/controller/PhabricatorProjectMilestonesController.php +++ b/src/applications/project/controller/PhabricatorProjectMilestonesController.php @@ -18,6 +18,64 @@ final class PhabricatorProjectMilestonesController $project = $this->getProject(); $id = $project->getID(); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + + $has_support = $project->supportsMilestones(); + if ($has_support) { + $milestones = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs(array($project->getPHID())) + ->needImages(true) + ->withIsMilestone(true) + ->setOrder('newest') + ->execute(); + } else { + $milestones = array(); + } + + $can_create = $can_edit && $has_support; + + if ($project->getHasMilestones()) { + $button_text = pht('Create Next Milestone'); + } else { + $button_text = pht('Add Milestones'); + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Milestones')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref("/project/edit/?milestone={$id}") + ->setIconFont('fa-plus') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create) + ->setText($button_text)); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + if (!$has_support) { + $no_support = pht( + 'This project is a milestone. Milestones can not have their own '. + 'milestones.'); + + $info_view = id(new PHUIInfoView()) + ->setErrors(array($no_support)) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + + $box->setInfoView($info_view); + } + + $box->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($milestones) + ->renderList()); + $nav = $this->buildIconNavView($project); $nav->selectFilter("milestones/{$id}/"); @@ -27,7 +85,8 @@ final class PhabricatorProjectMilestonesController return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) - ->setTitle(array($project->getName(), pht('Milestones'))); + ->setTitle(array($project->getName(), pht('Milestones'))) + ->appendChild($box); } } diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index e4014aaf49..a75a9ebc5f 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -18,6 +18,63 @@ final class PhabricatorProjectSubprojectsController $project = $this->getProject(); $id = $project->getID(); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + + $has_support = $project->supportsSubprojects(); + + if ($has_support) { + $subprojects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs(array($project->getPHID())) + ->needImages(true) + ->withIsMilestone(false) + ->execute(); + } else { + $subprojects = array(); + } + + $can_create = $can_edit && $has_support; + + if ($project->getHasSubprojects()) { + $button_text = pht('Create Subproject'); + } else { + $button_text = pht('Add Subprojects'); + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Subprojects')) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setHref("/project/edit/?parent={$id}") + ->setIconFont('fa-plus') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create) + ->setText($button_text)); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + if (!$has_support) { + $no_support = pht( + 'This project is a milestone. Milestones can not have subprojects.'); + + $info_view = id(new PHUIInfoView()) + ->setErrors(array($no_support)) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING); + + $box->setInfoView($info_view); + } + + $box->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($subprojects) + ->renderList()); + $nav = $this->buildIconNavView($project); $nav->selectFilter("subprojects/{$id}/"); @@ -27,7 +84,8 @@ final class PhabricatorProjectSubprojectsController return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) - ->setTitle(array($project->getName(), pht('Subprojects'))); + ->setTitle(array($project->getName(), pht('Subprojects'))) + ->appendChild($box); } } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index a5ef9b840d..4e5d61ce54 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -74,23 +74,10 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_COLOR: case PhabricatorProjectTransaction::TYPE_LOCKED: case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: return $xaction->getNewValue(); case PhabricatorProjectTransaction::TYPE_SLUGS: return $this->normalizeSlugs($xaction->getNewValue()); - case PhabricatorProjectTransaction::TYPE_MILESTONE: - $current = queryfx_one( - $object->establishConnection('w'), - 'SELECT MAX(milestoneNumber) n - FROM %T - WHERE parentProjectPHID = %s', - $object->getTableName(), - $object->getParentProject()->getPHID()); - if (!$current) { - $number = 1; - } else { - $number = (int)$current['n'] + 1; - } - return $number; } return parent::getCustomTransactionNewValue($object, $xaction); @@ -127,7 +114,21 @@ final class PhabricatorProjectTransactionEditor $object->setParentProjectPHID($xaction->getNewValue()); return; case PhabricatorProjectTransaction::TYPE_MILESTONE: - $object->setMilestoneNumber($xaction->getNewValue()); + $current = queryfx_one( + $object->establishConnection('w'), + 'SELECT MAX(milestoneNumber) n + FROM %T + WHERE parentProjectPHID = %s', + $object->getTableName(), + $object->getParentProject()->getPHID()); + if (!$current) { + $number = 1; + } else { + $number = (int)$current['n'] + 1; + } + + $object->setMilestoneNumber($number); + $object->setParentProjectPHID($xaction->getNewValue()); return; } @@ -239,6 +240,84 @@ final class PhabricatorProjectTransactionEditor return parent::applyBuiltinExternalTransaction($object, $xaction); } + protected function validateAllTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + $errors = array(); + + // Prevent creating projects which are both subprojects and milestones, + // since this does not make sense, won't work, and will break everything. + $parent_xaction = null; + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: + if ($xaction->getNewValue() === null) { + continue; + } + + if (!$parent_xaction) { + $parent_xaction = $xaction; + continue; + } + + $errors[] = new PhabricatorApplicationTransactionValidationError( + $xaction->getTransactionType(), + pht('Invalid'), + pht( + 'When creating a project, specify a maximum of one parent '. + 'project or milestone project. A project can not be both a '. + 'subproject and a milestone.'), + $xaction); + break; + break; + } + } + + $is_milestone = $object->isMilestone(); + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorProjectTransaction::TYPE_MILESTONE: + if ($xaction->getNewValue() !== null) { + $is_milestone = true; + } + break; + } + } + + $is_parent = $object->getHasSubprojects(); + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorProjectTransaction::TYPE_MEMBERS: + if ($is_parent) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $xaction->getTransactionType(), + pht('Invalid'), + pht( + 'You can not change members of a project with subprojects '. + 'directly. Members of any subproject are automatically '. + 'members of the parent project.'), + $xaction); + } + + if ($is_milestone) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + $xaction->getTransactionType(), + pht('Invalid'), + pht( + 'You can not change members of a milestone. Members of the '. + 'parent project are automatically members of the milestone.'), + $xaction); + } + break; + } + } + + return $errors; + } + protected function validateTransaction( PhabricatorLiskDAO $object, $type, @@ -367,25 +446,29 @@ final class PhabricatorProjectTransactionEditor break; case PhabricatorProjectTransaction::TYPE_PARENT: + case PhabricatorProjectTransaction::TYPE_MILESTONE: if (!$xactions) { break; } $xaction = last($xactions); + $parent_phid = $xaction->getNewValue(); + if (!$parent_phid) { + continue; + } + if (!$this->getIsNewObject()) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( - 'You can only set a parent project when creating a project '. - 'for the first time.'), + 'You can only set a parent or milestone project when creating a '. + 'project for the first time.'), $xaction); break; } - $parent_phid = $xaction->getNewValue(); - $projects = id(new PhabricatorProjectQuery()) ->setViewer($this->requireActor()) ->withPHIDs(array($parent_phid)) @@ -400,8 +483,8 @@ final class PhabricatorProjectTransactionEditor $type, pht('Invalid'), pht( - 'Parent project PHID ("%s") must be the PHID of a valid, '. - 'visible project which you have permission to edit.', + 'Parent or milestone project PHID ("%s") must be the PHID of a '. + 'valid, visible project which you have permission to edit.', $parent_phid), $xaction); break; @@ -414,8 +497,8 @@ final class PhabricatorProjectTransactionEditor $type, pht('Invalid'), pht( - 'Parent project PHID ("%s") must not be a milestone. '. - 'Milestones may not have subprojects.', + 'Parent or milestone project PHID ("%s") must not be a '. + 'milestone. Milestones may not have subprojects or milestones.', $parent_phid), $xaction); break; @@ -427,9 +510,9 @@ final class PhabricatorProjectTransactionEditor $type, pht('Invalid'), pht( - 'You can not create a subproject under this parent because '. - 'it would nest projects too deeply. The maximum nesting '. - 'depth of projects is %s.', + 'You can not create a subproject or mielstone under this parent '. + 'because it would nest projects too deeply. The maximum '. + 'nesting depth of projects is %s.', new PhutilNumber($limit)), $xaction); break; @@ -611,6 +694,7 @@ final class PhabricatorProjectTransactionEditor array $xactions) { $materialize = false; + $new_parent = null; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_EDGE: @@ -622,10 +706,34 @@ final class PhabricatorProjectTransactionEditor break; case PhabricatorProjectTransaction::TYPE_PARENT: $materialize = true; + $new_parent = $object->getParentProject(); break; } } + if ($new_parent) { + // If we just created the first subproject of this parent, we want to + // copy all of the real members to the subproject. + if (!$new_parent->getHasSubprojects()) { + $member_type = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; + + $project_members = PhabricatorEdgeQuery::loadDestinationPHIDs( + $new_parent->getPHID(), + $member_type); + + if ($project_members) { + $editor = id(new PhabricatorEdgeEditor()); + foreach ($project_members as $phid) { + $editor->addEdge($object->getPHID(), $member_type, $phid); + } + $editor->save(); + } + } + } + + // TODO: We should dump an informational transaction onto the parent + // project to show that we created the sub-thing. + if ($materialize) { id(new PhabricatorProjectsMembershipIndexEngineExtension()) ->rematerialize($object); diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 976a65b78c..c90ad6935d 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -5,6 +5,27 @@ final class PhabricatorProjectEditEngine const ENGINECONST = 'projects.project'; + private $parentProject; + private $milestoneProject; + + public function setParentProject(PhabricatorProject $parent_project) { + $this->parentProject = $parent_project; + return $this; + } + + public function getParentProject() { + return $this->parentProject; + } + + public function setMilestoneProject(PhabricatorProject $milestone_project) { + $this->milestoneProject = $milestone_project; + return $this; + } + + public function getMilestoneProject() { + return $this->milestoneProject; + } + public function getEngineName() { return pht('Projects'); } @@ -50,6 +71,22 @@ final class PhabricatorProjectEditEngine return $object->getURI(); } + protected function getObjectCreateCancelURI($object) { + $parent = $this->getParentProject(); + if ($parent) { + $id = $parent->getID(); + return "/project/subprojects/{$id}/"; + } + + $milestone = $this->getMilestoneProject(); + if ($milestone) { + $id = $milestone->getID(); + return "/project/milestones/{$id}/"; + } + + return parent::getObjectCreateCancelURI($object); + } + protected function getCreateNewObjectPolicy() { return $this->getApplication()->getPolicy( ProjectCreateProjectsCapability::CAPABILITY); @@ -65,6 +102,8 @@ final class PhabricatorProjectEditEngine $configuration ->setFieldOrder( array( + 'parent', + 'milestone', 'name', 'std:project:internal:description', 'icon', @@ -84,7 +123,52 @@ final class PhabricatorProjectEditEngine unset($slugs[$object->getPrimarySlug()]); $slugs = array_values($slugs); + $milestone = $this->getMilestoneProject(); + $parent = $this->getParentProject(); + + if ($parent) { + $parent_phid = $parent->getPHID(); + } else { + $parent_phid = null; + } + + if ($milestone) { + $milestone_phid = $milestone->getPHID(); + } else { + $milestone_phid = null; + } + return array( + id(new PhabricatorHandlesEditField()) + ->setKey('parent') + ->setLabel(pht('Parent')) + ->setDescription(pht('Create a subproject of an existing project.')) + ->setConduitDescription( + pht('Choose a parent project to create a subproject beneath.')) + ->setConduitTypeDescription(pht('PHID of the parent project.')) + ->setAliases(array('parentPHID')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_PARENT) + ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) + ->setSingleValue($parent_phid) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false) + ->setIsLocked(true), + id(new PhabricatorHandlesEditField()) + ->setKey('milestone') + ->setLabel(pht('Milestone Of')) + ->setDescription(pht('Parent project to create a milestone for.')) + ->setConduitDescription( + pht('Choose a parent project to create a new milestone for.')) + ->setConduitTypeDescription(pht('PHID of the parent project.')) + ->setAliases(array('milestonePHID')) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_MILESTONE) + ->setHandleParameterType(new AphrontPHIDHTTPParameterType()) + ->setSingleValue($milestone_phid) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false) + ->setIsLocked(true), id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index f68d4e67f8..66933ac81a 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -164,42 +164,15 @@ protected function buildQueryFromParameters(array $map) { array $handles) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->requireViewer(); - $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); - $list = new PHUIObjectItemListView(); - $list->setUser($viewer); - $can_edit_projects = id(new PhabricatorPolicyFilter()) - ->setViewer($viewer) - ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) - ->apply($projects); - - foreach ($projects as $key => $project) { - $id = $project->getID(); - - $tag_list = id(new PHUIHandleTagListView()) - ->setSlim(true) - ->setHandles(array($handles[$project->getPHID()])); - - $item = id(new PHUIObjectItemView()) - ->setHeader($project->getName()) - ->setHref($this->getApplicationURI("view/{$id}/")) - ->setImageURI($project->getProfileImageURI()) - ->addAttribute($tag_list); - - if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { - $item->addIcon('delete-grey', pht('Archived')); - $item->setDisabled(true); - } - - $list->addItem($item); - } - - $result = new PhabricatorApplicationSearchResultView(); - $result->setObjectList($list); - $result->setNoDataString(pht('No projects found.')); - - return $result; + $list = id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($projects) + ->renderList(); + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list) + ->setNoDataString(pht('No projects found.')); } protected function getNewUserBody() { diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 6a4ed4afba..9f0eae8343 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -88,6 +88,10 @@ final class PhabricatorProject extends PhabricatorProjectDAO } public function getPolicy($capability) { + if ($this->isMilestone()) { + return $this->getParentProject()->getPolicy($capability); + } + switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); @@ -99,6 +103,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if ($this->isMilestone()) { + return $this->getParentProject()->hasAutomaticCapability( + $capability, + $viewer); + } + $can_edit = PhabricatorPolicyCapability::CAN_EDIT; switch ($capability) { @@ -437,6 +447,34 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $ancestors; } + public function supportsEditMembers() { + if ($this->isMilestone()) { + return false; + } + + if ($this->getHasSubprojects()) { + return false; + } + + return true; + } + + public function supportsMilestones() { + if ($this->isMilestone()) { + return false; + } + + return true; + } + + public function supportsSubprojects() { + if ($this->isMilestone()) { + return false; + } + + return true; + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php new file mode 100644 index 0000000000..f9e864ff3d --- /dev/null +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -0,0 +1,53 @@ +projects = $projects; + return $this; + } + + public function getProjects() { + return $this->projects; + } + + public function renderList() { + $viewer = $this->getUser(); + $projects = $this->getProjects(); + + $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + + foreach ($projects as $key => $project) { + $id = $project->getID(); + + $tag_list = id(new PHUIHandleTagListView()) + ->setSlim(true) + ->setHandles(array($handles[$project->getPHID()])); + + $item = id(new PHUIObjectItemView()) + ->setHeader($project->getName()) + ->setHref("/project/view/{$id}/") + ->setImageURI($project->getProfileImageURI()) + ->addAttribute($tag_list); + + if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { + $item->addIcon('delete-grey', pht('Archived')); + $item->setDisabled(true); + } + + $list->addItem($item); + } + + return $list; + } + + public function render() { + return $this->renderList(); + } + +} diff --git a/webroot/rsrc/css/layout/phabricator-side-menu-view.css b/webroot/rsrc/css/layout/phabricator-side-menu-view.css index 5a3521b17e..7f0d2bc720 100644 --- a/webroot/rsrc/css/layout/phabricator-side-menu-view.css +++ b/webroot/rsrc/css/layout/phabricator-side-menu-view.css @@ -89,6 +89,10 @@ color: {$blue}; } +.phabricator-icon-nav .phabricator-side-menu .phui-list-item-icon.grey { + color: {$lightgreyborder}; +} + .phabricator-icon-nav .phabricator-side-menu .phui-list-item-selected { border: none; } From 70053beeed6095be15c0f6eac225396694abcc90 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Dec 2015 04:38:21 -0800 Subject: [PATCH 24/83] Smooth out milestone creation workflow Summary: Ref T10010. - Default name to "Milestone X". - Remove policy controls, which have no effect. - Don't generate slugs for milestones since this is a big pain where they all generate as `#milestone_1` by default (you can add one if you want). I plan to add some kind of syntax like `#parent/32` to mean "Milestone 32 in Parent" later. - Don't require projects to have unique names (again, 900 copies of "Milestone X"). I think we can trust users to sort this out for themselves since modern Phabricator has "Can Create Projects" permission, etc. Test Plan: Created some milestones, had a less awful experience. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14909 --- .../PhabricatorProjectProfileController.php | 4 +- .../PhabricatorProjectTransactionEditor.php | 76 +++++++++---------- .../engine/PhabricatorProjectEditEngine.php | 34 ++++++++- .../project/storage/PhabricatorProject.php | 27 ++++--- .../editengine/PhabricatorEditEngine.php | 11 ++- .../PhabricatorEditEngineExtension.php | 7 -- 6 files changed, 96 insertions(+), 63 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 9262bf26c1..9ee3526c4f 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -186,7 +186,9 @@ final class PhabricatorProjectProfileController ->setName('#'.$slug->getSlug()); } - $view->addProperty(pht('Hashtags'), phutil_implode_html(' ', $hashtags)); + if ($hashtags) { + $view->addProperty(pht('Hashtags'), phutil_implode_html(' ', $hashtags)); + } $view->addProperty( pht('Members'), diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 4e5d61ce54..2c624f85f0 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -3,6 +3,17 @@ final class PhabricatorProjectTransactionEditor extends PhabricatorApplicationTransactionEditor { + private $isMilestone; + + private function setIsMilestone($is_milestone) { + $this->isMilestone = $is_milestone; + return $this; + } + + private function getIsMilestone() { + return $this->isMilestone; + } + public function getEditorApplicationClass() { return 'PhabricatorProjectApplication'; } @@ -91,7 +102,9 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_NAME: $name = $xaction->getNewValue(); $object->setName($name); - $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); + if ($this->getIsMilestone()) { + $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); + } return; case PhabricatorProjectTransaction::TYPE_SLUGS: return; @@ -114,19 +127,7 @@ final class PhabricatorProjectTransactionEditor $object->setParentProjectPHID($xaction->getNewValue()); return; case PhabricatorProjectTransaction::TYPE_MILESTONE: - $current = queryfx_one( - $object->establishConnection('w'), - 'SELECT MAX(milestoneNumber) n - FROM %T - WHERE parentProjectPHID = %s', - $object->getTableName(), - $object->getParentProject()->getPHID()); - if (!$current) { - $number = 1; - } else { - $number = (int)$current['n'] + 1; - } - + $number = $object->getParentProject()->loadNextMilestoneNumber(); $object->setMilestoneNumber($number); $object->setParentProjectPHID($xaction->getNewValue()); return; @@ -275,16 +276,7 @@ final class PhabricatorProjectTransactionEditor } } - $is_milestone = $object->isMilestone(); - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_MILESTONE: - if ($xaction->getNewValue() !== null) { - $is_milestone = true; - } - break; - } - } + $is_milestone = $this->getIsMilestone(); $is_parent = $object->getHasSubprojects(); @@ -346,6 +338,10 @@ final class PhabricatorProjectTransactionEditor break; } + if ($this->getIsMilestone()) { + break; + } + $name = last($xactions)->getNewValue(); if (!PhabricatorSlug::isValidProjectSlug($name)) { @@ -358,20 +354,6 @@ final class PhabricatorProjectTransactionEditor break; } - $name_used_already = id(new PhabricatorProjectQuery()) - ->setViewer($this->getActor()) - ->withNames(array($name)) - ->executeOne(); - if ($name_used_already && - ($name_used_already->getPHID() != $object->getPHID())) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Duplicate'), - pht('Project name is already used.'), - nonempty(last($xactions), null)); - $errors[] = $error; - } - $slug = PhabricatorSlug::normalizeProjectSlug($name); $slug_used_already = id(new PhabricatorProjectSlug()) @@ -381,7 +363,10 @@ final class PhabricatorProjectTransactionEditor $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Duplicate'), - pht('Project name can not be used due to hashtag collision.'), + pht( + 'Project name generates the same hashtag ("%s") as another '. + 'existing project. Choose a unique name.', + '#'.$slug), nonempty(last($xactions), null)); $errors[] = $error; } @@ -883,6 +868,19 @@ final class PhabricatorProjectTransactionEditor } } + $is_milestone = $object->isMilestone(); + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhabricatorProjectTransaction::TYPE_MILESTONE: + if ($xaction->getNewValue() !== null) { + $is_milestone = true; + } + break; + } + } + + $this->setIsMilestone($is_milestone); + return $results; } diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index c90ad6935d..9f72ad8147 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -43,7 +43,17 @@ final class PhabricatorProjectEditEngine } protected function newEditableObject() { - return PhabricatorProject::initializeNewProject($this->getViewer()); + $project = PhabricatorProject::initializeNewProject($this->getViewer()); + + $milestone = $this->getMilestoneProject(); + if ($milestone) { + $default_name = pht( + 'Milestone %s', + new PhutilNumber($milestone->loadNextMilestoneNumber())); + $project->setName($default_name); + } + + return $project; } protected function newObjectQuery() { @@ -92,6 +102,28 @@ final class PhabricatorProjectEditEngine ProjectCreateProjectsCapability::CAPABILITY); } + protected function willConfigureFields($object, array $fields) { + $is_milestone = ($this->getMilestoneProject() || $object->isMilestone()); + + $unavailable = array( + PhabricatorTransactions::TYPE_VIEW_POLICY, + PhabricatorTransactions::TYPE_EDIT_POLICY, + PhabricatorTransactions::TYPE_JOIN_POLICY, + ); + $unavailable = array_fuse($unavailable); + + if ($is_milestone) { + foreach ($fields as $key => $field) { + $xaction_type = $field->getTransactionType(); + if (isset($unavailable[$xaction_type])) { + unset($fields[$key]); + } + } + } + + return $fields; + } + protected function newBuiltinEngineConfigurations() { $configuration = head(parent::newBuiltinEngineConfigurations()); diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 9f0eae8343..7e31bfa1f2 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -211,21 +211,12 @@ final class PhabricatorProject extends PhabricatorProjectDAO 'projectPathKey' => 'bytes4', ), self::CONFIG_KEY_SCHEMA => array( - 'key_phid' => null, - 'phid' => array( - 'columns' => array('phid'), - 'unique' => true, - ), 'key_icon' => array( 'columns' => array('icon'), ), 'key_color' => array( 'columns' => array('color'), ), - 'name' => array( - 'columns' => array('name'), - 'unique' => true, - ), 'key_milestone' => array( 'columns' => array('parentProjectPHID', 'milestoneNumber'), 'unique' => true, @@ -475,6 +466,24 @@ final class PhabricatorProject extends PhabricatorProjectDAO return true; } + public function loadNextMilestoneNumber() { + $current = queryfx_one( + $this->establishConnection('w'), + 'SELECT MAX(milestoneNumber) n + FROM %T + WHERE parentProjectPHID = %s', + $this->getTableName(), + $this->getPHID()); + + if (!$current) { + $number = 1; + } else { + $number = (int)$current['n'] + 1; + } + + return $number; + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index e44a64f66e..e89fea3604 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -64,10 +64,6 @@ abstract class PhabricatorEditEngine abstract public function getEngineApplicationClass(); abstract protected function buildCustomEditFields($object); - protected function didBuildCustomEditFields($object, array $fields) { - return; - } - public function getFieldsForConfig( PhabricatorEditEngineConfiguration $config) { @@ -93,7 +89,6 @@ abstract class PhabricatorEditEngine } $fields = mpull($fields, null, 'getKey'); - $this->didBuildCustomEditFields($object, $fields); $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { @@ -115,7 +110,6 @@ abstract class PhabricatorEditEngine } $extension_fields = mpull($extension_fields, null, 'getKey'); - $extension->didBuildCustomEditFields($this, $object, $extension_fields); foreach ($extension_fields as $key => $field) { $fields[$key] = $field; @@ -123,11 +117,16 @@ abstract class PhabricatorEditEngine } $config = $this->getEditEngineConfiguration(); + $fields = $this->willConfigureFields($object, $fields); $fields = $config->applyConfigurationToFields($this, $object, $fields); return $fields; } + protected function willConfigureFields($object, array $fields) { + return $fields; + } + /* -( Display Text )------------------------------------------------------- */ diff --git a/src/applications/transactions/engineextension/PhabricatorEditEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorEditEngineExtension.php index d4ff06bb40..9a799d4e5b 100644 --- a/src/applications/transactions/engineextension/PhabricatorEditEngineExtension.php +++ b/src/applications/transactions/engineextension/PhabricatorEditEngineExtension.php @@ -32,13 +32,6 @@ abstract class PhabricatorEditEngineExtension extends Phobject { PhabricatorEditEngine $engine, PhabricatorApplicationTransactionInterface $object); - public function didBuildCustomEditFields( - PhabricatorEditEngine $engine, - PhabricatorApplicationTransactionInterface $object, - array $fields) { - return; - } - final public static function getAllExtensions() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) From 14ae3c099c2e4ce6c706bbaa4aa9fcb4bc7d3dec Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Dec 2015 05:06:47 -0800 Subject: [PATCH 25/83] Make queries for Project "X" mean "X, or any subproject of X" Summary: Ref T10010. I think this is the desired/expected default behavior (e.g., searching for "Maniphest" should find tasks in any subproject or sprint of that project). I'll probably add an "exact(...)" function later to mean "only the Maniphest superproject, exactly, not any of its children". Test Plan: - Added and executed unit tests. - Ran various queries from the web UI. - Got sensible-seeming results. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14910 --- src/__phutil_library_map__.php | 4 +- .../PhabricatorProjectCoreTestCase.php | 151 ++++++++++++++++++ ...icatorProjectLogicalAncestorDatasource.php | 95 +++++++++++ ...PhabricatorProjectLogicalAndDatasource.php | 36 ----- .../PhabricatorProjectLogicalDatasource.php | 2 +- .../constraint/PhabricatorQueryConstraint.php | 1 + ...PhabricatorCursorPagedPolicyAwareQuery.php | 82 +++++++++- 7 files changed, 327 insertions(+), 44 deletions(-) create mode 100644 src/applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php delete mode 100644 src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0e06a9198a..03dae199af 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2857,7 +2857,7 @@ phutil_register_library_map(array( 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 'PhabricatorProjectListView' => 'applications/project/view/PhabricatorProjectListView.php', 'PhabricatorProjectLockController' => 'applications/project/controller/PhabricatorProjectLockController.php', - 'PhabricatorProjectLogicalAndDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php', + 'PhabricatorProjectLogicalAncestorDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php', 'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php', 'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php', 'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php', @@ -7209,7 +7209,7 @@ phutil_register_library_map(array( 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectListView' => 'AphrontView', 'PhabricatorProjectLockController' => 'PhabricatorProjectController', - 'PhabricatorProjectLogicalAndDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorProjectLogicalAncestorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index c31c69fda9..36c78386f3 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -711,6 +711,157 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { pht('Leave allowed without any permission.')); } + + public function testComplexConstraints() { + $user = $this->createUser(); + $user->save(); + + $engineering = $this->createProject($user); + $engineering_scan = $this->createProject($user, $engineering); + $engineering_warp = $this->createProject($user, $engineering); + + $exploration = $this->createProject($user); + $exploration_diplomacy = $this->createProject($user, $exploration); + + $task_engineering = $this->newTask( + $user, + array($engineering), + pht('Engineering Only')); + + $task_exploration = $this->newTask( + $user, + array($exploration), + pht('Exploration Only')); + + $task_warp_explore = $this->newTask( + $user, + array($engineering_warp, $exploration), + pht('Warp to New Planet')); + + $task_diplomacy_scan = $this->newTask( + $user, + array($engineering_scan, $exploration_diplomacy), + pht('Scan Diplomat')); + + $task_diplomacy = $this->newTask( + $user, + array($exploration_diplomacy), + pht('Diplomatic Meeting')); + + $task_warp_scan = $this->newTask( + $user, + array($engineering_scan, $engineering_warp), + pht('Scan Warp Drives')); + + $this->assertQueryByProjects( + $user, + array( + $task_engineering, + $task_warp_explore, + $task_diplomacy_scan, + $task_warp_scan, + ), + array($engineering), + pht('All Engineering')); + + $this->assertQueryByProjects( + $user, + array( + $task_diplomacy_scan, + $task_warp_scan, + ), + array($engineering_scan), + pht('All Scan')); + + $this->assertQueryByProjects( + $user, + array( + $task_warp_explore, + $task_diplomacy_scan, + ), + array($engineering, $exploration), + pht('Engineering + Exploration')); + + // This is testing that a query for "Parent" and "Parent > Child" works + // properly. + $this->assertQueryByProjects( + $user, + array( + $task_diplomacy_scan, + $task_warp_scan, + ), + array($engineering, $engineering_scan), + pht('Engineering + Scan')); + } + + private function newTask( + PhabricatorUser $viewer, + array $projects, + $name = null) { + + $task = ManiphestTask::initializeNewTask($viewer); + + if (!strlen($name)) { + $name = pht('Test Task'); + } + + $xactions = array(); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setNewValue($name); + + if ($projects) { + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setNewValue( + array( + '=' => array_fuse(mpull($projects, 'getPHID')), + )); + } + + $editor = id(new ManiphestTransactionEditor()) + ->setActor($viewer) + ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContinueOnNoEffect(true) + ->applyTransactions($task, $xactions); + + return $task; + } + + private function assertQueryByProjects( + PhabricatorUser $viewer, + array $expect, + array $projects, + $label = null) { + + $datasource = id(new PhabricatorProjectLogicalDatasource()) + ->setViewer($viewer); + + $project_phids = mpull($projects, 'getPHID'); + $constraints = $datasource->evaluateTokens($project_phids); + + $query = id(new ManiphestTaskQuery()) + ->setViewer($viewer); + + $query->withEdgeLogicConstraints( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + $constraints); + + $tasks = $query->execute(); + + $expect_phids = mpull($expect, 'getTitle', 'getPHID'); + ksort($expect_phids); + + $actual_phids = mpull($tasks, 'getTitle', 'getPHID'); + ksort($actual_phids); + + $this->assertEqual($expect_phids, $actual_phids, $label); + } + private function refreshProject( PhabricatorProject $project, PhabricatorUser $viewer, diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php new file mode 100644 index 0000000000..070cb88485 --- /dev/null +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalAncestorDatasource.php @@ -0,0 +1,95 @@ +getViewer(); + + $all_projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withAncestorProjectPHIDs($phids) + ->execute(); + + foreach ($phids as $phid) { + $map[$phid][] = $phid; + } + + foreach ($all_projects as $project) { + $project_phid = $project->getPHID(); + $map[$project_phid][] = $project_phid; + foreach ($project->getAncestorProjects() as $ancestor) { + $ancestor_phid = $ancestor->getPHID(); + + if (isset($phids[$project_phid]) && isset($phids[$ancestor_phid])) { + // This is a descendant of some other project in the query, so + // we don't need to query for that project. This happens if a user + // runs a query for both "Engineering" and "Engineering > Warp + // Drive". We can only ever match the "Warp Drive" results, so + // we do not need to add the weaker "Engineering" constraint. + $skip[$ancestor_phid] = true; + } + + $map[$ancestor_phid][] = $project_phid; + } + } + } + + foreach ($results as $key => $result) { + if (!is_string($result)) { + continue; + } + + if (empty($map[$result])) { + continue; + } + + // This constraint is implied by another, stronger constraint. + if (isset($skip[$result])) { + unset($results[$key]); + continue; + } + + // If we have duplicates, don't apply the second constraint. + $skip[$result] = true; + + $results[$key] = new PhabricatorQueryConstraint( + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, + $map[$result]); + } + + return $results; + } + +} diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php deleted file mode 100644 index a3016820a0..0000000000 --- a/src/applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php +++ /dev/null @@ -1,36 +0,0 @@ - $result) { - if (is_string($result)) { - $results[$key] = new PhabricatorQueryConstraint( - PhabricatorQueryConstraint::OPERATOR_AND, - $result); - } - } - - return $results; - } - -} diff --git a/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php b/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php index 1bce988339..b724e9f8ca 100644 --- a/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectLogicalDatasource.php @@ -18,7 +18,7 @@ final class PhabricatorProjectLogicalDatasource public function getComponentDatasources() { return array( new PhabricatorProjectNoProjectsDatasource(), - new PhabricatorProjectLogicalAndDatasource(), + new PhabricatorProjectLogicalAncestorDatasource(), new PhabricatorProjectLogicalOrNotDatasource(), new PhabricatorProjectLogicalViewerDatasource(), new PhabricatorProjectLogicalUserDatasource(), diff --git a/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php index 7a04d9d7db..5b5702673b 100644 --- a/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php +++ b/src/infrastructure/query/constraint/PhabricatorQueryConstraint.php @@ -6,6 +6,7 @@ final class PhabricatorQueryConstraint extends Phobject { const OPERATOR_OR = 'or'; const OPERATOR_NOT = 'not'; const OPERATOR_NULL = 'null'; + const OPERATOR_ANCESTOR = 'ancestor'; private $operator; private $value; diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 58df7aea33..9996f03ae0 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1516,8 +1516,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $constraints = mgroup($constraints, 'getOperator'); foreach ($constraints as $operator => $list) { foreach ($list as $item) { - $value = $item->getValue(); - $this->edgeLogicConstraints[$edge_type][$operator][$value] = $item; + $this->edgeLogicConstraints[$edge_type][$operator][] = $item; } } @@ -1548,6 +1547,47 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $this->buildEdgeLogicTableAliasCount($alias)); } break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: + // This is tricky. We have a query which specifies multiple + // projects, each of which may have an arbitrarily large number + // of descendants. + + // Suppose the projects are "Engineering" and "Operations", and + // "Engineering" has subprojects X, Y and Z. + + // We first use `FIELD(dst, X, Y, Z)` to produce a 0 if a row + // is not part of Engineering at all, or some number other than + // 0 if it is. + + // Then we use `IF(..., idx, NULL)` to convert the 0 to a NULL and + // any other value to an index (say, 1) for the ancestor. + + // We build these up for every ancestor, then use `COALESCE(...)` + // to select the non-null one, giving us an ancestor which this + // row is a member of. + + // From there, we use `COUNT(DISTINCT(...))` to make sure that + // each result row is a member of all ancestors. + if (count($list) > 1) { + $idx = 1; + $parts = array(); + foreach ($list as $constraint) { + $parts[] = qsprintf( + $conn, + 'IF(FIELD(%T.dst, %Ls) != 0, %d, NULL)', + $alias, + (array)$constraint->getValue(), + $idx++); + } + $parts = implode(', ', $parts); + + $select[] = qsprintf( + $conn, + 'COUNT(DISTINCT(COALESCE(%Q))) %T', + $parts, + $this->buildEdgeLogicTableAliasAncestor($alias)); + } + break; default: break; } @@ -1573,6 +1613,16 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery foreach ($constraints as $operator => $list) { $alias = $this->getEdgeLogicTableAlias($operator, $type); + + $phids = array(); + foreach ($list as $constraint) { + $value = (array)$constraint->getValue(); + foreach ($value as $v) { + $phids[$v] = $v; + } + } + $phids = array_keys($phids); + switch ($operator) { case PhabricatorQueryConstraint::OPERATOR_NOT: $joins[] = qsprintf( @@ -1586,8 +1636,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $alias, $type, $alias, - mpull($list, 'getValue')); + $phids); break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: // If we're including results with no matches, we have to degrade @@ -1611,7 +1662,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery $alias, $type, $alias, - mpull($list, 'getValue')); + $phids); break; case PhabricatorQueryConstraint::OPERATOR_NULL: $joins[] = qsprintf( @@ -1711,6 +1762,15 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery count($list)); } break; + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: + if (count($list) > 1) { + $having[] = qsprintf( + $conn, + '%T = %d', + $this->buildEdgeLogicTableAliasAncestor($alias), + count($list)); + } + break; } } } @@ -1729,6 +1789,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery case PhabricatorQueryConstraint::OPERATOR_NOT: case PhabricatorQueryConstraint::OPERATOR_AND: case PhabricatorQueryConstraint::OPERATOR_OR: + case PhabricatorQueryConstraint::OPERATOR_ANCESTOR: if (count($list) > 1) { return true; } @@ -1758,6 +1819,13 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery return $alias.'_count'; } + /** + * @task edgelogic + */ + private function buildEdgeLogicTableAliasAncestor($alias) { + return $alias.'_ancestor'; + } + /** * Select certain edge logic constraint values. @@ -1781,7 +1849,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery } foreach ($constraints as $operator => $list) { foreach ($list as $constraint) { - $values[] = $constraint->getValue(); + $value = (array)$constraint->getValue(); + foreach ($value as $v) { + $values[] = $v; + } } } } @@ -1812,6 +1883,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery PhabricatorQueryConstraint::OPERATOR_AND, PhabricatorQueryConstraint::OPERATOR_OR, PhabricatorQueryConstraint::OPERATOR_NOT, + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, )); if ($project_phids) { $projects = id(new PhabricatorProjectQuery()) From 4acb7f63e85c0718cfb7ac4b51b2487ba245d9e4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 30 Dec 2015 12:52:53 -0800 Subject: [PATCH 26/83] Drop domain key on PhameBlog Summary: Right now you can't create two blogs without a domain name, since it has a unique key on the column. Removing the key. Test Plan: Create two blogs with no domain name, works as expected. Create two blogs with `cat.dog` as domain name, get duplicate domain error. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9360 Differential Revision: https://secure.phabricator.com/D14915 --- src/applications/phame/editor/PhameBlogEditor.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index 3698293059..c73c2be0a4 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -47,9 +47,14 @@ final class PhameBlogEditor switch ($xaction->getTransactionType()) { case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_DESCRIPTION: - case PhameBlogTransaction::TYPE_DOMAIN: case PhameBlogTransaction::TYPE_STATUS: return $xaction->getNewValue(); + case PhameBlogTransaction::TYPE_DOMAIN: + $domain = $xaction->getNewValue(); + if (!strlen($xaction->getNewValue())) { + return null; + } + return $domain; } } From 5ea5b0c41c610f660d51357f85346f190d49a88b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 30 Dec 2015 12:05:39 -0800 Subject: [PATCH 27/83] Update some PhamePost transactions Summary: Cleans up some language, colors, etc. Test Plan: Write lots of new posts, hide them, edit them, check history. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9360 Differential Revision: https://secure.phabricator.com/D14914 --- .../phame/storage/PhamePostTransaction.php | 65 +++++++------------ 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index fcdb7265f8..be5dbfd8b5 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -4,7 +4,6 @@ final class PhamePostTransaction extends PhabricatorApplicationTransaction { const TYPE_TITLE = 'phame.post.title'; - const TYPE_PHAME_TITLE = 'phame.post.phame.title'; const TYPE_BODY = 'phame.post.body'; const TYPE_VISIBILITY = 'phame.post.visibility'; const TYPE_BLOG = 'phame.post.blog'; @@ -39,12 +38,6 @@ final class PhamePostTransaction } public function shouldHide() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_PHAME_TITLE: - case self::TYPE_BODY: - return ($old === null); - } return parent::shouldHide(); } @@ -72,19 +65,18 @@ final class PhamePostTransaction public function getIcon() { $old = $this->getOldValue(); + $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return 'fa-plus'; - } else { - return 'fa-pencil'; - } - break; - case self::TYPE_PHAME_TITLE: - case self::TYPE_BODY: + case PhabricatorTransactions::TYPE_CREATE: + return 'fa-plus'; + break; case self::TYPE_VISIBILITY: - return 'fa-pencil'; - break; + if ($new == PhameConstants::VISIBILITY_PUBLISHED) { + return 'fa-globe'; + } else { + return 'fa-eye-slash'; + } + break; } return parent::getIcon(); } @@ -100,7 +92,6 @@ final class PhamePostTransaction $tags[] = self::MAILTAG_SUBSCRIBERS; break; case self::TYPE_TITLE: - case self::TYPE_PHAME_TITLE: case self::TYPE_BODY: $tags[] = self::MAILTAG_CONTENT; break; @@ -123,7 +114,7 @@ final class PhamePostTransaction switch ($type) { case PhabricatorTransactions::TYPE_CREATE: return pht( - '%s created this post.', + '%s authored this post.', $this->renderHandleLink($author_phid)); case self::TYPE_BLOG: return pht( @@ -159,12 +150,6 @@ final class PhamePostTransaction $this->renderHandleLink($author_phid)); } break; - case self::TYPE_PHAME_TITLE: - return pht( - '%s updated the post\'s Phame title to "%s".', - $this->renderHandleLink($author_phid), - rtrim($new, '/')); - break; } return parent::getTitle(); @@ -179,6 +164,11 @@ final class PhamePostTransaction $type = $this->getTransactionType(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s authored %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); case self::TYPE_BLOG: return pht( '%s moved post "%s" from "%s" to "%s".', @@ -218,37 +208,30 @@ final class PhamePostTransaction $this->renderHandleLink($object_phid)); } break; - case self::TYPE_PHAME_TITLE: - return pht( - '%s updated the Phame title for %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($object_phid)); - break; } return parent::getTitleForFeed(); } public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { + $old = $this->getOldValue(); + switch ($this->getTransactionType()) { case self::TYPE_BODY: - return $this->getNewValue(); + if ($old === null) { + return $this->getNewValue(); + } + break; } return null; } public function getColor() { - $old = $this->getOldValue(); - switch ($this->getTransactionType()) { - case self::TYPE_TITLE: - if ($old === null) { - return PhabricatorTransactions::COLOR_GREEN; - } - break; + case PhabricatorTransactions::TYPE_CREATE: + return PhabricatorTransactions::COLOR_GREEN; } - return parent::getColor(); } From 972788b8b5df76ca0d298d25a00a66c8663ca00e Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 30 Dec 2015 03:24:19 -0800 Subject: [PATCH 28/83] Give IconSetControl a meaningful disabled state Summary: Ref T10004. This control doesn't disable visually or behaviorally, e.g. when locked in an EditEngine configuration. Test Plan: - Locked field for Projects. - Reviewed form in EditEngine. - Created/edited a project. - Swapped default. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10004 Differential Revision: https://secure.phabricator.com/D14911 --- resources/celerity/map.php | 16 ++++++++-------- .../PhabricatorProjectTransactionEditor.php | 2 +- src/view/form/control/PHUIFormIconSetControl.php | 13 ++++++++++++- webroot/rsrc/js/core/behavior-choose-control.js | 9 +++++++-- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bd9c2f2217..5fb67b2d91 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -465,7 +465,7 @@ return array( 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', - 'rsrc/js/core/behavior-choose-control.js' => 'dfaafb14', + 'rsrc/js/core/behavior-choose-control.js' => '8fee767e', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'a205cf28', @@ -571,7 +571,7 @@ return array( 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-bulk-job-reload' => 'edf8a145', - 'javelin-behavior-choose-control' => 'dfaafb14', + 'javelin-behavior-choose-control' => '8fee767e', 'javelin-behavior-comment-actions' => 'b65559c0', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', @@ -1517,6 +1517,12 @@ return array( 'javelin-install', 'javelin-dom', ), + '8fee767e' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-workflow', + ), '9007c197' => array( 'javelin-behavior', 'javelin-dom', @@ -1909,12 +1915,6 @@ return array( 'df5e11d2' => array( 'javelin-install', ), - 'dfaafb14' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-workflow', - ), 'e10f8e18' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 2c624f85f0..86748f6287 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -102,7 +102,7 @@ final class PhabricatorProjectTransactionEditor case PhabricatorProjectTransaction::TYPE_NAME: $name = $xaction->getNewValue(); $object->setName($name); - if ($this->getIsMilestone()) { + if (!$this->getIsMilestone()) { $object->setPrimarySlug(PhabricatorSlug::normalizeProjectSlug($name)); } return; diff --git a/src/view/form/control/PHUIFormIconSetControl.php b/src/view/form/control/PHUIFormIconSetControl.php index 913402d8ea..459bff08fa 100644 --- a/src/view/form/control/PHUIFormIconSetControl.php +++ b/src/view/form/control/PHUIFormIconSetControl.php @@ -26,11 +26,21 @@ final class PHUIFormIconSetControl $input_id = celerity_generate_unique_node_id(); $display_id = celerity_generate_unique_node_id(); + $is_disabled = $this->getDisabled(); + + $classes = array(); + $classes[] = 'button'; + $classes[] = 'grey'; + + if ($is_disabled) { + $classes[] = 'disabled'; + } + $button = javelin_tag( 'a', array( 'href' => '#', - 'class' => 'button grey', + 'class' => implode(' ', $classes), 'sigil' => 'phui-form-iconset-button', ), $set->getChooseButtonText()); @@ -79,6 +89,7 @@ final class PHUIFormIconSetControl 'input', array( 'type' => 'hidden', + 'disabled' => ($is_disabled ? 'disabled' : null), 'name' => $this->getName(), 'value' => $this->getValue(), 'id' => $input_id, diff --git a/webroot/rsrc/js/core/behavior-choose-control.js b/webroot/rsrc/js/core/behavior-choose-control.js index 8a33ac4eb3..3ff2f166ca 100644 --- a/webroot/rsrc/js/core/behavior-choose-control.js +++ b/webroot/rsrc/js/core/behavior-choose-control.js @@ -15,14 +15,19 @@ JX.behavior('choose-control', function() { e.kill(); var data = e.getNodeData('phui-form-iconset'); + var input = JX.$(data.inputID); + + if (input.disabled) { + return; + } var params = { - value: JX.$(data.inputID).value + value: input.value }; new JX.Workflow(data.uri, params) .setHandler(function(r) { - JX.$(data.inputID).value = r.value; + input.value = r.value; JX.DOM.setContent(JX.$(data.displayID), JX.$H(r.display)); }) .start(); From 389e4d1b1fa0a0baab4ad9e4c2451e9d0d2cc4e4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 30 Dec 2015 03:35:51 -0800 Subject: [PATCH 29/83] Lock milestone projects to an automatic color/icon Summary: Ref T10010. Currently, milestone subproject have editable icons/colors, but I don't think this is likely to be used much (the expectation is that milestones will be common and homogenous, and it doesn't make much sense to pick different icons for "Sprint 32" vs "Sprint 33", I think). Locking the icon and color lets us simplify the form, make milestones more distinct, and potentially reuse the color later for other things (e.g., active/future/past or on time / overdue or whatever else) or just give them a special color to make them more visible. The best argument for retaining this that I can come up with is that certain milestones may be special (e.g., Sprint 19 is a major release?), but you can just name it "Sprint 19 (v3.0!)" or something, which seems pretty good for now. Also don't show milestones on task browse/list view. Test Plan: {F1048532} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10010 Differential Revision: https://secure.phabricator.com/D14912 --- .../engine/PhabricatorProjectEditEngine.php | 2 ++ .../phid/PhabricatorProjectProjectPHIDType.php | 14 ++++++++++---- .../query/PhabricatorProjectSearchEngine.php | 3 ++- .../project/storage/PhabricatorProject.php | 16 ++++++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 9f72ad8147..389c585fce 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -109,6 +109,8 @@ final class PhabricatorProjectEditEngine PhabricatorTransactions::TYPE_VIEW_POLICY, PhabricatorTransactions::TYPE_EDIT_POLICY, PhabricatorTransactions::TYPE_JOIN_POLICY, + PhabricatorProjectTransaction::TYPE_ICON, + PhabricatorProjectTransaction::TYPE_COLOR, ); $unavailable = array_fuse($unavailable); diff --git a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php index c3d9bdd3fb..eb1a59a5f5 100644 --- a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php +++ b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php @@ -42,11 +42,17 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType { $slug = $project->getPrimarySlug(); $handle->setName($name); - $handle->setObjectName('#'.$slug); - $handle->setURI("/tag/{$slug}/"); + + if (strlen($slug)) { + $handle->setObjectName('#'.$slug); + $handle->setURI("/tag/{$slug}/"); + } else { + $handle->setURI("/project/view/{$id}/"); + } + $handle->setImageURI($project->getProfileImageURI()); - $handle->setIcon($project->getIcon()); - $handle->setTagColor($project->getColor()); + $handle->setIcon($project->getDisplayIcon()); + $handle->setTagColor($project->getDisplayColor()); if ($project->isArchived()) { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 66933ac81a..0c3af4a8e6 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -13,7 +13,8 @@ final class PhabricatorProjectSearchEngine public function newQuery() { return id(new PhabricatorProjectQuery()) - ->needImages(true); + ->needImages(true) + ->withIsMilestone(false); } protected function buildCustomSearchFields() { diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 7e31bfa1f2..5d2aa3461e 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -484,6 +484,22 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $number; } + public function getDisplayIcon() { + if ($this->isMilestone()) { + return 'fa-map-marker'; + } + + return $this->getIcon(); + } + + public function getDisplayColor() { + if ($this->isMilestone()) { + return self::DEFAULT_COLOR; + } + + return $this->getColor(); + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ From 84a570a61b8e4ab67538203081039f97fdf8f441 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 31 Dec 2015 05:57:12 -0800 Subject: [PATCH 30/83] fix broken link for project creating button Summary: fix T10074 Test Plan: click the "create project" button Reviewers: joshuaspence, #blessed_reviewers, epriestley Reviewed By: joshuaspence, #blessed_reviewers, epriestley Subscribers: joshuaspence, epriestley Maniphest Tasks: T10074 Differential Revision: https://secure.phabricator.com/D14917 --- .../controller/DiffusionCommitEditController.php | 9 --------- .../project/query/PhabricatorProjectSearchEngine.php | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php index c9526883e0..82c87f7e85 100644 --- a/src/applications/diffusion/controller/DiffusionCommitEditController.php +++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php @@ -47,15 +47,6 @@ final class DiffusionCommitEditController extends DiffusionController { ->setName('projects') ->setValue($current_proj_phids) ->setID($tokenizer_id) - ->setCaption( - javelin_tag( - 'a', - array( - 'href' => '/project/create/', - 'mustcapture' => true, - 'sigil' => 'project-create', - ), - pht('Create New Project'))) ->setDatasource(new PhabricatorProjectDatasource())); $reason = $data->getCommitDetail('autocloseReason', false); diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 0c3af4a8e6..66d64dcdcc 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -180,7 +180,7 @@ protected function buildQueryFromParameters(array $map) { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Project')) - ->setHref('/project/create/') + ->setHref('/project/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getFontIcon(); From fe6224f5059e269db130dcb2f22ded402f795e08 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 31 Dec 2015 13:08:33 -0800 Subject: [PATCH 31/83] Add Next and Previous UI to PhamePostView Summary: Creates a new next/previous UI for PhamePosts, and adds a setFoot to PHUIDocumentViewPro for future use in other apps. Test Plan: Test first, next, last posts on Phame in mobile, desktop, and tablet breakpoints. {F1050152} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14919 --- resources/celerity/map.php | 8 +- src/__phutil_library_map__.php | 2 + .../post/PhamePostViewController.php | 17 +-- .../phame/view/PhameNextPostView.php | 113 ++++++++++++++++++ src/view/phui/PHUIDocumentViewPro.php | 20 ++++ webroot/rsrc/css/application/phame/phame.css | 103 ++++++++++++++++ webroot/rsrc/css/phui/phui-document-pro.css | 13 +- 7 files changed, 256 insertions(+), 20 deletions(-) create mode 100644 src/applications/phame/view/PhameNextPostView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5fb67b2d91..8343d9526c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -82,7 +82,7 @@ return array( 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'a5157c48', 'rsrc/css/application/people/people-profile.css' => '25970776', - 'rsrc/css/application/phame/phame.css' => '09a39e8d', + 'rsrc/css/application/phame/phame.css' => 'dac8fdf2', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '95174bdd', @@ -127,7 +127,7 @@ return array( 'rsrc/css/phui/phui-box.css' => 'a5bb366d', 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => '414406b5', - 'rsrc/css/phui/phui-document-pro.css' => 'e0fad431', + 'rsrc/css/phui/phui-document-pro.css' => '8799acf7', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'a4a1c3b9', 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', @@ -781,7 +781,7 @@ return array( 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '57ddcaa2', - 'phame-css' => '09a39e8d', + 'phame-css' => 'dac8fdf2', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', @@ -802,7 +802,7 @@ return array( 'phui-crumbs-view-css' => '414406b5', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'a4a1c3b9', - 'phui-document-view-pro-css' => 'e0fad431', + 'phui-document-view-pro-css' => '8799acf7', 'phui-feed-story-css' => 'b7b26d23', 'phui-font-icon-base-css' => 'ecbbb4c2', 'phui-fontkit-css' => '9cda225e', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 03dae199af..6c5a01fd0e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3427,6 +3427,7 @@ phutil_register_library_map(array( 'PhameDraftListView' => 'applications/phame/view/PhameDraftListView.php', 'PhameHomeController' => 'applications/phame/controller/PhameHomeController.php', 'PhameLiveController' => 'applications/phame/controller/PhameLiveController.php', + 'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php', 'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', @@ -7880,6 +7881,7 @@ phutil_register_library_map(array( 'PhameDraftListView' => 'AphrontTagView', 'PhameHomeController' => 'PhamePostController', 'PhameLiveController' => 'PhameController', + 'PhameNextPostView' => 'AphrontTagView', 'PhamePost' => array( 'PhameDAO', 'PhabricatorPolicyInterface', diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 0472db20bd..6a0ceace4f 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -126,20 +126,15 @@ final class PhamePostViewController ->setUser($viewer) ->setObject($post); + $next_view = new PhameNextPostView(); if ($next) { - $properties->addProperty( - pht('Later Posts'), - $viewer->renderHandleList(mpull($next, 'getPHID'))); + $next_view->setNext($next->getTitle(), $next->getViewURI()); } - if ($prev) { - $properties->addProperty( - pht('Earlier Posts'), - $viewer->renderHandleList(mpull($prev, 'getPHID'))); + $next_view->setPrevious($prev->getTitle(), $prev->getViewURI()); } - $properties->invokeWillRenderEvent(); - + $document->setFoot($next_view); $crumbs = $this->buildApplicationCrumbs(); $page = $this->newPage() @@ -257,7 +252,7 @@ final class PhamePostViewController ->setViewer($viewer) ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) ->withBlogPHIDs(array($post->getBlog()->getPHID())) - ->setLimit(2); + ->setLimit(1); $prev = id(clone $query) ->setAfterID($post->getID()) @@ -267,7 +262,7 @@ final class PhamePostViewController ->setBeforeID($post->getID()) ->execute(); - return array($prev, $next); + return array(head($prev), head($next)); } } diff --git a/src/applications/phame/view/PhameNextPostView.php b/src/applications/phame/view/PhameNextPostView.php new file mode 100644 index 0000000000..87f4f7aa1a --- /dev/null +++ b/src/applications/phame/view/PhameNextPostView.php @@ -0,0 +1,113 @@ +nextTitle = $title; + $this->nextHref = $href; + return $this; + } + + public function setPrevious($title, $href) { + $this->previousTitle = $title; + $this->previousHref = $href; + return $this; + } + + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'phame-next-post-view'; + $classes[] = 'grouped'; + return array('class' => implode(' ', $classes)); + } + + protected function getTagContent() { + require_celerity_resource('phame-css'); + + $p_icon = id(new PHUIIconView()) + ->setIconFont('fa-angle-left'); + + $previous_icon = phutil_tag( + 'div', + array( + 'class' => 'phame-previous-arrow', + ), + $p_icon); + + $previous_text = phutil_tag( + 'div', + array( + 'class' => 'phame-previous-header', + ), + pht('Previous Post')); + + $previous_title = phutil_tag( + 'div', + array( + 'class' => 'phame-previous-title', + ), + $this->previousTitle); + + $previous = null; + if ($this->previousHref) { + $previous = phutil_tag( + 'a', + array( + 'class' => 'phame-previous', + 'href' => $this->previousHref, + ), + array( + $previous_icon, + $previous_text, + $previous_title, + )); + } + + $n_icon = id(new PHUIIconView()) + ->setIconFont('fa-angle-right'); + + $next_icon = phutil_tag( + 'div', + array( + 'class' => 'phame-next-arrow', + ), + $n_icon); + + $next_text = phutil_tag( + 'div', + array( + 'class' => 'phame-next-header', + ), + pht('Next Post')); + + $next_title = phutil_tag( + 'div', + array( + 'class' => 'phame-next-title', + ), + $this->nextTitle); + + $next = null; + if ($this->nextHref) { + $next = phutil_tag( + 'a', + array( + 'class' => 'phame-next', + 'href' => $this->nextHref, + ), + array( + $next_icon, + $next_text, + $next_title, + )); + } + + return array($previous, $next); + } + +} diff --git a/src/view/phui/PHUIDocumentViewPro.php b/src/view/phui/PHUIDocumentViewPro.php index c18732e2c6..dbab3ab54c 100644 --- a/src/view/phui/PHUIDocumentViewPro.php +++ b/src/view/phui/PHUIDocumentViewPro.php @@ -7,6 +7,7 @@ final class PHUIDocumentViewPro extends AphrontTagView { private $bookdescription; private $fluid; private $toc; + private $foot; public function setHeader(PHUIHeaderView $header) { $header->setTall(true); @@ -30,6 +31,11 @@ final class PHUIDocumentViewPro extends AphrontTagView { return $this; } + public function setFoot($foot) { + $this->foot = $foot; + return $this; + } + protected function getTagAttributes() { $classes = array(); @@ -37,6 +43,9 @@ final class PHUIDocumentViewPro extends AphrontTagView { if ($this->fluid) { $classes[] = 'phui-document-fluid'; } + if ($this->foot) { + $classes[] = 'document-has-foot'; + } return array( 'class' => implode(' ', $classes), @@ -95,6 +104,16 @@ final class PHUIDocumentViewPro extends AphrontTagView { $toc); } + $foot_content = null; + if ($this->foot) { + $foot_content = phutil_tag( + 'div', + array( + 'class' => 'phui-document-foot-content', + ), + $this->foot); + } + $content_inner = phutil_tag( 'div', array( @@ -104,6 +123,7 @@ final class PHUIDocumentViewPro extends AphrontTagView { $table_of_contents, $this->header, $main_content, + $foot_content, )); $content = phutil_tag( diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 02d8761a6b..65604a6dd6 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -134,3 +134,106 @@ width: 14px; color: {$lightbluetext}; } + +.phame-next-post-view { + margin: 0 auto; + padding: 12px 0; + font-family: 'Aleo', {$fontfamily}; +} + +.phame-next { + width: 360px; + float: right; + text-align: right; + color: #000; + position: relative; +} + +.phame-next-arrow, +.phame-previous-arrow { + border: 1px solid {$lightblueborder}; + border-radius: 36px; + height: 36px; + width: 36px; + text-align: center; +} + +.phame-next-arrow .phui-icon-view, +.phame-previous-arrow .phui-icon-view { + height: 36px; + width: 34px; + font-size: 24px; + line-height: 35px; + color: {$lightblueborder}; +} + +.phame-previous-arrow { + float: left; +} + +.phame-next-arrow { + float: right; +} + +.phame-previous { + width: 360px; + float: left; + text-align: left; + color: #000; + position: relative; +} + +.device .phame-previous, +.device .phame-next { + width: 100px; +} + +.device .phame-previous .phame-previous-title, +.device .phame-next .phame-next-title { + display: none; +} + +.phame-next:hover, +.phame-previous:hover { + text-decoration: none; +} + +.phame-next:hover .phame-next-arrow, +.phame-previous:hover .phame-previous-arrow { + border-color: {$indigo}; +} + +.phame-next:hover .phui-icon-view, +.phame-previous:hover .phui-icon-view { + color: {$indigo}; +} + +.phame-next-header, +.phame-previous-header { + font-weight: bold; + font-size: {$biggerfontsize}; +} + +.phame-next-header { + top: 2px; + right: 50px; + position: absolute; +} + +.phame-next-title { + top: 22px; + right: 50px; + position: absolute; +} + +.phame-previous-header { + top: 2px; + left: 50px; + position: absolute; +} + +.phame-previous-title { + top: 22px; + left: 50px; + position: absolute; +} diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index ab3a22656c..90f268cf66 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -179,11 +179,6 @@ a.button.phui-document-toc { display: none; } -.phui-document-view-pro-box .phui-timeline-major-event .phui-timeline-content - .phui-timeline-core-content { - -} - .phui-document-view-pro-box .phui-object-box { margin: 0; } @@ -195,3 +190,11 @@ a.button.phui-document-toc { .phui-document-view-pro-box .phui-object-box .remarkup-assist-textarea { height: 9em; } + +.document-has-foot .phui-document-view-pro { + padding-bottom: 0; +} + +.phui-document-foot-content { + margin: 64px 0 32px; +} From 45ccc930ec4a19109f39c2f22e31454c633f0de9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 1 Jan 2016 16:12:13 +0000 Subject: [PATCH 32/83] Convert Badges to use EditEngine Summary: Moves Badges over to EditEngine Test Plan: Create a new Badge, Edit a Badge Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14771 --- src/__phutil_library_map__.php | 4 +- .../PhabricatorBadgesEditController.php | 183 +----------------- .../editor/PhabricatorBadgesEditEngine.php | 98 ++++++++++ .../badges/storage/PhabricatorBadgesBadge.php | 4 + 4 files changed, 109 insertions(+), 180 deletions(-) create mode 100644 src/applications/badges/editor/PhabricatorBadgesEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6c5a01fd0e..d43c9a6cb8 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1783,6 +1783,7 @@ phutil_register_library_map(array( 'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php', 'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php', 'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php', + 'PhabricatorBadgesEditEngine' => 'applications/badges/editor/PhabricatorBadgesEditEngine.php', 'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php', 'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php', 'PhabricatorBadgesIconSet' => 'applications/badges/icon/PhabricatorBadgesIconSet.php', @@ -5945,7 +5946,8 @@ phutil_register_library_map(array( 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability', 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', - 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController', + 'PhabricatorBadgesEditController' => 'PhabricatorPasteController', + 'PhabricatorBadgesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorBadgesIconSet' => 'PhabricatorIconSet', diff --git a/src/applications/badges/controller/PhabricatorBadgesEditController.php b/src/applications/badges/controller/PhabricatorBadgesEditController.php index b1a4fb1030..aef92a983e 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditController.php @@ -1,186 +1,11 @@ getViewer(); - $id = $request->getURIData('id'); - - if ($id) { - $badge = id(new PhabricatorBadgesQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$badge) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $this->requireApplicationCapability( - PhabricatorBadgesCreateCapability::CAPABILITY); - - $badge = PhabricatorBadgesBadge::initializeNewBadge($viewer); - $is_new = true; - } - - if ($is_new) { - $title = pht('Create Badge'); - $button_text = pht('Create Badge'); - $cancel_uri = $this->getApplicationURI(); - } else { - $title = pht( - 'Edit %s', - $badge->getName()); - $button_text = pht('Save Changes'); - $cancel_uri = $this->getApplicationURI('view/'.$id.'/'); - } - - $e_name = true; - $v_name = $badge->getName(); - $v_icon = $badge->getIcon(); - $v_flav = $badge->getFlavor(); - $v_desc = $badge->getDescription(); - $v_qual = $badge->getQuality(); - $v_edit = $badge->getEditPolicy(); - - $validation_exception = null; - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_flav = $request->getStr('flavor'); - $v_desc = $request->getStr('description'); - $v_icon = $request->getStr('icon'); - $v_qual = $request->getStr('quality'); - - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - - $type_name = PhabricatorBadgesTransaction::TYPE_NAME; - $type_flav = PhabricatorBadgesTransaction::TYPE_FLAVOR; - $type_desc = PhabricatorBadgesTransaction::TYPE_DESCRIPTION; - $type_icon = PhabricatorBadgesTransaction::TYPE_ICON; - $type_qual = PhabricatorBadgesTransaction::TYPE_QUALITY; - - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_flav) - ->setNewValue($v_flav); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_desc) - ->setNewValue($v_desc); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_icon) - ->setNewValue($v_icon); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_qual) - ->setNewValue($v_qual); - - $xactions[] = id(new PhabricatorBadgesTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $editor = id(new PhabricatorBadgesEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($badge, $xactions); - $return_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); - return id(new AphrontRedirectResponse())->setURI($return_uri); - - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($badge) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('flavor') - ->setLabel(pht('Flavor Text')) - ->setValue($v_flav)) - ->appendChild( - id(new PHUIFormIconSetControl()) - ->setLabel(pht('Icon')) - ->setName('icon') - ->setIconSet(new PhabricatorBadgesIconSet()) - ->setValue($v_icon)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('quality') - ->setLabel(pht('Quality')) - ->setValue($v_qual) - ->setOptions($badge->getQualityNameMap())) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setName('description') - ->setLabel(pht('Description')) - ->setValue($v_desc)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($badge) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setValue($v_edit) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($button_text) - ->addCancelButton($cancel_uri)); - - $crumbs = $this->buildApplicationCrumbs(); - if ($is_new) { - $crumbs->addTextCrumb(pht('Create Badge')); - } else { - $crumbs->addTextCrumb( - $badge->getName(), - '/badges/view/'.$badge->getID().'/'); - $crumbs->addTextCrumb(pht('Edit')); - } - - $box = id(new PHUIObjectBoxView()) - ->setValidationException($validation_exception) - ->setHeaderText($title) - ->appendChild($form); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - )); + return id(new PhabricatorBadgesEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/badges/editor/PhabricatorBadgesEditEngine.php b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php new file mode 100644 index 0000000000..49a8e85022 --- /dev/null +++ b/src/applications/badges/editor/PhabricatorBadgesEditEngine.php @@ -0,0 +1,98 @@ +getViewer()); + } + + protected function newObjectQuery() { + return new PhabricatorBadgesQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Badge'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return $object->getName(); + } + + protected function getObjectCreateShortText() { + return pht('Create Badge'); + } + + protected function getCommentViewHeaderText($object) { + return pht('Add Comment'); + } + + protected function getCommentViewButtonText($object) { + return pht('Submit'); + } + + protected function getObjectViewURI($object) { + return $object->getViewURI(); + } + + protected function buildCustomEditFields($object) { + + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Badge name.')) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_NAME) + ->setValue($object->getName()), + id(new PhabricatorTextEditField()) + ->setKey('flavor') + ->setLabel(pht('Flavor text')) + ->setDescription(pht('Short description of the badge.')) + ->setValue($object->getFlavor()) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_FLAVOR), + id(new PhabricatorIconSetEditField()) + ->setKey('icon') + ->setLabel(pht('Icon')) + ->setIconSet(new PhabricatorBadgesIconSet()) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_ICON) + ->setConduitDescription(pht('Change the badge icon.')) + ->setConduitTypeDescription(pht('New badge icon.')) + ->setValue($object->getIcon()), + id(new PhabricatorSelectEditField()) + ->setKey('quality') + ->setLabel(pht('Quality')) + ->setValue($object->getQuality()) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_QUALITY) + ->setOptions($object->getQualityNameMap()), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setDescription(pht('Badge long description.')) + ->setTransactionType(PhabricatorBadgesTransaction::TYPE_DESCRIPTION) + ->setValue($object->getDescription()), + ); + } + +} diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index f6ff495a6e..29cccb9f44 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -111,6 +111,10 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return $this->assertAttached($this->recipientPHIDs); } + public function getViewURI() { + return '/badges/view/'.$this->getID().'/'; + } + public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); From ff1bfb64dd56eb41779d9723bba5e52c455c321f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 1 Jan 2016 17:34:19 -0800 Subject: [PATCH 33/83] Reduce the total number of calls to getCallsign() Summary: Ref T4245. Before doing any hard work here, we can dramatically reduce the number of things that make calls to `getCallsign()` to make navigating things easier. Almost all of them only care about a monogram, URI, or display name. Test Plan: - Searched for `r uniquename` in jump nav. - Ran `bin/repository reparse --change rXXXyyyyy --trace`, observed query against bad commit table. - Ran `bin/search index rXXXyyyy --trace --force`, observed proper title when indexing commit. - Browed repository list, saw proper `rXXX` and appropriate link targets. - Mentioned `rXXX` in Remarkup, got a link to the right place. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14923 --- ...habricatorRepositoryRepositoryPHIDType.php | 3 +- .../PhabricatorRepositorySearchEngine.php | 10 +++---- .../search/DiffusionCommitFulltextEngine.php | 6 ++-- .../storage/PhabricatorRepositoryCommit.php | 13 ++++++--- ...habricatorRepositoryCommitParserWorker.php | 28 ++++++++----------- ...atorRepositoryCommitChangeParserWorker.php | 8 ++---- .../engine/PhabricatorJumpNavHandler.php | 2 +- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php index 5a3bca339d..c2cfed251d 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php @@ -40,10 +40,11 @@ final class PhabricatorRepositoryRepositoryPHIDType $monogram = $repository->getMonogram(); $callsign = $repository->getCallsign(); $name = $repository->getName(); + $uri = $repository->getURI(); $handle->setName($monogram); $handle->setFullName("{$monogram} {$name}"); - $handle->setURI("/diffusion/{$callsign}/"); + $handle->setURI($uri); } } diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 04333384f9..1abc964f6c 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -155,15 +155,15 @@ final class PhabricatorRepositorySearchEngine ->setUser($viewer) ->setObject($repository) ->setHeader($repository->getName()) - ->setObjectName('r'.$repository->getCallsign()) - ->setHref($this->getApplicationURI($repository->getCallsign().'/')); + ->setObjectName($repository->getMonogram()) + ->setHref($repository->getURI()); $commit = $repository->getMostRecentCommit(); if ($commit) { $commit_link = DiffusionView::linkCommit( - $repository, - $commit->getCommitIdentifier(), - $commit->getSummary()); + $repository, + $commit->getCommitIdentifier(), + $commit->getSummary()); $item->setSubhead($commit_link); $item->setEpoch($commit->getEpoch()); } diff --git a/src/applications/repository/search/DiffusionCommitFulltextEngine.php b/src/applications/repository/search/DiffusionCommitFulltextEngine.php index 51d2ead5fa..dd87a8bda5 100644 --- a/src/applications/repository/search/DiffusionCommitFulltextEngine.php +++ b/src/applications/repository/search/DiffusionCommitFulltextEngine.php @@ -20,8 +20,10 @@ final class DiffusionCommitFulltextEngine $commit_message = $commit_data->getCommitMessage(); $author_phid = $commit_data->getCommitDetail('authorPHID'); - $title = 'r'.$repository->getCallsign().$commit->getCommitIdentifier(). - ' '.$commit_data->getSummary(); + $monogram = $commit->getMonogram(); + $summary = $commit_data->getSummary(); + + $title = "{$monogram} {$summary}"; $document ->setDocumentCreated($date_created) diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index da6d56b915..70478bb681 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -203,10 +203,7 @@ final class PhabricatorRepositoryCommit } public function getURI() { - $repository = $this->getRepository(); - $callsign = $repository->getCallsign(); - $commit_identifier = $this->getCommitIdentifier(); - return '/r'.$callsign.$commit_identifier; + return '/'.$this->getMonogram(); } /** @@ -251,6 +248,14 @@ final class PhabricatorRepositoryCommit return $this->setAuditStatus($status); } + public function getMonogram() { + $repository = $this->getRepository(); + $callsign = $repository->getCallsign(); + $identifier = $this->getCommitIdentifier(); + + return "r{$callsign}{$identifier}"; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php index cafb28d7f6..aa2aaa270b 100644 --- a/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php +++ b/src/applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php @@ -17,30 +17,26 @@ abstract class PhabricatorRepositoryCommitParserWorker pht('No "%s" in task data.', 'commitID')); } - $commit = id(new PhabricatorRepositoryCommit())->load($commit_id); - + $commit = id(new DiffusionCommitQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs(array($commit_id)) + ->executeOne(); if (!$commit) { throw new PhabricatorWorkerPermanentFailureException( pht('Commit "%s" does not exist.', $commit_id)); } - return $this->commit = $commit; + $this->commit = $commit; + + return $commit; } final protected function doWork() { - if (!$this->loadCommit()) { - return; - } - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIDs(array($this->commit->getRepositoryID())) - ->executeOne(); - if (!$repository) { - return; - } + $commit = $this->loadCommit(); + $repository = $commit->getRepository(); $this->repository = $repository; + $this->parseCommit($repository, $this->commit); } @@ -52,14 +48,14 @@ abstract class PhabricatorRepositoryCommitParserWorker PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit); - protected function isBadCommit($full_commit_name) { + protected function isBadCommit(PhabricatorRepositoryCommit $commit) { $repository = new PhabricatorRepository(); $bad_commit = queryfx_one( $repository->establishConnection('w'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, - $full_commit_name); + $commit->getMonogram()); return (bool)$bad_commit; } diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php index 6563c4442c..6a0161fd06 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php @@ -17,12 +17,8 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker PhabricatorRepository $repository, PhabricatorRepositoryCommit $commit) { - $identifier = $commit->getCommitIdentifier(); - $callsign = $repository->getCallsign(); - $full_name = 'r'.$callsign.$identifier; - - $this->log("%s\n", pht('Parsing %s...', $full_name)); - if ($this->isBadCommit($full_name)) { + $this->log("%s\n", pht('Parsing "%s"...', $commit->getMonogram())); + if ($this->isBadCommit($commit)) { $this->log(pht('This commit is marked bad!')); return; } diff --git a/src/applications/search/engine/PhabricatorJumpNavHandler.php b/src/applications/search/engine/PhabricatorJumpNavHandler.php index 9e810e3977..6c30af98dd 100644 --- a/src/applications/search/engine/PhabricatorJumpNavHandler.php +++ b/src/applications/search/engine/PhabricatorJumpNavHandler.php @@ -57,7 +57,7 @@ final class PhabricatorJumpNavHandler extends Phobject { ->execute(); if (count($repositories) == 1) { // Just one match, jump to repository. - $uri = '/diffusion/'.head($repositories)->getCallsign().'/'; + $uri = head($repositories)->getURI(); } else { // More than one match, jump to search. $uri = urisprintf('/diffusion/?order=name&name=%s', $name); From edcc3232aabca198a8f6e78ad7589da06048d7a5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 1 Jan 2016 18:39:12 -0800 Subject: [PATCH 34/83] Remove calls to getCallsign() in bin/repository scripts Summary: Ref T4245. Prepare these scripts for a callsign-free world. This also makes them more flexible and easier to use. The following are now valid ways to identify a repository for these scripts: ID (`3`), PHID (`PHID-REPO-wxyz`), R (`R3`), r (`rSKYNET`), CALLSIGN (`SKYNET`). In the future, a human-readable label (`skynet`) may also become valid. Test Plan: - Ran `bin/repository reparse --all ...` with `rX`, `X`, `3`, `R3`. - Ran `bin/repository reparse --change ...` with `rXaaa`, including short versions. - Ran `bin/repository update ...` with `rX`, `X`, `3`, `R3`. - Ran `bin/repository refs ...` with various identifiers. - Ran `bin/repository pull ...` with various identifiers. - Ran `bin/repository mirror ...` with various identifiers. - Ran `bin/repository mark-imported ...` with various identifiers. - Ran `bin/repository list`. - Ran `bin/repository importing ...` with various identifiers and examined output. - Ran `bin/repository edit ...` with various identifiers. - Ran `bin/repository discover ...` with various identifiers. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14924 --- ...orRepositoryManagementDiscoverWorkflow.php | 8 ++- ...icatorRepositoryManagementEditWorkflow.php | 11 ++-- ...rRepositoryManagementImportingWorkflow.php | 8 +-- ...icatorRepositoryManagementListWorkflow.php | 2 +- ...positoryManagementMarkImportedWorkflow.php | 22 +++++--- ...atorRepositoryManagementMirrorWorkflow.php | 10 ++-- ...icatorRepositoryManagementPullWorkflow.php | 10 +++- ...icatorRepositoryManagementRefsWorkflow.php | 9 +-- ...torRepositoryManagementReparseWorkflow.php | 54 ++++-------------- ...atorRepositoryManagementUpdateWorkflow.php | 8 +-- ...habricatorRepositoryManagementWorkflow.php | 26 +++++---- ...habricatorRepositoryRepositoryPHIDType.php | 2 +- .../query/PhabricatorRepositoryQuery.php | 56 ++++++++++++++++++- .../storage/PhabricatorRepository.php | 20 +++++++ 14 files changed, 156 insertions(+), 90 deletions(-) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php index 526348d150..03c4745841 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementDiscoverWorkflow $this ->setName('discover') ->setExamples('**discover** [__options__] __repository__ ...') - ->setSynopsis(pht('Discover __repository__, named by callsign.')) + ->setSynopsis(pht('Discover __repository__.')) ->setArguments( array( array( @@ -31,14 +31,16 @@ final class PhabricatorRepositoryManagementDiscoverWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to discover, by callsign.')); + pht('Specify one or more repositories to discover.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { $console->writeOut( "%s\n", - pht("Discovering '%s'...", $repo->getCallsign())); + pht( + 'Discovering "%s"...', + $repo->getDisplayName())); id(new PhabricatorRepositoryDiscoveryEngine()) ->setRepository($repo) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php index adee507c51..83447fcc18 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php @@ -9,8 +9,7 @@ final class PhabricatorRepositoryManagementEditWorkflow ->setExamples('**edit** --as __username__ __repository__ ...') ->setSynopsis( pht( - 'Edit __repository__, named by callsign '. - '(will eventually be deprecated by Conduit).')) + 'Edit __repository__ (will eventually be deprecated by Conduit).')) ->setArguments( array( array( @@ -45,7 +44,7 @@ final class PhabricatorRepositoryManagementEditWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to edit, by callsign.')); + pht('Specify one or more repositories to edit.')); } $console = PhutilConsole::getConsole(); @@ -76,7 +75,11 @@ final class PhabricatorRepositoryManagementEditWorkflow } foreach ($repos as $repo) { - $console->writeOut("%s\n", pht("Editing '%s'...", $repo->getCallsign())); + $console->writeOut( + "%s\n", + pht( + 'Editing "%s"...', + $repo->getDisplayName())); $xactions = array(); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php index 4da1e03c9b..acb533999b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php @@ -9,8 +9,7 @@ final class PhabricatorRepositoryManagementImportingWorkflow ->setExamples('**importing** __repository__ ...') ->setSynopsis( pht( - 'Show commits in __repository__, named by callsign, which are '. - 'still importing.')) + 'Show commits in __repository__ which are still importing.')) ->setArguments( array( array( @@ -30,8 +29,7 @@ final class PhabricatorRepositoryManagementImportingWorkflow if (!$repos) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more repositories to find importing commits for, '. - 'by callsign.')); + 'Specify one or more repositories to find importing commits for.')); } $repos = mpull($repos, null, 'getID'); @@ -54,7 +52,7 @@ final class PhabricatorRepositoryManagementImportingWorkflow $repo = $repos[$row['repositoryID']]; $identifier = $row['commitIdentifier']; - $console->writeOut('%s', 'r'.$repo->getCallsign().$identifier); + $console->writeOut('%s', $repo->formatCommitName($identifier)); if (!$args->getArg('simple')) { $status = $row['importStatus']; diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php index a663142987..24f4c68b9b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php @@ -18,7 +18,7 @@ final class PhabricatorRepositoryManagementListWorkflow ->execute(); if ($repos) { foreach ($repos as $repo) { - $console->writeOut("%s\n", $repo->getCallsign()); + $console->writeOut("%s\n", $repo->getMonogram()); } } else { $console->writeErr("%s\n", pht('There are no repositories.')); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php index fb14261df0..034617deb2 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementMarkImportedWorkflow $this ->setName('mark-imported') ->setExamples('**mark-imported** __repository__ ...') - ->setSynopsis(pht('Mark __repository__, named by callsign, as imported.')) + ->setSynopsis(pht('Mark __repository__ as imported.')) ->setArguments( array( array( @@ -26,32 +26,40 @@ final class PhabricatorRepositoryManagementMarkImportedWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to mark imported, by callsign.')); + pht('Specify one or more repositories to mark imported.')); } $new_importing_value = (bool)$args->getArg('mark-not-imported'); $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { - $callsign = $repo->getCallsign(); + $name = $repo->getDisplayName(); if ($repo->isImporting() && $new_importing_value) { $console->writeOut( "%s\n", - pht("Repository '%s' is already importing.", $callsign)); + pht( + 'Repository "%s" is already importing.', + $name)); } else if (!$repo->isImporting() && !$new_importing_value) { $console->writeOut( "%s\n", - pht("Repository '%s' is already imported.", $callsign)); + pht( + 'Repository "%s" is already imported.', + $name)); } else { if ($new_importing_value) { $console->writeOut( "%s\n", - pht("Marking repository '%s' as importing.", $callsign)); + pht( + 'Marking repository "%s" as importing.', + $name)); } else { $console->writeOut( "%s\n", - pht("Marking repository '%s' as imported.", $callsign)); + pht( + 'Marking repository "%s" as imported.', + $name)); } $repo->setDetail('importing', $new_importing_value); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php index 4a4c732558..99a2b08c8b 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php @@ -8,7 +8,7 @@ final class PhabricatorRepositoryManagementMirrorWorkflow ->setName('mirror') ->setExamples('**mirror** [__options__] __repository__ ...') ->setSynopsis( - pht('Push __repository__, named by callsign, to mirrors.')) + pht('Push __repository__ to mirrors.')) ->setArguments( array( array( @@ -28,14 +28,16 @@ final class PhabricatorRepositoryManagementMirrorWorkflow if (!$repos) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more repositories to push to mirrors, by callsign.')); + 'Specify one or more repositories to push to mirrors.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { $console->writeOut( "%s\n", - pht('Pushing "%s" to mirrors...', $repo->getCallsign())); + pht( + "Pushing '%s' to mirrors...", + $repo->getDisplayName())); $engine = id(new PhabricatorRepositoryMirrorEngine()) ->setRepository($repo) @@ -43,7 +45,7 @@ final class PhabricatorRepositoryManagementMirrorWorkflow ->pushToMirrors(); } - $console->writeOut('%s\b', pht('Done.')); + $console->writeOut("%s\n", pht('Done.')); return 0; } diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php index b58c56d46b..f41f8b95a1 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementPullWorkflow $this ->setName('pull') ->setExamples('**pull** __repository__ ...') - ->setSynopsis(pht('Pull __repository__, named by callsign.')) + ->setSynopsis(pht('Pull __repository__.')) ->setArguments( array( array( @@ -26,12 +26,16 @@ final class PhabricatorRepositoryManagementPullWorkflow if (!$repos) { throw new PhutilArgumentUsageException( - pht('Specify one or more repositories to pull, by callsign.')); + pht('Specify one or more repositories to pull.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { - $console->writeOut("%s\n", pht("Pulling '%s'...", $repo->getCallsign())); + $console->writeOut( + "%s\n", + pht( + 'Pulling "%s"...', + $repo->getDisplayName())); id(new PhabricatorRepositoryPullEngine()) ->setRepository($repo) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php index 5d5dd169e8..ad9bbbd0db 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php @@ -7,7 +7,7 @@ final class PhabricatorRepositoryManagementRefsWorkflow $this ->setName('refs') ->setExamples('**refs** [__options__] __repository__ ...') - ->setSynopsis(pht('Update refs in __repository__, named by callsign.')) + ->setSynopsis(pht('Update refs in __repository__.')) ->setArguments( array( array( @@ -27,15 +27,16 @@ final class PhabricatorRepositoryManagementRefsWorkflow if (!$repos) { throw new PhutilArgumentUsageException( pht( - 'Specify one or more repositories to update refs for, '. - 'by callsign.')); + 'Specify one or more repositories to update refs for.')); } $console = PhutilConsole::getConsole(); foreach ($repos as $repo) { $console->writeOut( "%s\n", - pht("Updating refs in '%s'...", $repo->getCallsign())); + pht( + 'Updating refs in "%s"...', + $repo->getDisplayName())); $engine = id(new PhabricatorRepositoryRefEngine()) ->setRepository($repo) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php index 524cc606aa..2067fdbd11 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php @@ -28,7 +28,7 @@ final class PhabricatorRepositoryManagementReparseWorkflow ), array( 'name' => 'all', - 'param' => 'callsign or phid', + 'param' => 'repository', 'help' => pht( 'Reparse all commits in the specified repository. This mode '. 'queues parsers into the task queue; you must run taskmasters '. @@ -192,16 +192,16 @@ final class PhabricatorRepositoryManagementReparseWorkflow $commits = array(); if ($all_from_repo) { - $repository = id(new PhabricatorRepository())->loadOneWhere( - 'callsign = %s OR phid = %s', - $all_from_repo, - $all_from_repo); + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIdentifiers(array($all_from_repo)) + ->executeOne(); + if (!$repository) { throw new PhutilArgumentUsageException( - pht('Unknown repository %s!', $all_from_repo)); + pht('Unknown repository "%s"!', $all_from_repo)); } - $query = id(new DiffusionCommitQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withRepository($repository); @@ -216,46 +216,14 @@ final class PhabricatorRepositoryManagementReparseWorkflow $commits = $query->execute(); - $callsign = $repository->getCallsign(); if (!$commits) { throw new PhutilArgumentUsageException( pht( - 'No commits have been discovered in %s repository!', - $callsign)); + 'No commits have been discovered in the "%s" repository!', + $repository->getDisplayName())); } } else { - $commits = array(); - foreach ($reparse_what as $identifier) { - $matches = null; - if (!preg_match('/r([A-Z]+)([a-z0-9]+)/', $identifier, $matches)) { - throw new PhutilArgumentUsageException(pht( - "Can't parse commit identifier: %s", - $identifier)); - } - $callsign = $matches[1]; - $commit_identifier = $matches[2]; - $repository = id(new PhabricatorRepository())->loadOneWhere( - 'callsign = %s', - $callsign); - if (!$repository) { - throw new PhutilArgumentUsageException(pht( - "No repository with callsign '%s'!", - $callsign)); - } - $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( - 'repositoryID = %d AND commitIdentifier = %s', - $repository->getID(), - $commit_identifier); - if (!$commit) { - throw new PhutilArgumentUsageException(pht( - "No matching commit '%s' in repository '%s'. ". - "(For git and mercurial repositories, you must specify the entire ". - "commit hash.)", - $commit_identifier, - $callsign)); - } - $commits[] = $commit; - } + $commits = $this->loadNamedCommits($reparse_what); } if ($all_from_repo && !$force_local) { @@ -273,6 +241,8 @@ final class PhabricatorRepositoryManagementReparseWorkflow $tasks = array(); foreach ($commits as $commit) { + $repository = $commit->getRepository(); + if ($importing) { $status = $commit->getImportStatus(); // Find the first missing import step and queue that up. diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php index 99b4a3adc6..a754f3c3d4 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php @@ -20,9 +20,9 @@ final class PhabricatorRepositoryManagementUpdateWorkflow ->setExamples('**update** [options] __repository__') ->setSynopsis( pht( - 'Update __repository__, named by callsign. '. - 'This performs the __pull__, __discover__, __ref__ and __mirror__ '. - 'operations and is primarily an internal workflow.')) + 'Update __repository__. This performs the __pull__, __discover__, '. + '__ref__ and __mirror__ operations and is primarily an internal '. + 'workflow.')) ->setArguments( array( array( @@ -47,7 +47,7 @@ final class PhabricatorRepositoryManagementUpdateWorkflow $repos = $this->loadRepositories($args, 'repos'); if (count($repos) !== 1) { throw new PhutilArgumentUsageException( - pht('Specify exactly one repository to update, by callsign.')); + pht('Specify exactly one repository to update.')); } $repository = head($repos); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php index 26ab7c315e..300f8ec50f 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementWorkflow.php @@ -4,26 +4,32 @@ abstract class PhabricatorRepositoryManagementWorkflow extends PhabricatorManagementWorkflow { protected function loadRepositories(PhutilArgumentParser $args, $param) { - $callsigns = $args->getArg($param); + $identifiers = $args->getArg($param); - if (!$callsigns) { + if (!$identifiers) { return null; } - $repos = id(new PhabricatorRepositoryQuery()) + $query = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) - ->withCallsigns($callsigns) - ->execute(); + ->withIdentifiers($identifiers); - $repos = mpull($repos, null, 'getCallsign'); - foreach ($callsigns as $callsign) { - if (empty($repos[$callsign])) { + $query->execute(); + + $map = $query->getIdentifierMap(); + foreach ($identifiers as $identifier) { + if (empty($map[$identifier])) { throw new PhutilArgumentUsageException( - pht("No repository with callsign '%s' exists!", $callsign)); + pht( + 'Repository "%s" does not exist!', + $identifier)); } } - return $repos; + // Reorder repositories according to argument order. + $repositories = array_select_keys($map, $identifiers); + + return array_values($repositories); } protected function loadCommits(PhutilArgumentParser $args, $param) { diff --git a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php index c2cfed251d..704de09755 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php @@ -49,7 +49,7 @@ final class PhabricatorRepositoryRepositoryPHIDType } public function canLoadNamedObject($name) { - return preg_match('/^r[A-Z]+|R[0-9]+$/', $name); + return preg_match('/^r[A-Z]+|R[1-9]\d*\z/', $name); } public function loadNamedObjects( diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 7595cd0c8e..9bb15d1804 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -15,6 +15,7 @@ final class PhabricatorRepositoryQuery private $numericIdentifiers; private $callsignIdentifiers; private $phidIdentifiers; + private $monogramIdentifiers; private $identifierMap; @@ -48,10 +49,16 @@ final class PhabricatorRepositoryQuery } public function withIdentifiers(array $identifiers) { - $ids = array(); $callsigns = array(); $phids = array(); + $ids = array(); + $callsigns = array(); + $phids = array(); + $monograms = array(); + foreach ($identifiers as $identifier) { if (ctype_digit($identifier)) { $ids[$identifier] = $identifier; + } else if (preg_match('/^(r[A-Z]+)|(R[1-9]\d*)\z/', $identifier)) { + $monograms[$identifier] = $identifier; } else { $repository_type = PhabricatorRepositoryRepositoryPHIDType::TYPECONST; if (phid_get_type($identifier) === $repository_type) { @@ -65,6 +72,8 @@ final class PhabricatorRepositoryQuery $this->numericIdentifiers = $ids; $this->callsignIdentifiers = $callsigns; $this->phidIdentifiers = $phids; + $this->monogramIdentifiers = $monograms; + return $this; } @@ -273,6 +282,21 @@ final class PhabricatorRepositoryQuery } } + if ($this->monogramIdentifiers) { + $monogram_map = array(); + foreach ($repositories as $repository) { + foreach ($repository->getAllMonograms() as $monogram) { + $monogram_map[$monogram] = $repository; + } + } + + foreach ($this->monogramIdentifiers as $monogram) { + if (isset($monogram_map[$monogram])) { + $this->identifierMap[$monogram] = $monogram_map[$monogram]; + } + } + } + return $repositories; } @@ -447,7 +471,8 @@ final class PhabricatorRepositoryQuery if ($this->numericIdentifiers || $this->callsignIdentifiers || - $this->phidIdentifiers) { + $this->phidIdentifiers || + $this->monogramIdentifiers) { $identifier_clause = array(); if ($this->numericIdentifiers) { @@ -471,6 +496,33 @@ final class PhabricatorRepositoryQuery $this->phidIdentifiers); } + if ($this->monogramIdentifiers) { + $monogram_callsigns = array(); + $monogram_ids = array(); + + foreach ($this->monogramIdentifiers as $identifier) { + if ($identifier[0] == 'r') { + $monogram_callsigns[] = substr($identifier, 1); + } else { + $monogram_ids[] = substr($identifier, 1); + } + } + + if ($monogram_ids) { + $identifier_clause[] = qsprintf( + $conn, + 'r.id IN (%Ld)', + $monogram_ids); + } + + if ($monogram_callsigns) { + $identifier_clause[] = qsprintf( + $conn, + 'r.callsign IN (%Ls)', + $monogram_callsigns); + } + } + $where = array('('.implode(' OR ', $identifier_clause).')'); } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index c7ef71e9b3..c65a51228f 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -151,6 +151,26 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return 'r'.$this->getCallsign(); } + public function getDisplayName() { + // TODO: This is intended to produce a human-readable name that is not + // necessarily a global, unique identifier. Eventually, it may just return + // a string like "skynet" instead of "rSKYNET". + return $this->getMonogram(); + } + + public function getAllMonograms() { + $monograms = array(); + + $monograms[] = 'R'.$this->getID(); + + $callsign = $this->getCallsign(); + if (strlen($callsign)) { + $monograms[] = 'r'.$callsign; + } + + return $monograms; + } + public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } From 2328e739b760eb56ac8030c731342878ef99b443 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 05:16:10 -0800 Subject: [PATCH 35/83] Fix an issue where Phame could post to the wrong blog When you `getInt()` an array, PHP decides the array has value `1`. This would cause us to post to blog #1 incorrectly. I didn't catch this locally because I happened to be posting to blog #1. Stop us from interpreting array values as `1`, and fix blog interpretation. This approach is a little messy (projects has the same issue) but I'll see if I can clean it up in some future change. Auditors: chad --- src/aphront/AphrontRequest.php | 5 ++++ .../post/PhamePostEditController.php | 28 +++++++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index f15b289d38..e88a5a5e36 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -123,6 +123,11 @@ final class AphrontRequest extends Phobject { */ public function getInt($name, $default = null) { if (isset($this->requestData[$name])) { + // Converting from array to int is "undefined". Don't rely on whatever + // PHP decides to do. + if (is_array($this->requestData[$name])) { + return $default; + } return (int)$this->requestData[$name]; } else { return $default; diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php index 923af4d967..91df519adb 100644 --- a/src/applications/phame/controller/post/PhamePostEditController.php +++ b/src/applications/phame/controller/post/PhamePostEditController.php @@ -32,18 +32,27 @@ final class PhamePostEditController extends PhamePostController { } $blog_id = $post->getBlog()->getID(); } else { - $blog_id = $request->getInt('blog'); + $blog_id = head($request->getArr('blog')); + if (!$blog_id) { + $blog_id = $request->getStr('blog'); + } } - $blog = id(new PhameBlogQuery()) + $query = id(new PhameBlogQuery()) ->setViewer($viewer) - ->withIDs(array($blog_id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); + )); + + if (ctype_digit($blog_id)) { + $query->withIDs(array($blog_id)); + } else { + $query->withPHIDs(array($blog_id)); + } + + $blog = $query->executeOne(); if (!$blog) { return new Aphront404Response(); } @@ -60,10 +69,11 @@ final class PhamePostEditController extends PhamePostController { $crumbs = parent::buildApplicationCrumbs(); $blog = $this->getBlog(); - - $crumbs->addTextCrumb( - $blog->getName(), - $blog->getViewURI()); + if ($blog) { + $crumbs->addTextCrumb( + $blog->getName(), + $blog->getViewURI()); + } return $crumbs; } From 1b4f5e38cec3230a620db3e3ab8bbf65bf8efbc1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 05:09:10 -0800 Subject: [PATCH 36/83] Fix an issue with rendering some commit hovercards Summary: This logic wasn't quite right. Test Plan: Hovered over a recognized commit, got a valid hovercard Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14925 --- .../engineextension/DiffusionHovercardEngineExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php index a8193e569f..64caee879f 100644 --- a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php @@ -28,7 +28,7 @@ final class DiffusionHovercardEngineExtension $author_phid = $commit->getAuthorPHID(); if ($author_phid) { - $author = $viewer->loadHandle($author)->renderLink(); + $author = $viewer->renderHandle($author_phid); } else { $commit_data = $commit->loadCommitData(); $author = phutil_tag('em', array(), $commit_data->getAuthorName()); From d9e034f02ca0bfc266d5975b75374a5d20f77964 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 05:53:55 -0800 Subject: [PATCH 37/83] Remove calls to getCallsign() from repository daemons Summary: Ref T4245. These are all descriptive or UI-facing. Test Plan: - Ran `bin/repository pull ...` with various identifiers. - Ran `bin/repository mirror ...` with various identifiers. - Ran `bin/repository discover ...` with various identifiers. - Ran `bin/phd debug pull X Y --not Z` with various identifiers. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14926 --- .../PhabricatorRepositoryPullLocalDaemon.php | 41 ++++++++++++------- .../PhabricatorRepositoryDiscoveryEngine.php | 6 +-- .../PhabricatorRepositoryMirrorEngine.php | 2 +- .../PhabricatorRepositoryPullEngine.php | 25 +++++------ 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index bccb58e35a..21b0e13413 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -6,7 +6,7 @@ * * By default, the daemon pulls **every** repository. If you want it to be * responsible for only some repositories, you can launch it with a list of - * PHIDs or callsigns: + * repositories: * * ./phd launch repositorypulllocal -- X Q Z * @@ -228,9 +228,8 @@ final class PhabricatorRepositoryPullLocalDaemon $flags[] = '--no-discovery'; } - $callsign = $repository->getCallsign(); - - $future = new ExecFuture('%s update %Ls -- %s', $bin, $flags, $callsign); + $monogram = $repository->getMonogram(); + $future = new ExecFuture('%s update %Ls -- %s', $bin, $flags, $monogram); // Sometimes, the underlying VCS commands will hang indefinitely. We've // observed this occasionally with GitHub, and other users have observed @@ -303,30 +302,44 @@ final class PhabricatorRepositoryPullLocalDaemon ->setViewer($this->getViewer()); if ($include) { - $query->withCallsigns($include); + $query->withIdentifiers($include); } $repositories = $query->execute(); + $repositories = mpull($repositories, null, 'getPHID'); if ($include) { - $by_callsign = mpull($repositories, null, 'getCallsign'); - foreach ($include as $name) { - if (empty($by_callsign[$name])) { + $map = $query->getIdentifierMap(); + foreach ($include as $identifier) { + if (empty($map[$identifier])) { throw new Exception( pht( - "No repository exists with callsign '%s'!", - $name)); + 'No repository "%s" exists!', + $identifier)); } } } if ($exclude) { - $exclude = array_fuse($exclude); - foreach ($repositories as $key => $repository) { - if (isset($exclude[$repository->getCallsign()])) { - unset($repositories[$key]); + $xquery = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withIdentifiers($exclude); + + $excluded_repos = $xquery->execute(); + $xmap = $xquery->getIdentifierMap(); + + foreach ($exclude as $identifier) { + if (empty($xmap[$identifier])) { + throw new Exception( + pht( + 'No repository "%s" exists!', + $identifier)); } } + + foreach ($excluded_repos as $excluded_repo) { + unset($repositories[$excluded_repo->getPHID()]); + } } foreach ($repositories as $key => $repository) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index 407940e474..3c3a0526be 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -100,8 +100,8 @@ final class PhabricatorRepositoryDiscoveryEngine $this->log( pht( - 'Discovering commits in repository %s.', - $repository->getCallsign())); + 'Discovering commits in repository "%s".', + $repository->getDisplayName())); $this->fillCommitCache(array_values($branches)); @@ -244,7 +244,7 @@ final class PhabricatorRepositoryDiscoveryEngine 'configured URI is "%s". To resolve this error, set the remote URI '. 'to point at the repository root. If you want to import only part '. 'of a Subversion repository, use the "Import Only" option.', - $repository->getCallsign(), + $repository->getDisplayName(), $remote_root, $expect_root)); } diff --git a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php index 8d3dd9f8f9..e4e65aab6f 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryMirrorEngine.php @@ -37,7 +37,7 @@ final class PhabricatorRepositoryMirrorEngine throw new PhutilAggregateException( pht( 'Exceptions occurred while mirroring the "%s" repository.', - $repository->getCallsign()), + $repository->getDisplayName()), $exceptions); } } diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 299310be27..30ab234027 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -29,7 +29,6 @@ final class PhabricatorRepositoryPullEngine $is_svn = false; $vcs = $repository->getVersionControlSystem(); - $callsign = $repository->getCallsign(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: @@ -37,9 +36,9 @@ final class PhabricatorRepositoryPullEngine if (!$repository->isHosted()) { $this->skipPull( pht( - "Repository '%s' is a non-hosted Subversion repository, which ". - "does not require a local working copy to be pulled.", - $callsign)); + 'Repository "%s" is a non-hosted Subversion repository, which '. + 'does not require a local working copy to be pulled.', + $repository->getDisplayName())); return; } $is_svn = true; @@ -55,13 +54,12 @@ final class PhabricatorRepositoryPullEngine break; } - $callsign = $repository->getCallsign(); $local_path = $repository->getLocalPath(); if ($local_path === null) { $this->abortPull( pht( - "No local path is configured for repository '%s'.", - $callsign)); + 'No local path is configured for repository "%s".', + $repository->getDisplayName())); } try { @@ -73,8 +71,8 @@ final class PhabricatorRepositoryPullEngine if (!Filesystem::pathExists($local_path)) { $this->logPull( pht( - "Creating a new working copy for repository '%s'.", - $callsign)); + 'Creating a new working copy for repository "%s".', + $repository->getDisplayName())); if ($is_git) { $this->executeGitCreate(); } else if ($is_hg) { @@ -86,8 +84,8 @@ final class PhabricatorRepositoryPullEngine if (!$repository->isHosted()) { $this->logPull( pht( - "Updating the working copy for repository '%s'.", - $callsign)); + 'Updating the working copy for repository "%s".', + $repository->getDisplayName())); if ($is_git) { $this->verifyGitOrigin($repository); $this->executeGitUpdate(); @@ -113,7 +111,10 @@ final class PhabricatorRepositoryPullEngine } catch (Exception $ex) { $this->abortPull( - pht('Pull of "%s" failed: %s', $callsign, $ex->getMessage()), + pht( + "Pull of '%s' failed: %s", + $repository->getDisplayName(), + $ex->getMessage()), $ex); } From 37532a0bf001ad569150c9e36562fd4ad351aa32 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 06:16:49 -0800 Subject: [PATCH 38/83] Remove various additional calls to getCallsign() Summary: Ref T4245. These are all straightforward to remove. Test Plan: - Edited paths in a package. - Ran `bin/audit delete --repositories ...` with various identifiers. - Searched by repository for `R3`, `rAAAA` in Harbormaster. - Did a Herald dry run on a commit. - Browsed commits, made comments. - Viewed a Releeph product list. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14927 --- .../editor/PhabricatorAuditCommentEditor.php | 2 +- ...abricatorAuditManagementDeleteWorkflow.php | 21 ++++++++++--------- .../diffusion/herald/HeraldCommitAdapter.php | 5 +---- .../HarbormasterBuildableSearchEngine.php | 2 +- .../PhabricatorOwnersPathsController.php | 2 +- .../ReleephProductCreateController.php | 6 +++--- ...entialReleephRequestFieldSpecification.php | 2 +- .../query/ReleephProductSearchEngine.php | 4 ++-- 8 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php index 03476f70a7..8a17999ee0 100644 --- a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php @@ -46,7 +46,7 @@ final class PhabricatorAuditCommentEditor extends PhabricatorEditor { 'diffusion-audit-'.$commit->getPHID(), pht( 'Commit %s', - 'r'.$repository->getCallsign().$commit->getCommitIdentifier()), + $commit->getMonogram()), ); } diff --git a/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php b/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php index fa9dbff635..95fbdb430f 100644 --- a/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php +++ b/src/applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php @@ -222,22 +222,23 @@ final class PhabricatorAuditManagementDeleteWorkflow return $list; } - private function loadRepos($callsigns) { - $callsigns = $this->parseList($callsigns); - if (!$callsigns) { + private function loadRepos($identifiers) { + $identifiers = $this->parseList($identifiers); + if (!$identifiers) { return null; } - $repos = id(new PhabricatorRepositoryQuery()) + $query = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) - ->withCallsigns($callsigns) - ->execute(); - $repos = mpull($repos, null, 'getCallsign'); + ->withIdentifiers($identifiers); - foreach ($callsigns as $sign) { - if (empty($repos[$sign])) { + $repos = $query->execute(); + + $map = $query->getIdentifierMap(); + foreach ($identifiers as $identifier) { + if (empty($map[$identifier])) { throw new PhutilArgumentUsageException( - pht('No such repository with callsign "%s"!', $sign)); + pht('No repository "%s" exists!', $identifier)); } } diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 9baac6f2ad..67234edeab 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -130,10 +130,7 @@ final class HeraldCommitAdapter } public function getHeraldName() { - return - 'r'. - $this->repository->getCallsign(). - $this->commit->getCommitIdentifier(); + return $this->commit->getMonogram(); } public function loadAffectedPaths() { diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index 094968437b..195a68a695 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -112,7 +112,7 @@ final class HarbormasterBuildableSearchEngine } else if ($object instanceof DifferentialDiff) { $diff_names[] = $object->getID(); } else if ($object instanceof PhabricatorRepository) { - $repository_names[] = 'r'.$object->getCallsign(); + $repository_names[] = $object->getMonogram(); } else if ($object instanceof PhabricatorRepositoryCommit) { $repository = $object->getRepository(); $commit_names[] = $repository->formatCommitName( diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index ad278d5296..b02f5437be 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -83,7 +83,7 @@ final class PhabricatorOwnersPathsController } } - $repos = mpull($repos, 'getCallsign', 'getPHID'); + $repos = mpull($repos, 'getMonogram', 'getPHID'); asort($repos); $template = new AphrontTypeaheadTemplateView(); diff --git a/src/applications/releeph/controller/product/ReleephProductCreateController.php b/src/applications/releeph/controller/product/ReleephProductCreateController.php index 9772b293fe..2aedf8cdf1 100644 --- a/src/applications/releeph/controller/product/ReleephProductCreateController.php +++ b/src/applications/releeph/controller/product/ReleephProductCreateController.php @@ -121,11 +121,11 @@ final class ReleephProductCreateController extends ReleephProductController { foreach ($repos as $repo_id => $repo) { $repo_name = $repo->getName(); - $callsign = $repo->getCallsign(); - $choices[$repo->getPHID()] = "r{$callsign} ({$repo_name})"; + $display = $repo->getDisplayName(); + $choices[$repo->getPHID()] = "{$display} ({$repo_name})"; } - ksort($choices); + asort($choices); return $choices; } diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index 2f06f50f13..26e708a00c 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -372,7 +372,7 @@ final class DifferentialReleephRequestFieldSpecification extends Phobject { 'part of a Releeph branch, but also has %d path change(s) not '. 'part of a Releeph branch!', $commit->getCommitIdentifier(), - $repo->getCallsign(), + $repo->getDisplayName(), count($in_branch), count($ex_branch)); phlog($error); diff --git a/src/applications/releeph/query/ReleephProductSearchEngine.php b/src/applications/releeph/query/ReleephProductSearchEngine.php index ca92068b6f..f56125a687 100644 --- a/src/applications/releeph/query/ReleephProductSearchEngine.php +++ b/src/applications/releeph/query/ReleephProductSearchEngine.php @@ -114,9 +114,9 @@ final class ReleephProductSearchEngine phutil_tag( 'a', array( - 'href' => '/diffusion/'.$repo->getCallsign().'/', + 'href' => $repo->getURI(), ), - 'r'.$repo->getCallsign())); + $repo->getMonogram())); $list->addItem($item); } From 9d84eb4c747d33fc7a762d13387e49b49c9c0c0b Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 11:03:05 -0800 Subject: [PATCH 39/83] Allow Conduit API methods in Diffusion to accept any repository identifier Summary: Ref T4245. Broaden support to include "ABCD", "rABCD", "1234", "R1234", etc. This doesn't change the old behavior, just accepts more stuff. Test Plan: - Browsed Diffusion. - Made various calls via API console. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14928 --- .../DiffusionQueryConduitAPIMethod.php | 12 +- .../diffusion/request/DiffusionRequest.php | 106 ++++++++++-------- 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php index e9f5681f9f..4b3eb6721f 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php @@ -38,7 +38,7 @@ abstract class DiffusionQueryConduitAPIMethod return $this->defineCustomErrorTypes() + array( 'ERR-UNKNOWN-REPOSITORY' => - pht('There is no repository with that callsign.'), + pht('There is no matching repository.'), 'ERR-UNKNOWN-VCS-TYPE' => pht('Unknown repository VCS type.'), 'ERR-UNSUPPORTED-VCS' => @@ -56,7 +56,8 @@ abstract class DiffusionQueryConduitAPIMethod final protected function defineParamTypes() { return $this->defineCustomParamTypes() + array( - 'callsign' => 'required string', + 'callsign' => 'optional string (deprecated)', + 'repository' => 'optional string', 'branch' => 'optional string', ); } @@ -95,10 +96,15 @@ abstract class DiffusionQueryConduitAPIMethod * should occur after @{method:getResult}, like formatting a timestamp. */ final protected function execute(ConduitAPIRequest $request) { + $identifier = $request->getValue('repository'); + if ($identifier === null) { + $identifier = $request->getValue('callsign'); + } + $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $request->getUser(), - 'callsign' => $request->getValue('callsign'), + 'repository' => $identifier, 'branch' => $request->getValue('branch'), 'path' => $request->getValue('path'), 'commit' => $request->getValue('commit'), diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 6cf9aba8b3..fe28364f5d 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -9,7 +9,6 @@ */ abstract class DiffusionRequest extends Phobject { - protected $callsign; protected $path; protected $line; protected $branch; @@ -47,9 +46,8 @@ abstract class DiffusionRequest extends Phobject { * * Parameters are: * - * - `callsign` Repository callsign. Provide this or `repository`. - * - `user` Viewing user. Required if `callsign` is provided. - * - `repository` Repository object. Provide this or `callsign`. + * - `repository` Repository object or identifier. + * - `user` Viewing user. Required if `repository` is an identifier. * - `branch` Optional, branch name. * - `path` Optional, file path. * - `commit` Optional, commit identifier. @@ -60,30 +58,51 @@ abstract class DiffusionRequest extends Phobject { * @task new */ final public static function newFromDictionary(array $data) { - if (isset($data['repository']) && isset($data['callsign'])) { + $repository_key = 'repository'; + $identifier_key = 'callsign'; + $viewer_key = 'user'; + + $repository = idx($data, $repository_key); + $identifier = idx($data, $identifier_key); + + $have_repository = ($repository !== null); + $have_identifier = ($identifier !== null); + + if ($have_repository && $have_identifier) { throw new Exception( pht( - "Specify '%s' or '%s', but not both.", - 'repository', - 'callsign')); - } else if (!isset($data['repository']) && !isset($data['callsign'])) { - throw new Exception( - pht( - "One of '%s' and '%s' is required.", - 'repository', - 'callsign')); - } else if (isset($data['callsign']) && empty($data['user'])) { - throw new Exception( - pht( - "Parameter '%s' is required if '%s' is provided.", - 'user', - 'callsign')); + 'Specify "%s" or "%s", but not both.', + $repository_key, + $identifier_key)); } - if (isset($data['repository'])) { - $object = self::newFromRepository($data['repository']); + if (!$have_repository && !$have_identifier) { + throw new Exception( + pht( + 'One of "%s" and "%s" is required.', + $repository_key, + $identifier_key)); + } + + if ($have_repository) { + if (!($repository instanceof PhabricatorRepository)) { + if (empty($data[$viewer_key])) { + throw new Exception( + pht( + 'Parameter "%s" is required if "%s" is provided.', + $viewer_key, + $identifier_key)); + } + + $identifier = $repository; + $repository = null; + } + } + + if ($identifier !== null) { + $object = self::newFromIdentifier($identifier, $data[$viewer_key]); } else { - $object = self::newFromCallsign($data['callsign'], $data['user']); + $object = self::newFromRepository($repository); } $object->initializeFromDictionary($data); @@ -105,8 +124,8 @@ abstract class DiffusionRequest extends Phobject { array $data, AphrontRequest $request) { - $callsign = phutil_unescape_uri_path_component(idx($data, 'callsign')); - $object = self::newFromCallsign($callsign, $request->getUser()); + $identifier = phutil_unescape_uri_path_component(idx($data, 'callsign')); + $object = self::newFromIdentifier($identifier, $request->getUser()); $use_branches = $object->supportsBranches(); @@ -141,22 +160,22 @@ abstract class DiffusionRequest extends Phobject { /** * Internal. Use @{method:newFromDictionary}, not this method. * - * @param string Repository callsign. + * @param string Repository identifier. * @param PhabricatorUser Viewing user. * @return DiffusionRequest New request object. * @task new */ - final private static function newFromCallsign( - $callsign, + final private static function newFromIdentifier( + $identifier, PhabricatorUser $viewer) { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withCallsigns(array($callsign)) + ->withIdentifiers(array($identifier)) ->executeOne(); if (!$repository) { - throw new Exception(pht("No such repository '%s'.", $callsign)); + throw new Exception(pht("No such repository '%s'.", $identifier)); } return self::newFromRepository($repository); @@ -189,7 +208,6 @@ abstract class DiffusionRequest extends Phobject { $object = new $class(); $object->repository = $repository; - $object->callsign = $repository->getCallsign(); return $object; } @@ -239,7 +257,7 @@ abstract class DiffusionRequest extends Phobject { } public function getCallsign() { - return $this->callsign; + return $this->getRepository()->getCallsign(); } public function setPath($path) { @@ -702,28 +720,26 @@ abstract class DiffusionRequest extends Phobject { protected function raisePermissionException() { $host = php_uname('n'); - $callsign = $this->getRepository()->getCallsign(); throw new DiffusionSetupException( pht( - "The clone of this repository ('%s') on the local machine ('%s') ". - "could not be read. Ensure that the repository is in a ". - "location where the web server has read permissions.", - $callsign, + 'The clone of this repository ("%s") on the local machine ("%s") '. + 'could not be read. Ensure that the repository is in a '. + 'location where the web server has read permissions.', + $this->getRepository()->getDisplayName(), $host)); } protected function raiseCloneException() { $host = php_uname('n'); - $callsign = $this->getRepository()->getCallsign(); throw new DiffusionSetupException( pht( - "The working copy for this repository ('%s') hasn't been cloned yet ". - "on this machine ('%s'). Make sure you've started the Phabricator ". - "daemons. If this problem persists for longer than a clone should ". - "take, check the daemon logs (in the Daemon Console) to see if there ". - "were errors cloning the repository. Consult the 'Diffusion User ". - "Guide' in the documentation for help setting up repositories.", - $callsign, + 'The working copy for this repository ("%s") has not been cloned yet '. + 'on this machine ("%s"). Make sure you havestarted the Phabricator '. + 'daemons. If this problem persists for longer than a clone should '. + 'take, check the daemon logs (in the Daemon Console) to see if there '. + 'were errors cloning the repository. Consult the "Diffusion User '. + 'Guide" in the documentation for help setting up repositories.', + $this->getRepository()->getDisplayName(), $host)); } From 35e7a1f3c07326ebb7940242ef189371d31a055d Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 11:28:31 -0800 Subject: [PATCH 40/83] Continue reducing callers to getCallsign() Summary: Ref T4245. More of the same, just narrowing down the easy cases. Test Plan: - Called `diffusion.querycommit`. - Browsed branches. - Browsed repository. - Browsed directory. - Searched for stuff. - Viewed a commit. - Viewed a file diff. - Edited a commit. - Viewed history. - Viewed tags. - Viewed push log. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14929 --- .../DiffusionQueryCommitsConduitAPIMethod.php | 6 ++---- .../DiffusionBranchTableController.php | 2 +- .../controller/DiffusionBrowseController.php | 1 - .../DiffusionBrowseDirectoryController.php | 4 +--- .../DiffusionBrowseSearchController.php | 4 +--- .../controller/DiffusionChangeController.php | 1 - .../controller/DiffusionCommitController.php | 18 ++++++----------- .../DiffusionCommitEditController.php | 7 +++---- .../controller/DiffusionController.php | 2 +- .../DiffusionExternalController.php | 2 +- .../controller/DiffusionHistoryController.php | 3 +-- .../controller/DiffusionLintController.php | 10 +++++++--- .../DiffusionLintDetailsController.php | 9 ++++----- .../DiffusionPushEventViewController.php | 3 ++- .../DiffusionRepositoryController.php | 4 +--- .../DiffusionRepositoryEditController.php | 4 ++-- ...ffusionRepositoryEditStorageController.php | 2 +- .../controller/DiffusionTagListController.php | 2 +- .../DiffusionRepositoryDatasource.php | 2 +- .../view/DiffusionPushLogListView.php | 11 +++++----- .../diffusion/view/DiffusionView.php | 20 +------------------ .../storage/PhabricatorRepository.php | 5 +++++ .../storage/PhabricatorRepositoryCommit.php | 6 ++++++ 23 files changed, 54 insertions(+), 74 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php index c2e1a3de50..3bb69276c8 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php @@ -70,16 +70,14 @@ final class DiffusionQueryCommitsConduitAPIMethod foreach ($commits as $commit) { $commit_data = $commit->getCommitData(); - $callsign = $commit->getRepository()->getCallsign(); - $identifier = $commit->getCommitIdentifier(); - $uri = '/r'.$callsign.$identifier; + $uri = $commit->getURI(); $uri = PhabricatorEnv::getProductionURI($uri); $dict = array( 'id' => $commit->getID(), 'phid' => $commit->getPHID(), 'repositoryPHID' => $commit->getRepository()->getPHID(), - 'identifier' => $identifier, + 'identifier' => $commit->getCommitIdentifier(), 'epoch' => $commit->getEpoch(), 'uri' => $uri, 'isImporting' => !$commit->isImported(), diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index e6c3b9592e..98157f8056 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -66,7 +66,7 @@ final class DiffusionBranchTableController extends DiffusionController { array( 'title' => array( pht('Branches'), - 'r'.$repository->getCallsign(), + $repository->getDisplayName(), ), )); } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index c924a9b0b3..e6120b6670 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -125,7 +125,6 @@ abstract class DiffusionBrowseController extends DiffusionController { ->setActionList($actions); $stable_commit = $drequest->getStableCommit(); - $callsign = $drequest->getRepository()->getCallsign(); $view->addProperty( pht('Commit'), diff --git a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php b/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php index f455b1b00d..b465d454e0 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php @@ -98,9 +98,7 @@ final class DiffusionBrowseDirectoryController array( 'title' => array( nonempty(basename($drequest->getPath()), '/'), - pht( - '%s Repository', - $drequest->getRepository()->getCallsign()), + $drequest->getRepository()->getDisplayName(), ), )); } diff --git a/src/applications/diffusion/controller/DiffusionBrowseSearchController.php b/src/applications/diffusion/controller/DiffusionBrowseSearchController.php index a2b1f8aa83..e27468ab77 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseSearchController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseSearchController.php @@ -33,9 +33,7 @@ final class DiffusionBrowseSearchController extends DiffusionBrowseController { array( 'title' => array( nonempty(basename($drequest->getPath()), '/'), - pht( - '%s Repository', - $drequest->getRepository()->getCallsign()), + $drequest->getRepository()->getDisplayName(), ), )); } diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php index d91c95a9a2..1ffa4e0120 100644 --- a/src/applications/diffusion/controller/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/DiffusionChangeController.php @@ -142,7 +142,6 @@ final class DiffusionChangeController extends DiffusionController { ->setActionList($actions); $stable_commit = $drequest->getStableCommit(); - $callsign = $drequest->getRepository()->getCallsign(); $view->addProperty( pht('Commit'), diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 6f1dbf26ac..6f47820501 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -181,7 +181,7 @@ final class DiffusionCommitController extends DiffusionController { id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, - 'r'.$callsign.$commit->getCommitIdentifier()); + $commit->getMonogram()); } $show_changesets = false; @@ -314,9 +314,8 @@ final class DiffusionCommitController extends DiffusionController { } } - $change_list_title = DiffusionView::nameCommit( - $repository, - $commit->getCommitIdentifier()); + $change_list_title = $commit->getDisplayName(); + $change_list = new DifferentialChangesetListView(); $change_list->setTitle($change_list_title); $change_list->setChangesets($changesets); @@ -344,11 +343,6 @@ final class DiffusionCommitController extends DiffusionController { $content[] = $this->renderAddCommentPanel($commit, $audit_requests); - $commit_id = 'r'.$callsign.$commit->getCommitIdentifier(); - $short_name = DiffusionView::nameCommit( - $repository, - $commit->getCommitIdentifier()); - $prefs = $user->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED; @@ -357,8 +351,8 @@ final class DiffusionCommitController extends DiffusionController { if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) - ->setTitle($short_name) - ->setBaseURI(new PhutilURI('/'.$commit_id)) + ->setTitle($commit->getDisplayName()) + ->setBaseURI(new PhutilURI($commit->getURI())) ->build($changesets) ->setCrumbs($crumbs) ->setCollapsed((bool)$collapsed) @@ -371,7 +365,7 @@ final class DiffusionCommitController extends DiffusionController { return $this->buildApplicationPage( $content, array( - 'title' => $commit_id, + 'title' => $commit->getDisplayName(), 'pageObjects' => array($commit->getPHID()), )); } diff --git a/src/applications/diffusion/controller/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php index 82c87f7e85..ffaf892e75 100644 --- a/src/applications/diffusion/controller/DiffusionCommitEditController.php +++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php @@ -5,7 +5,6 @@ final class DiffusionCommitEditController extends DiffusionController { protected function processDiffusionRequest(AphrontRequest $request) { $user = $request->getUser(); $drequest = $this->getDiffusionRequest(); - $callsign = $drequest->getRepository()->getCallsign(); $repository = $drequest->getRepository(); $commit = $drequest->loadCommit(); $data = $commit->loadCommitData(); @@ -34,7 +33,7 @@ final class DiffusionCommitEditController extends DiffusionController { ->setContentSourceFromRequest($request); $xactions = $editor->applyTransactions($commit, $xactions); return id(new AphrontRedirectResponse()) - ->setURI('/r'.$callsign.$commit->getCommitIdentifier()); + ->setURI($commit->getURI()); } $tokenizer_id = celerity_generate_unique_node_id(); @@ -95,8 +94,8 @@ final class DiffusionCommitEditController extends DiffusionController { $submit = id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) - ->addCancelButton('/r'.$callsign.$commit->getCommitIdentifier()); - $form->appendChild($submit); + ->addCancelButton($commit->getURI()); + $form->appendChild($submit); $crumbs = $this->buildCrumbs(array( 'commit' => true, diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 262223c6f9..810286176b 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -235,7 +235,7 @@ abstract class DiffusionController extends PhabricatorController { } } } else { - $links[] = 'r'.$drequest->getRepository()->getCallsign(); + $links[] = $drequest->getRepository()->getDisplayName(); $links[] = $divider; } diff --git a/src/applications/diffusion/controller/DiffusionExternalController.php b/src/applications/diffusion/controller/DiffusionExternalController.php index aa9375cf98..011865dc3f 100644 --- a/src/applications/diffusion/controller/DiffusionExternalController.php +++ b/src/applications/diffusion/controller/DiffusionExternalController.php @@ -109,7 +109,7 @@ final class DiffusionExternalController extends DiffusionController { array( 'href' => $href, ), - 'r'.$repo->getCallsign().$commit->getCommitIdentifier()), + $commit->getMonogram()), $commit->loadCommitData()->getSummary(), ); } diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 65286e9d34..6cca484207 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -93,7 +93,7 @@ final class DiffusionHistoryController extends DiffusionController { array( 'title' => array( pht('History'), - pht('%s Repository', $drequest->getRepository()->getCallsign()), + $drequest->getRepository()->getDisplayName(), ), )); } @@ -151,7 +151,6 @@ final class DiffusionHistoryController extends DiffusionController { ->setActionList($actions); $stable_commit = $drequest->getStableCommit(); - $callsign = $drequest->getRepository()->getCallsign(); $view->addProperty( pht('Commit'), diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index b5bf561f8f..ec5ed3a14e 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -80,7 +80,12 @@ final class DiffusionLintController extends DiffusionController { $rows[] = array( phutil_tag('a', array('href' => $href_lint), $code['n']), phutil_tag('a', array('href' => $href_browse), $code['files']), - phutil_tag('a', array('href' => $href_repo), $drequest->getCallsign()), + phutil_tag( + 'a', + array( + 'href' => $href_repo, + ), + $drequest->getRepository()->getDisplayName()), ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']), $code['code'], $code['maxName'], @@ -133,7 +138,7 @@ final class DiffusionLintController extends DiffusionController { )); if ($this->diffusionRequest) { - $title[] = $drequest->getCallsign(); + $title[] = $drequest->getRepository()->getDisplayName(); } else { $crumbs->addTextCrumb(pht('All Lint')); } @@ -314,7 +319,6 @@ final class DiffusionLintController extends DiffusionController { ->setUser($viewer) ->setActionList($actions); - $callsign = $drequest->getRepository()->getCallsign(); $lint_commit = $branch->getLintCommit(); $view->addProperty( diff --git a/src/applications/diffusion/controller/DiffusionLintDetailsController.php b/src/applications/diffusion/controller/DiffusionLintDetailsController.php index 71b823d508..13ef9c5559 100644 --- a/src/applications/diffusion/controller/DiffusionLintDetailsController.php +++ b/src/applications/diffusion/controller/DiffusionLintDetailsController.php @@ -90,11 +90,10 @@ final class DiffusionLintDetailsController extends DiffusionController { $pager, ), array( - 'title' => - array( - pht('Lint'), - $drequest->getRepository()->getCallsign(), - ), + 'title' => array( + pht('Lint'), + $drequest->getRepository()->getDisplayName(), + ), )); } diff --git a/src/applications/diffusion/controller/DiffusionPushEventViewController.php b/src/applications/diffusion/controller/DiffusionPushEventViewController.php index 0ad40ae840..3c5861e512 100644 --- a/src/applications/diffusion/controller/DiffusionPushEventViewController.php +++ b/src/applications/diffusion/controller/DiffusionPushEventViewController.php @@ -25,7 +25,8 @@ final class DiffusionPushEventViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( $repository->getName(), - $this->getApplicationURI($repository->getCallsign().'/')); + $repository->getURI()); + $crumbs->addTextCrumb( pht('Push Logs'), $this->getApplicationURI( diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index cd7859cc0a..cd469c1424 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -500,9 +500,8 @@ final class DiffusionRepositoryController extends DiffusionController { ->setDisabled(!$can_edit)); if ($repository->isHosted()) { - $callsign = $repository->getCallsign(); $push_uri = $this->getApplicationURI( - 'pushlog/?repositories=r'.$callsign); + 'pushlog/?repositories='.$repository->getMonogram()); $view->addAction( id(new PhabricatorActionView()) @@ -551,7 +550,6 @@ final class DiffusionRepositoryController extends DiffusionController { } $history_table->setIsHead(true); - $callsign = $drequest->getRepository()->getCallsign(); $icon = id(new PHUIIconView()) ->setIconFont('fa-list-alt'); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 5c2a7669e0..68749b1132 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -8,10 +8,10 @@ abstract class DiffusionRepositoryEditController if ($this->diffusionRequest) { $repository = $this->getDiffusionRequest()->getRepository(); - $repo_uri = $this->getRepositoryControllerURI($repository, ''); + $repo_uri = $repository->getURI(); $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); - $crumbs->addTextCrumb('r'.$repository->getCallsign(), $repo_uri); + $crumbs->addTextCrumb($repository->getDisplayname(), $repo_uri); if ($is_main) { $crumbs->addTextCrumb(pht('Edit Repository')); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php index 345464a005..4afa594ed9 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php @@ -60,7 +60,7 @@ final class DiffusionRepositoryEditStorageController "web interface. To edit it, run this command:\n\n %s", sprintf( 'phabricator/ $ ./bin/repository edit %s --as %s --local-path ...', - $repository->getCallsign(), + $repository->getMonogram(), $user->getUsername()))) ->appendChild( id(new AphrontFormSubmitControl()) diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index 897de30650..a5e90bd46c 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -88,7 +88,7 @@ final class DiffusionTagListController extends DiffusionController { array( 'title' => array( pht('Tags'), - pht('%s Repository', $repository->getCallsign()), + $repository->getDisplayName(), ), )); } diff --git a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php index 4c2b8e1fb0..06d4ff0be9 100644 --- a/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php +++ b/src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php @@ -28,7 +28,7 @@ final class DiffusionRepositoryDatasource foreach ($repos as $repo) { $results[] = id(new PhabricatorTypeaheadResult()) ->setName($repo->getMonogram().' '.$repo->getName()) - ->setURI('/diffusion/'.$repo->getCallsign().'/') + ->setURI($repo->getURI()) ->setPHID($repo->getPHID()) ->setPriorityString($repo->getMonogram()); } diff --git a/src/applications/diffusion/view/DiffusionPushLogListView.php b/src/applications/diffusion/view/DiffusionPushLogListView.php index acb1b60131..860320e624 100644 --- a/src/applications/diffusion/view/DiffusionPushLogListView.php +++ b/src/applications/diffusion/view/DiffusionPushLogListView.php @@ -39,6 +39,7 @@ final class DiffusionPushLogListView extends AphrontView { $rows = array(); foreach ($logs as $log) { + $repository = $log->getRepository(); // Reveal this if it's valid and the user can edit the repository. $remote_addr = '-'; @@ -51,16 +52,16 @@ final class DiffusionPushLogListView extends AphrontView { $event_id = $log->getPushEvent()->getID(); - $callsign = $log->getRepository()->getCallsign(); $old_ref_link = null; if ($log->getRefOld() != DiffusionCommitHookEngine::EMPTY_HASH) { $old_ref_link = phutil_tag( 'a', array( - 'href' => '/r'.$callsign.$log->getRefOld(), + 'href' => $repository->getCommitURI($log->getRefOld()), ), $log->getRefOldShort()); } + $rows[] = array( phutil_tag( 'a', @@ -71,9 +72,9 @@ final class DiffusionPushLogListView extends AphrontView { phutil_tag( 'a', array( - 'href' => '/diffusion/'.$callsign.'/', + 'href' => $repository->getURI(), ), - $callsign), + $repository->getDisplayName()), $handles[$log->getPusherPHID()]->renderLink(), $remote_addr, $log->getPushEvent()->getRemoteProtocol(), @@ -83,7 +84,7 @@ final class DiffusionPushLogListView extends AphrontView { phutil_tag( 'a', array( - 'href' => '/r'.$callsign.$log->getRefNew(), + 'href' => $repository->getCommitURI($log->getRefNew()), ), $log->getRefNewShort()), diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index 83fdc1f7e5..c82661e90e 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -123,30 +123,12 @@ abstract class DiffusionView extends AphrontView { )); } - final public static function nameCommit( - PhabricatorRepository $repository, - $commit) { - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $commit_name = substr($commit, 0, 12); - break; - default: - $commit_name = $commit; - break; - } - - $callsign = $repository->getCallsign(); - return "r{$callsign}{$commit_name}"; - } - final public static function linkCommit( PhabricatorRepository $repository, $commit, $summary = '') { - $commit_name = self::nameCommit($repository, $commit); + $commit_name = $repository->formatCommitName($commit); $callsign = $repository->getCallsign(); if (strlen($summary)) { diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index c65a51228f..1002fb9491 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -601,6 +601,11 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return '/diffusion/'.$this->getCallsign().'/'; } + public function getCommitURI($identifier) { + $callsign = $this->getCallsign(); + return "/r{$callsign}{$identifier}"; + } + public function getNormalizedPath() { $uri = (string)$this->getCloneURIObject(); diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 70478bb681..1db4b71d5c 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -256,6 +256,12 @@ final class PhabricatorRepositoryCommit return "r{$callsign}{$identifier}"; } + public function getDisplayName() { + $repository = $this->getRepository(); + $identifier = $this->getCommitIdentifier(); + return $repository->formatCommitName($identifier); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ From b1388c5ca1576229c6a36bd873a8e0cc30b8179d Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 11:48:41 -0800 Subject: [PATCH 41/83] Remove `diffusion.getcommits` Conduit API method Summary: Ref T4245. This was obsoleted long ago and has no callers in Phabricator or Arcanist. Also some minor cleanup. Test Plan: `grep` for callers everywhere. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14930 --- src/__phutil_library_map__.php | 2 - .../DiffusionGetCommitsConduitAPIMethod.php | 293 ------------------ .../diffusion/view/DiffusionView.php | 3 +- ...habricatorRepositoryRepositoryPHIDType.php | 1 - .../PhabricatorChangeParserTestCase.php | 2 +- ...torRepositoryCommitMessageParserWorker.php | 3 +- 6 files changed, 3 insertions(+), 301 deletions(-) delete mode 100644 src/applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d43c9a6cb8..fdbd1d7539 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -603,7 +603,6 @@ phutil_register_library_map(array( 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php', 'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php', - 'DiffusionGetCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php', 'DiffusionGetLintMessagesConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetLintMessagesConduitAPIMethod.php', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php', 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', @@ -4566,7 +4565,6 @@ phutil_register_library_map(array( 'DiffusionFileContentQuery' => 'DiffusionQuery', 'DiffusionFileContentQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod', - 'DiffusionGetCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetLintMessagesConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGitBranch' => 'Phobject', diff --git a/src/applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php deleted file mode 100644 index 15dd1f53f6..0000000000 --- a/src/applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php +++ /dev/null @@ -1,293 +0,0 @@ - 'required list', - ); - } - - protected function defineReturnType() { - return 'nonempty list>'; - } - - protected function execute(ConduitAPIRequest $request) { - $results = array(); - - $commits = $request->getValue('commits'); - $commits = array_fill_keys($commits, array()); - foreach ($commits as $name => $info) { - $matches = null; - if (!preg_match('/^r([A-Z]+)([0-9a-f]+)\z/', $name, $matches)) { - $results[$name] = array( - 'error' => 'ERR-UNPARSEABLE', - ); - unset($commits[$name]); - continue; - } - $commits[$name] = array( - 'callsign' => $matches[1], - 'commitIdentifier' => $matches[2], - ); - } - - if (!$commits) { - return $results; - } - - $callsigns = ipull($commits, 'callsign'); - $callsigns = array_unique($callsigns); - $repos = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withCallsigns($callsigns) - ->execute(); - $repos = mpull($repos, null, 'getCallsign'); - - foreach ($commits as $name => $info) { - $repo = idx($repos, $info['callsign']); - if (!$repo) { - $results[$name] = $info + array( - 'error' => 'ERR-UNKNOWN-REPOSITORY', - ); - unset($commits[$name]); - continue; - } - $commits[$name] += array( - 'repositoryPHID' => $repo->getPHID(), - 'repositoryID' => $repo->getID(), - ); - } - - if (!$commits) { - return $results; - } - - // Execute a complicated query to figure out the primary commit information - // for each referenced commit. - $cdata = $this->queryCommitInformation($commits, $repos); - - // We've built the queries so that each row also has the identifier we used - // to select it, which might be a git prefix rather than a full identifier. - $ref_map = ipull($cdata, 'commitIdentifier', 'commitRef'); - - $cobjs = id(new PhabricatorRepositoryCommit())->loadAllFromArray($cdata); - $cobjs = mgroup($cobjs, 'getRepositoryID', 'getCommitIdentifier'); - foreach ($commits as $name => $commit) { - - // Expand short git names into full identifiers. For SVN this map is just - // the identity. - $full_identifier = idx($ref_map, $commit['commitIdentifier']); - - $repo_id = $commit['repositoryID']; - unset($commits[$name]['repositoryID']); - - if (empty($full_identifier) || - empty($cobjs[$commit['repositoryID']][$full_identifier])) { - $results[$name] = $commit + array( - 'error' => 'ERR-UNKNOWN-COMMIT', - ); - unset($commits[$name]); - continue; - } - - $cobj_arr = $cobjs[$commit['repositoryID']][$full_identifier]; - $cobj = head($cobj_arr); - - $commits[$name] += array( - 'epoch' => $cobj->getEpoch(), - 'commitPHID' => $cobj->getPHID(), - 'commitID' => $cobj->getID(), - ); - - // Upgrade git short references into full commit identifiers. - $identifier = $cobj->getCommitIdentifier(); - $commits[$name]['commitIdentifier'] = $identifier; - - $callsign = $commits[$name]['callsign']; - $uri = "/r{$callsign}{$identifier}"; - $commits[$name]['uri'] = PhabricatorEnv::getProductionURI($uri); - } - - if (!$commits) { - return $results; - } - - $commits = $this->addRepositoryCommitDataInformation($commits); - $commits = $this->addDifferentialInformation($commits, $request); - $commits = $this->addManiphestInformation($commits); - - foreach ($commits as $name => $commit) { - $results[$name] = $commit; - } - - return $results; - } - - /** - * Retrieve primary commit information for all referenced commits. - */ - private function queryCommitInformation(array $commits, array $repos) { - assert_instances_of($repos, 'PhabricatorRepository'); - $conn_r = id(new PhabricatorRepositoryCommit())->establishConnection('r'); - $repos = mpull($repos, null, 'getID'); - - $groups = array(); - foreach ($commits as $name => $commit) { - $groups[$commit['repositoryID']][] = $commit['commitIdentifier']; - } - - // NOTE: MySQL goes crazy and does a massive table scan if we build a more - // sensible version of this query. Make sure the query plan is OK if you - // attempt to reduce the craziness here. METANOTE: The addition of prefix - // selection for Git further complicates matters. - $query = array(); - $commit_table = id(new PhabricatorRepositoryCommit())->getTableName(); - - foreach ($groups as $repository_id => $identifiers) { - $vcs = $repos[$repository_id]->getVersionControlSystem(); - $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); - if ($is_git) { - foreach ($identifiers as $identifier) { - if (strlen($identifier) < 7) { - // Don't bother with silly stuff like 'rX2', which will select - // 1/16th of all commits. Note that with length 7 we'll still get - // collisions in repositories at the tens-of-thousands-of-commits - // scale. - continue; - } - $query[] = qsprintf( - $conn_r, - 'SELECT %T.*, %s commitRef - FROM %T WHERE repositoryID = %d - AND commitIdentifier LIKE %>', - $commit_table, - $identifier, - $commit_table, - $repository_id, - $identifier); - } - } else { - $query[] = qsprintf( - $conn_r, - 'SELECT %T.*, commitIdentifier commitRef - FROM %T WHERE repositoryID = %d - AND commitIdentifier IN (%Ls)', - $commit_table, - $commit_table, - $repository_id, - $identifiers); - } - } - - return queryfx_all( - $conn_r, - '%Q', - implode(' UNION ALL ', $query)); - } - - /** - * Enhance the commit list with RepositoryCommitData information. - */ - private function addRepositoryCommitDataInformation(array $commits) { - $commit_ids = ipull($commits, 'commitID'); - - $data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( - 'commitID in (%Ld)', - $commit_ids); - $data = mpull($data, null, 'getCommitID'); - - foreach ($commits as $name => $commit) { - if (isset($data[$commit['commitID']])) { - $dobj = $data[$commit['commitID']]; - $commits[$name] += array( - 'commitMessage' => $dobj->getCommitMessage(), - 'commitDetails' => $dobj->getCommitDetails(), - ); - } - - // Remove this information so we don't expose it via the API since - // external services shouldn't be storing internal Commit IDs. - unset($commits[$name]['commitID']); - } - - return $commits; - } - - /** - * Enhance the commit list with Differential information. - */ - private function addDifferentialInformation( - array $commits, - ConduitAPIRequest $request) { - - $commit_phids = ipull($commits, 'commitPHID'); - - $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($request->getUser()) - ->withCommitPHIDs($commit_phids) - ->needCommitPHIDs(true) - ->execute(); - $rev_phid_commit_phids_map = mpull($revisions, 'getCommitPHIDs', 'getPHID'); - $revisions = mpull($revisions, null, 'getPHID'); - foreach ($rev_phid_commit_phids_map as $rev_phid => $commit_phids) { - foreach ($commits as $name => $commit) { - $commit_phid = $commit['commitPHID']; - if (in_array($commit_phid, $commit_phids)) { - $revision = $revisions[$rev_phid]; - $commits[$name] += array( - 'differentialRevisionID' => 'D'.$revision->getID(), - 'differentialRevisionPHID' => $revision->getPHID(), - ); - } - } - } - - return $commits; - } - - /** - * Enhances the commits list with Maniphest information. - */ - private function addManiphestInformation(array $commits) { - $task_type = DiffusionCommitHasTaskEdgeType::EDGECONST; - - $commit_phids = ipull($commits, 'commitPHID'); - - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs($commit_phids) - ->withEdgeTypes(array($task_type)); - - $edges = $edge_query->execute(); - - foreach ($commits as $name => $commit) { - $task_phids = $edge_query->getDestinationPHIDs( - array($commit['commitPHID']), - array($task_type)); - - $commits[$name] += array( - 'taskPHIDs' => $task_phids, - ); - } - - return $commits; - } - -} diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index c82661e90e..b540dc3a4e 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -129,7 +129,6 @@ abstract class DiffusionView extends AphrontView { $summary = '') { $commit_name = $repository->formatCommitName($commit); - $callsign = $repository->getCallsign(); if (strlen($summary)) { $commit_name .= ': '.$summary; @@ -138,7 +137,7 @@ abstract class DiffusionView extends AphrontView { return phutil_tag( 'a', array( - 'href' => "/r{$callsign}{$commit}", + 'href' => $repository->getCommitURI($commit), ), $commit_name); } diff --git a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php index 704de09755..cf812366e5 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php @@ -38,7 +38,6 @@ final class PhabricatorRepositoryRepositoryPHIDType $repository = $objects[$phid]; $monogram = $repository->getMonogram(); - $callsign = $repository->getCallsign(); $name = $repository->getName(); $uri = $repository->getURI(); diff --git a/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php b/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php index 4925c9dbea..452d02ae40 100644 --- a/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php +++ b/src/applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php @@ -1186,7 +1186,7 @@ final class PhabricatorChangeParserTestCase pht( 'No test entry for commit "%s" in repository "%s"!', $commit_identifier, - $repository->getCallsign())); + $repository->getDisplayName())); } $changes = $this->parseCommit($repository, $commit); diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index c93810cb0c..0bc9957aef 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -319,8 +319,7 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker ->setDescription( pht( 'Commit %s', - 'r'.$this->repository->getCallsign(). - $this->commit->getCommitIdentifier())); + $this->commit->getMonogram())); $parents = DiffusionQuery::callConduitWithDiffusionRequest( $viewer, From be5b89687e3700d7f93a07d3191a101beec8c5d1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 11:56:19 -0800 Subject: [PATCH 42/83] Separate external editor integration from callsigns Summary: Ref T4245. Pass the whole repository in so it can do something else in a future change. Test Plan: Loaded changesets in Diffusion. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14931 --- .../console/plugin/DarkConsoleErrorLogPlugin.php | 2 +- .../view/DifferentialChangesetListView.php | 3 +-- .../controller/DiffusionBrowseFileController.php | 5 ++--- src/applications/people/storage/PhabricatorUser.php | 12 +++++++++++- src/view/widget/AphrontStackTraceView.php | 8 -------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php b/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php index 591904a85f..0d9604dcba 100644 --- a/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php +++ b/src/applications/console/plugin/DarkConsoleErrorLogPlugin.php @@ -64,7 +64,7 @@ final class DarkConsoleErrorLogPlugin extends DarkConsolePlugin { $line .= ' called at ['.$entry['file'].':'.$entry['line'].']'; try { $user = $this->getRequest()->getUser(); - $href = $user->loadEditorLink($entry['file'], $entry['line'], ''); + $href = $user->loadEditorLink($entry['file'], $entry['line'], null); } catch (Exception $ex) { // The database can be inaccessible. } diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index f849cdc347..83815cda82 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -314,8 +314,7 @@ final class DifferentialChangesetListView extends AphrontView { $changeset->getAbsoluteRepositoryPath($repository, $this->diff), '/'); $line = idx($changeset->getMetadata(), 'line:first', 1); - $callsign = $repository->getCallsign(); - $editor_link = $user->loadEditorLink($path, $line, $callsign); + $editor_link = $user->loadEditorLink($path, $line, $repository); if ($editor_link) { $meta['editor'] = $editor_link; } else { diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php index eb935208e5..91944112fc 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php @@ -435,9 +435,8 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController { $path = $drequest->getPath(); $line = nonempty((int)$drequest->getLine(), 1); - $callsign = $repository->getCallsign(); - $editor_link = $user->loadEditorLink($path, $line, $callsign); - $template = $user->loadEditorLink($path, '%l', $callsign); + $editor_link = $user->loadEditorLink($path, $line, $repository); + $template = $user->loadEditorLink($path, '%l', $repository); $icon_edit = id(new PHUIIconView()) ->setIconFont('fa-pencil'); diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index c805eb2b5c..18526ac04f 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -504,7 +504,11 @@ final class PhabricatorUser return $preferences; } - public function loadEditorLink($path, $line, $callsign) { + public function loadEditorLink( + $path, + $line, + PhabricatorRepository $repository = null) { + $editor = $this->loadPreferences()->getPreference( PhabricatorUserPreferences::PREFERENCE_EDITOR); @@ -524,6 +528,12 @@ final class PhabricatorUser return null; } + if ($repository) { + $callsign = $repository->getCallsign(); + } else { + $callsign = null; + } + $uri = strtr($editor, array( '%%' => '%', '%f' => phutil_escape_uri($path), diff --git a/src/view/widget/AphrontStackTraceView.php b/src/view/widget/AphrontStackTraceView.php index 11a85dae4b..1d0616df3d 100644 --- a/src/view/widget/AphrontStackTraceView.php +++ b/src/view/widget/AphrontStackTraceView.php @@ -50,14 +50,6 @@ final class AphrontStackTraceView extends AphrontView { if ($file) { if (isset($callsigns[$lib])) { $attrs = array('title' => $file); - try { - $attrs['href'] = $user->loadEditorLink( - '/src/'.$relative, - $part['line'], - $callsigns[$lib]); - } catch (Exception $ex) { - // The database can be inaccessible. - } if (empty($attrs['href'])) { $attrs['href'] = sprintf($path, $callsigns[$lib]). str_replace(DIRECTORY_SEPARATOR, '/', $relative). From 9febfb26a08d8ba78003dc5e370d92e419b4acab Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 12:03:08 -0800 Subject: [PATCH 43/83] Convert `diffusion.looksoon` to use repository identifiers instead of callsigns Summary: Ref T4245. Like everything else, accept more identifiers. This needs a change in `arc`, which I've made a note about elsewhere. Test Plan: Used "Update Now" from web UI, saw update get scheduled. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14932 --- .../DifferentialRevisionLandController.php | 4 ++-- .../conduit/DiffusionLookSoonConduitAPIMethod.php | 14 ++++++++++---- .../diffusion/controller/DiffusionController.php | 2 +- .../DiffusionRepositoryEditUpdateController.php | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php index 4f8787956f..f2d2497a42 100644 --- a/src/applications/differential/controller/DifferentialRevisionLandController.php +++ b/src/applications/differential/controller/DifferentialRevisionLandController.php @@ -146,7 +146,7 @@ final class DifferentialRevisionLandController extends DifferentialController { $looksoon = new ConduitCall( 'diffusion.looksoon', array( - 'callsigns' => array($repository->getCallsign()), + 'repositories' => array($repository->getPHID()), )); $looksoon->setUser($request->getUser()); $looksoon->execute(); @@ -155,7 +155,7 @@ final class DifferentialRevisionLandController extends DifferentialController { } private function lockRepository($repository) { - $lock_name = __CLASS__.':'.($repository->getCallsign()); + $lock_name = __CLASS__.':'.($repository->getPHID()); $lock = PhabricatorGlobalLock::newLock($lock_name); $lock->lock(); return $lock; diff --git a/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php index 49dd5dc117..88d216e4a1 100644 --- a/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php @@ -24,7 +24,8 @@ final class DiffusionLookSoonConduitAPIMethod protected function defineParamTypes() { return array( - 'callsigns' => 'required list', + 'callsigns' => 'optional list (deprecated)', + 'repositories' => 'optional list', 'urgency' => 'optional string', ); } @@ -33,14 +34,19 @@ final class DiffusionLookSoonConduitAPIMethod // NOTE: The "urgency" parameter does nothing, it is just a hilarious joke // which exemplifies the boundless clever wit of this project. - $callsigns = $request->getValue('callsigns'); - if (!$callsigns) { + $identifiers = $request->getValue('repositories'); + + if (!$identifiers) { + $identifiers = $request->getValue('callsigns'); + } + + if (!$identifiers) { return null; } $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($request->getUser()) - ->withCallsigns($callsigns) + ->withIdentifiers($identifiers) ->execute(); foreach ($repositories as $repository) { diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 810286176b..1d0d27216b 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -212,7 +212,7 @@ abstract class DiffusionController extends PhabricatorController { 'path' => '', )), ), - 'r'.$drequest->getRepository()->getCallsign()); + $drequest->getRepository()->getDisplayName()); $links[] = $divider; $accum = ''; $last_key = last_key($path_parts); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php index e50d297586..74df9c488b 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php @@ -25,8 +25,8 @@ final class DiffusionRepositoryEditUpdateController if ($request->isFormPost()) { $params = array( - 'callsigns' => array( - $repository->getCallsign(), + 'repositories' => array( + $repository->getPHID(), ), ); From bcfd6bdd81eef91934c8f942c353f417cc7935e3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jan 2016 12:21:13 -0800 Subject: [PATCH 44/83] Move various other callsites away from callsigns Summary: Ref T4245. These mostly relate to building URIs. Test Plan: Tried to hunt down as many of these in the UI as I could. Some are a bit tricky but they should be low-risk. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14933 --- .../controller/DiffusionChangeController.php | 4 ++-- .../controller/DiffusionCommitController.php | 17 +++++++++-------- .../controller/DiffusionController.php | 18 +++++++----------- .../controller/DiffusionExternalController.php | 6 +++--- .../DiffusionRepositoryController.php | 2 +- .../DiffusionCommitBranchesHeraldField.php | 2 +- .../diffusion/query/DiffusionQuery.php | 2 +- .../diffusion/request/DiffusionRequest.php | 12 ++++++++++-- ...rWaitForPreviousBuildStepImplementation.php | 4 ++-- .../PhabricatorOwnersDetailController.php | 2 +- .../PhabricatorCommitBranchesField.php | 2 +- .../PhabricatorCommitMergedCommitsField.php | 2 +- .../customfield/PhabricatorCommitTagsField.php | 2 +- .../PhabricatorRepositorySearchEngine.php | 2 +- .../storage/PhabricatorRepository.php | 4 ++++ 15 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php index 1ffa4e0120..064e5cc750 100644 --- a/src/applications/diffusion/controller/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/DiffusionChangeController.php @@ -33,7 +33,6 @@ final class DiffusionChangeController extends DiffusionController { } $repository = $drequest->getRepository(); - $callsign = $repository->getCallsign(); $changesets = array( 0 => $changeset, ); @@ -59,7 +58,8 @@ final class DiffusionChangeController extends DiffusionController { $left_uri = $drequest->generateURI($raw_params); $changeset_view->setRawFileURIs($left_uri, $right_uri); - $changeset_view->setRenderURI('/diffusion/'.$callsign.'/diff/'); + $changeset_view->setRenderURI($repository->getPathURI('diff/')); + $changeset_view->setWhitespace( DifferentialChangesetParser::WHITESPACE_SHOW_ALL); $changeset_view->setUser($viewer); diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 6f47820501..42a64f416f 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -36,7 +36,6 @@ final class DiffusionCommitController extends DiffusionController { } $repository = $drequest->getRepository(); - $callsign = $repository->getCallsign(); $content = array(); $commit = id(new DiffusionCommitQuery()) @@ -321,19 +320,21 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setChangesets($changesets); $change_list->setVisibleChangesets($visible_changesets); $change_list->setRenderingReferences($references); - $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/'); + $change_list->setRenderURI( + $repository->getPathURI('diff/')); $change_list->setRepository($repository); $change_list->setUser($user); // TODO: Try to setBranch() to something reasonable here? $change_list->setStandaloneURI( - '/diffusion/'.$callsign.'/diff/'); + $repository->getPathURI('diff/')); + $change_list->setRawFileURIs( // TODO: Implement this, somewhat tricky if there's an octopus merge // or whatever? null, - '/diffusion/'.$callsign.'/diff/?view=r'); + $repository->getPathURI('diff/?view=r')); $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); @@ -567,8 +568,8 @@ final class DiffusionCommitController extends DiffusionController { ), pht('Unknown')); - $callsign = $repository->getCallsign(); - $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier(); + $identifier = $commit->getCommitIdentifier(); + $root = $repository->getPathURI("commit/{$identifier}"); Javelin::initBehavior( 'diffusion-commit-branches', array( @@ -904,8 +905,8 @@ final class DiffusionCommitController extends DiffusionController { $commit, PhabricatorPolicyCapability::CAN_EDIT); - $uri = '/diffusion/'.$repository->getCallsign().'/commit/'. - $commit->getCommitIdentifier().'/edit/'; + $identifier = $commit->getCommitIdentifier(); + $uri = $repository->getPathURI("commit/{$identifier}/edit/"); $action = id(new PhabricatorActionView()) ->setName(pht('Edit Commit')) diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 1d0d27216b..89446e132f 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -86,7 +86,6 @@ abstract class DiffusionController extends PhabricatorController { return $crumb_list; } - $callsign = $repository->getCallsign(); $repository_name = $repository->getName(); if (!$spec['commit'] && !$spec['tags'] && !$spec['branches']) { @@ -112,17 +111,14 @@ abstract class DiffusionController extends PhabricatorController { $crumb_list[] = $crumb; $stable_commit = $drequest->getStableCommit(); + $commit_name = $repository->formatCommitName($stable_commit); + $commit_uri = $repository->getCommitURI($stable_commit); if ($spec['tags']) { $crumb = new PHUICrumbView(); if ($spec['commit']) { - $crumb->setName( - pht('Tags for %s', 'r'.$callsign.$stable_commit)); - $crumb->setHref($drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $drequest->getStableCommit(), - ))); + $crumb->setName(pht('Tags for %s', $commit_name)); + $crumb->setHref($commit_uri); } else { $crumb->setName(pht('Tags')); } @@ -139,8 +135,8 @@ abstract class DiffusionController extends PhabricatorController { if ($spec['commit']) { $crumb = id(new PHUICrumbView()) - ->setName("r{$callsign}{$stable_commit}") - ->setHref("r{$callsign}{$stable_commit}"); + ->setName($commit_name) + ->setHref($commit_uri); $crumb_list[] = $crumb; return $crumb_list; } @@ -187,7 +183,7 @@ abstract class DiffusionController extends PhabricatorController { protected function getRepositoryControllerURI( PhabricatorRepository $repository, $path) { - return $this->getApplicationURI($repository->getCallsign().'/'.$path); + return $repository->getPathURI($path); } protected function renderPathLinks(DiffusionRequest $drequest, $action) { diff --git a/src/applications/diffusion/controller/DiffusionExternalController.php b/src/applications/diffusion/controller/DiffusionExternalController.php index 011865dc3f..8faa9275d1 100644 --- a/src/applications/diffusion/controller/DiffusionExternalController.php +++ b/src/applications/diffusion/controller/DiffusionExternalController.php @@ -48,7 +48,7 @@ final class DiffusionExternalController extends DiffusionController { $redirect = DiffusionRequest::generateDiffusionURI( array( 'action' => 'browse', - 'callsign' => $repository->getCallsign(), + 'repository' => $repository, 'branch' => $repository->getDefaultBranch(), 'commit' => $id, )); @@ -86,7 +86,7 @@ final class DiffusionExternalController extends DiffusionController { $redirect = DiffusionRequest::generateDiffusionURI( array( 'action' => 'browse', - 'callsign' => $repo->getCallsign(), + 'repository' => $repo, 'branch' => $repo->getDefaultBranch(), 'commit' => $commit->getCommitIdentifier(), )); @@ -99,7 +99,7 @@ final class DiffusionExternalController extends DiffusionController { $href = DiffusionRequest::generateDiffusionURI( array( 'action' => 'browse', - 'callsign' => $repo->getCallsign(), + 'repository' => $repo, 'branch' => $repo->getDefaultBranch(), 'commit' => $commit->getCommitIdentifier(), )); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index cd469c1424..21606e04ae 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -480,7 +480,7 @@ final class DiffusionRepositoryController extends DiffusionController { private function buildActionList(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); - $edit_uri = $this->getApplicationURI($repository->getCallsign().'/edit/'); + $edit_uri = $repository->getPathURI('edit/'); $view = id(new PhabricatorActionListView()) ->setUser($viewer) diff --git a/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php index dec0a05f0d..03c4e3be62 100644 --- a/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php @@ -14,7 +14,7 @@ final class DiffusionCommitBranchesHeraldField $repository = $object->getRepository(); $params = array( - 'callsign' => $repository->getCallsign(), + 'repository' => $repository->getPHID(), 'contains' => $commit->getCommitIdentifier(), ); diff --git a/src/applications/diffusion/query/DiffusionQuery.php b/src/applications/diffusion/query/DiffusionQuery.php index a180a61f5a..409bf68980 100644 --- a/src/applications/diffusion/query/DiffusionQuery.php +++ b/src/applications/diffusion/query/DiffusionQuery.php @@ -53,7 +53,7 @@ abstract class DiffusionQuery extends PhabricatorQuery { $repository = $drequest->getRepository(); $core_params = array( - 'callsign' => $repository->getCallsign(), + 'repository' => $repository->getPHID(), ); if ($drequest->getBranch() !== null) { diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index fe28364f5d..9df5efc2c8 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -465,7 +465,7 @@ abstract class DiffusionRequest extends Phobject { } $defaults = array( - 'callsign' => $this->getCallsign(), + 'repository' => $this->getRepository(), 'path' => $this->getPath(), 'branch' => $this->getBranch(), 'commit' => $default_commit, @@ -487,6 +487,7 @@ abstract class DiffusionRequest extends Phobject { * - `action` One of `history`, `browse`, `change`, `lastmodified`, * `branch`, `tags`, `branches`, or `revision-ref`. The action specified * by the URI. + * - `repository` Repository. * - `callsign` Repository callsign. * - `branch` Optional if action is not `branch`, branch name. * - `path` Optional, path to file. @@ -504,7 +505,14 @@ abstract class DiffusionRequest extends Phobject { public static function generateDiffusionURI(array $params) { $action = idx($params, 'action'); - $callsign = idx($params, 'callsign'); + $repository = idx($params, 'repository'); + + if ($repository) { + $callsign = $repository->getCallsign(); + } else { + $callsign = idx($params, 'callsign'); + } + $path = idx($params, 'path'); $branch = idx($params, 'branch'); $commit = idx($params, 'commit'); diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php index f4e965b6c2..7f63eda072 100644 --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -63,8 +63,8 @@ final class HarbormasterWaitForPreviousBuildStepImplementation $call = new ConduitCall( 'diffusion.commitparentsquery', array( - 'commit' => $commit->getCommitIdentifier(), - 'callsign' => $commit->getRepository()->getCallsign(), + 'commit' => $commit->getCommitIdentifier(), + 'repository' => $commit->getRepository()->getPHID(), )); $call->setUser(PhabricatorUser::getOmnipotentUser()); $parents = $call->execute(); diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 6193e5b367..f87a4bb59b 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -270,7 +270,7 @@ final class PhabricatorOwnersDetailController } $href = DiffusionRequest::generateDiffusionURI( array( - 'callsign' => $repo->getCallsign(), + 'repository' => $repo, 'branch' => $repo->getDefaultBranch(), 'path' => $path->getPath(), 'action' => 'browse', diff --git a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php index 63a807ea55..b018e32e34 100644 --- a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php +++ b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php @@ -26,7 +26,7 @@ final class PhabricatorCommitBranchesField $params = array( 'contains' => $this->getObject()->getCommitIdentifier(), - 'callsign' => $this->getObject()->getRepository()->getCallsign(), + 'repository' => $this->getObject()->getRepository()->getPHID(), ); try { diff --git a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php index 267b440dce..c8a53b387d 100644 --- a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php @@ -39,7 +39,7 @@ final class PhabricatorCommitMergedCommitsField id(new ConduitCall('diffusion.mergedcommitsquery', array( 'commit' => $commit->getCommitIdentifier(), 'limit' => $limit + 1, - 'callsign' => $commit->getRepository()->getCallsign(), + 'repository' => $commit->getRepository()->getPHID(), ))) ->setUser($this->getViewer()) ->execute()); diff --git a/src/applications/repository/customfield/PhabricatorCommitTagsField.php b/src/applications/repository/customfield/PhabricatorCommitTagsField.php index 001d81e960..5f8e1448c2 100644 --- a/src/applications/repository/customfield/PhabricatorCommitTagsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitTagsField.php @@ -26,7 +26,7 @@ final class PhabricatorCommitTagsField $params = array( 'commit' => $this->getObject()->getCommitIdentifier(), - 'callsign' => $this->getObject()->getRepository()->getCallsign(), + 'repository' => $this->getObject()->getRepository()->getPHID(), ); try { diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 1abc964f6c..1e15228630 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -177,7 +177,7 @@ final class PhabricatorRepositorySearchEngine if ($size) { $history_uri = DiffusionRequest::generateDiffusionURI( array( - 'callsign' => $repository->getCallsign(), + 'repository' => $repository, 'action' => 'history', )); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 1002fb9491..6996571b94 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -601,6 +601,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return '/diffusion/'.$this->getCallsign().'/'; } + public function getPathURI($path) { + return $this->getURI().$path; + } + public function getCommitURI($identifier) { $callsign = $this->getCallsign(); return "/r{$callsign}{$identifier}"; From 08de131da525efbbeb1ca0778c24f263497871f0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 1 Jan 2016 04:26:36 -0800 Subject: [PATCH 45/83] Begin modularizing main menu items Summary: Ref T10077. Ref T8918. The way the main menu is built is not very modular and fairly hacky. It assumes menus are provided by applications, but this isn't exactly true. Notably, the "Quick Create" menu is not per-application. The current method of building this menu is very inefficient (see T10077). Particularly, we have to build it //twice// because we need to build it once to render the item and then again to render the dropdown options. Start cleaning this up. This diff doesn't actually have any behavioral changes, since I can't swap the menu over until we get rid of all the other items and I haven't extended this to Notifications/Conpherence yet so it doesn't actually fix T8918. Test Plan: Viewed menus while logged in, logged out, in different applications, in desktop/mobile. Nothing appeared different. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8918, T10077 Differential Revision: https://secure.phabricator.com/D14922 --- src/__phutil_library_map__.php | 12 +++ .../PhabricatorAuthApplication.php | 42 --------- .../PhabricatorAuthMainMenuBarExtension.php | 73 +++++++++++++++ .../PhabricatorHelpApplication.php | 80 ----------------- .../PhabricatorHelpMainMenuBarExtension.php | 70 +++++++++++++++ .../PhabricatorPeopleApplication.php | 42 --------- .../PhabricatorPeopleMainMenuBarExtension.php | 49 ++++++++++ .../PhabricatorSettingsApplication.php | 22 ----- ...habricatorSettingsMainMenuBarExtension.php | 29 ++++++ .../menu/PhabricatorMainMenuBarExtension.php | 88 ++++++++++++++++++ .../page/menu/PhabricatorMainMenuView.php | 89 ++++++++++++++----- src/view/phui/PHUIMainMenuView.php | 31 +++++++ 12 files changed, 418 insertions(+), 209 deletions(-) create mode 100644 src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php create mode 100644 src/applications/help/extension/PhabricatorHelpMainMenuBarExtension.php create mode 100644 src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php create mode 100644 src/applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php create mode 100644 src/view/page/menu/PhabricatorMainMenuBarExtension.php create mode 100644 src/view/phui/PHUIMainMenuView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fdbd1d7539..22f7832780 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1500,6 +1500,7 @@ phutil_register_library_map(array( 'PHUIListItemView' => 'view/phui/PHUIListItemView.php', 'PHUIListView' => 'view/phui/PHUIListView.php', 'PHUIListViewTestCase' => 'view/layout/__tests__/PHUIListViewTestCase.php', + 'PHUIMainMenuView' => 'view/phui/PHUIMainMenuView.php', 'PHUIObjectBoxView' => 'view/phui/PHUIObjectBoxView.php', 'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php', 'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php', @@ -1722,6 +1723,7 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php', + 'PhabricatorAuthMainMenuBarExtension' => 'applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', @@ -2363,6 +2365,7 @@ phutil_register_library_map(array( 'PhabricatorHelpDocumentationController' => 'applications/help/controller/PhabricatorHelpDocumentationController.php', 'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php', 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php', + 'PhabricatorHelpMainMenuBarExtension' => 'applications/help/extension/PhabricatorHelpMainMenuBarExtension.php', 'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php', 'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php', 'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php', @@ -2480,6 +2483,7 @@ phutil_register_library_map(array( 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', + 'PhabricatorMainMenuBarExtension' => 'view/page/menu/PhabricatorMainMenuBarExtension.php', 'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php', 'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php', 'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php', @@ -2724,6 +2728,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php', 'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php', 'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php', + 'PhabricatorPeopleMainMenuBarExtension' => 'applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php', 'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php', 'PhabricatorPeopleNoOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleNoOwnerDatasource.php', 'PhabricatorPeopleOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleOwnerDatasource.php', @@ -3091,6 +3096,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsAdjustController' => 'applications/settings/controller/PhabricatorSettingsAdjustController.php', 'PhabricatorSettingsApplication' => 'applications/settings/application/PhabricatorSettingsApplication.php', 'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php', + 'PhabricatorSettingsMainMenuBarExtension' => 'applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php', 'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php', 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php', @@ -5615,6 +5621,7 @@ phutil_register_library_map(array( 'PHUIListItemView' => 'AphrontTagView', 'PHUIListView' => 'AphrontTagView', 'PHUIListViewTestCase' => 'PhabricatorTestCase', + 'PHUIMainMenuView' => 'AphrontView', 'PHUIObjectBoxView' => 'AphrontView', 'PHUIObjectItemListExample' => 'PhabricatorUIExample', 'PHUIObjectItemListView' => 'AphrontTagView', @@ -5863,6 +5870,7 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthLoginHandler' => 'Phobject', + 'PhabricatorAuthMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', @@ -6631,6 +6639,7 @@ phutil_register_library_map(array( 'PhabricatorHelpDocumentationController' => 'PhabricatorHelpController', 'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController', 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', + 'PhabricatorHelpMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorHeraldApplication' => 'PhabricatorApplication', 'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorHomeApplication' => 'PhabricatorApplication', @@ -6748,6 +6757,7 @@ phutil_register_library_map(array( 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorMailTarget' => 'Phobject', 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorMainMenuBarExtension' => 'Phobject', 'PhabricatorMainMenuSearchView' => 'AphrontView', 'PhabricatorMainMenuView' => 'AphrontView', 'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow', @@ -7037,6 +7047,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', + 'PhabricatorPeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorPeopleNewController' => 'PhabricatorPeopleController', 'PhabricatorPeopleNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', @@ -7488,6 +7499,7 @@ phutil_register_library_map(array( 'PhabricatorSettingsAdjustController' => 'PhabricatorController', 'PhabricatorSettingsApplication' => 'PhabricatorApplication', 'PhabricatorSettingsMainController' => 'PhabricatorController', + 'PhabricatorSettingsMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorSettingsPanel' => 'Phobject', 'PhabricatorSetupCheck' => 'Phobject', 'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php index dfa1be6f75..a9831b20e3 100644 --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -38,48 +38,6 @@ final class PhabricatorAuthApplication extends PhabricatorApplication { return array(); } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $items = array(); - - if ($user->isLoggedIn()) { - $item = id(new PHUIListItemView()) - ->addClass('core-menu-item') - ->setName(pht('Log Out')) - ->setIcon('fa-sign-out') - ->setWorkflow(true) - ->setHref('/logout/') - ->setSelected(($controller instanceof PhabricatorLogoutController)) - ->setAural(pht('Log Out')) - ->setOrder(900); - $items[] = $item; - } else { - if ($controller instanceof PhabricatorAuthController) { - // Don't show the "Login" item on auth controllers, since they're - // generally all related to logging in anyway. - } else { - $uri = new PhutilURI('/auth/start/'); - if ($controller) { - $path = $controller->getRequest()->getPath(); - $uri->setQueryParam('next', $path); - } - $item = id(new PHUIListItemView()) - ->addClass('core-menu-item') - ->setName(pht('Log In')) - // TODO: Login icon? - ->setIcon('fa-sign-in') - ->setHref($uri) - ->setAural(pht('Log In')) - ->setOrder(900); - $items[] = $item; - } - } - - return $items; - } - public function getApplicationGroup() { return self::GROUP_ADMIN; } diff --git a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php new file mode 100644 index 0000000000..47264517e0 --- /dev/null +++ b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php @@ -0,0 +1,73 @@ +getViewer(); + + if ($viewer->isLoggedIn()) { + return array( + $this->buildLogoutMenu(), + ); + } + + $controller = $this->getController(); + if ($controller instanceof PhabricatorAuthController) { + // Don't show the "Login" item on auth controllers, since they're + // generally all related to logging in anyway. + return array(); + } + + return array( + $this->buildLoginMenu(), + ); + } + + private function buildLogoutMenu() { + $controller = $this->getController(); + + $is_selected = ($controller instanceof PhabricatorLogoutController); + + $bar_item = id(new PHUIListItemView()) + ->addClass('core-menu-item') + ->setName(pht('Log Out')) + ->setIcon('fa-sign-out') + ->setWorkflow(true) + ->setHref('/logout/') + ->setSelected($is_selected) + ->setAural(pht('Log Out')); + + return id(new PHUIMainMenuView()) + ->setOrder(900) + ->setMenuBarItem($bar_item); + } + + private function buildLoginMenu() { + $controller = $this->getController(); + + $uri = new PhutilURI('/auth/start/'); + if ($controller) { + $path = $controller->getRequest()->getPath(); + $uri->setQueryParam('next', $path); + } + + $bar_item = id(new PHUIListItemView()) + ->addClass('core-menu-item') + ->setName(pht('Log In')) + ->setIcon('fa-sign-in') + ->setHref($uri) + ->setAural(pht('Log In')); + + return id(new PHUIMainMenuView()) + ->setOrder(900) + ->setMenuBarItem($bar_item); + } + +} diff --git a/src/applications/help/application/PhabricatorHelpApplication.php b/src/applications/help/application/PhabricatorHelpApplication.php index b1f66b02cd..deeae7fa94 100644 --- a/src/applications/help/application/PhabricatorHelpApplication.php +++ b/src/applications/help/application/PhabricatorHelpApplication.php @@ -25,84 +25,4 @@ final class PhabricatorHelpApplication extends PhabricatorApplication { ); } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $application = null; - if ($controller) { - $application = $controller->getCurrentApplication(); - } - - $items = array(); - - $help_id = celerity_generate_unique_node_id(); - - Javelin::initBehavior( - 'aphlict-dropdown', - array( - 'bubbleID' => $help_id, - 'dropdownID' => 'phabricator-help-menu', - 'applicationClass' => __CLASS__, - 'local' => true, - 'desktop' => true, - 'right' => true, - )); - - $item = id(new PHUIListItemView()) - ->setIcon('fa-life-ring') - ->addClass('core-menu-item') - ->setID($help_id) - ->setOrder(200); - - $hide = true; - if ($application) { - $help_name = pht('%s Help', $application->getName()); - $item - ->setName($help_name) - ->setHref('/help/documentation/'.get_class($application).'/') - ->setAural($help_name); - $help_items = $application->getHelpMenuItems($user); - if ($help_items) { - $hide = false; - } - } - if ($hide) { - $item->setStyle('display: none'); - } - $items[] = $item; - - return $items; - } - - public function buildMainMenuExtraNodes( - PhabricatorUser $viewer, - PhabricatorController $controller = null) { - - $application = null; - if ($controller) { - $application = $controller->getCurrentApplication(); - } - - $view = null; - if ($application) { - $help_items = $application->getHelpMenuItems($viewer); - if ($help_items) { - $view = new PHUIListView(); - foreach ($help_items as $item) { - $view->addMenuItem($item); - } - } - } - - return phutil_tag( - 'div', - array( - 'id' => 'phabricator-help-menu', - 'class' => 'phabricator-main-menu-dropdown phui-list-sidenav', - 'style' => 'display: none', - ), - $view); - } - } diff --git a/src/applications/help/extension/PhabricatorHelpMainMenuBarExtension.php b/src/applications/help/extension/PhabricatorHelpMainMenuBarExtension.php new file mode 100644 index 0000000000..f12714b5ac --- /dev/null +++ b/src/applications/help/extension/PhabricatorHelpMainMenuBarExtension.php @@ -0,0 +1,70 @@ +getApplication(); + if (!$application) { + return array(); + } + + $viewer = $this->getViewer(); + $help_links = $application->getHelpMenuItems($viewer); + if (!$help_links) { + return array(); + } + + $help_id = celerity_generate_unique_node_id(); + + Javelin::initBehavior( + 'aphlict-dropdown', + array( + 'bubbleID' => $help_id, + 'dropdownID' => 'phabricator-help-menu', + 'local' => true, + 'desktop' => true, + 'right' => true, + )); + + $help_name = pht('%s Help', $application->getName()); + + $help_item = id(new PHUIListItemView()) + ->setIcon('fa-life-ring') + ->addClass('core-menu-item') + ->setID($help_id) + ->setName($help_name) + ->setHref('/help/documentation/'.get_class($application).'/') + ->setAural($help_name); + + $view = new PHUIListView(); + foreach ($help_links as $help_link) { + $view->addMenuItem($help_link); + } + + $dropdown_menu = phutil_tag( + 'div', + array( + 'id' => 'phabricator-help-menu', + 'class' => 'phabricator-main-menu-dropdown phui-list-sidenav', + 'style' => 'display: none', + ), + $view); + + $help_menu = id(new PHUIMainMenuView()) + ->setOrder(200) + ->setMenuBarItem($help_item) + ->appendChild($dropdown_menu); + + return array( + $help_menu, + ); + } + +} diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index d1cb552e18..45599907c8 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -123,48 +123,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication { return $status; } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $items = array(); - - if ($user->isLoggedIn() && $user->isUserActivated()) { - $profile = id(new PhabricatorPeopleQuery()) - ->setViewer($user) - ->needProfileImage(true) - ->withPHIDs(array($user->getPHID())) - ->executeOne(); - $image = $profile->getProfileImageURI(); - - $item = id(new PHUIListItemView()) - ->setName($user->getUsername()) - ->setHref('/p/'.$user->getUsername().'/') - ->addClass('core-menu-item') - ->setAural(pht('Profile')) - ->setOrder(100); - - $classes = array( - 'phabricator-core-menu-icon', - 'phabricator-core-menu-profile-image', - ); - - $item->appendChild( - phutil_tag( - 'span', - array( - 'class' => implode(' ', $classes), - 'style' => 'background-image: url('.$image.')', - ), - '')); - - $items[] = $item; - } - - return $items; - } - - public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); diff --git a/src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php b/src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php new file mode 100644 index 0000000000..a92c5ba235 --- /dev/null +++ b/src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php @@ -0,0 +1,49 @@ +getViewer(); + + // TODO: This should get cached. + + $profile = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->needProfileImage(true) + ->withPHIDs(array($viewer->getPHID())) + ->executeOne(); + $image = $profile->getProfileImageURI(); + + $bar_item = id(new PHUIListItemView()) + ->setName($viewer->getUsername()) + ->setHref('/p/'.$viewer->getUsername().'/') + ->addClass('core-menu-item') + ->setAural(pht('Profile')); + + $classes = array( + 'phabricator-core-menu-icon', + 'phabricator-core-menu-profile-image', + ); + + $bar_item->appendChild( + phutil_tag( + 'span', + array( + 'class' => implode(' ', $classes), + 'style' => 'background-image: url('.$image.')', + ), + '')); + + $profile_menu = id(new PHUIMainMenuView()) + ->setOrder(100) + ->setMenuBarItem($bar_item); + + return array( + $profile_menu, + ); + } + +} diff --git a/src/applications/settings/application/PhabricatorSettingsApplication.php b/src/applications/settings/application/PhabricatorSettingsApplication.php index da0daf65d2..2608cabd88 100644 --- a/src/applications/settings/application/PhabricatorSettingsApplication.php +++ b/src/applications/settings/application/PhabricatorSettingsApplication.php @@ -40,26 +40,4 @@ final class PhabricatorSettingsApplication extends PhabricatorApplication { return self::GROUP_UTILITIES; } - public function buildMainMenuItems( - PhabricatorUser $user, - PhabricatorController $controller = null) { - - $items = array(); - - if ($user->isLoggedIn() && $user->isUserActivated()) { - $selected = ($controller instanceof PhabricatorSettingsMainController); - $item = id(new PHUIListItemView()) - ->setName(pht('Settings')) - ->setIcon('fa-wrench') - ->addClass('core-menu-item') - ->setSelected($selected) - ->setHref('/settings/') - ->setAural(pht('Settings')) - ->setOrder(400); - $items[] = $item; - } - - return $items; - } - } diff --git a/src/applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php b/src/applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php new file mode 100644 index 0000000000..21534c4237 --- /dev/null +++ b/src/applications/settings/extension/PhabricatorSettingsMainMenuBarExtension.php @@ -0,0 +1,29 @@ +getController(); + $is_selected = ($controller instanceof PhabricatorSettingsMainController); + + $bar_item = id(new PHUIListItemView()) + ->setName(pht('Settings')) + ->setIcon('fa-wrench') + ->addClass('core-menu-item') + ->setSelected($is_selected) + ->setHref('/settings/') + ->setAural(pht('Settings')); + + $settings_menu = id(new PHUIMainMenuView()) + ->setMenuBarItem($bar_item) + ->setOrder(400); + + return array( + $settings_menu, + ); + } + +} diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php new file mode 100644 index 0000000000..8b863c835c --- /dev/null +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -0,0 +1,88 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setApplication(PhabricatorApplication $application) { + $this->application = $application; + return $this; + } + + public function getApplication() { + return $this->application; + } + + public function setController(PhabricatorController $controller) { + $this->controller = $controller; + return $this; + } + + public function getController() { + return $this->controller; + } + + final public function getExtensionKey() { + return $this->getPhobjectClassConstant('MAINMENUBARKEY'); + } + + public function isExtensionEnabled() { + return true; + } + + public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { + if (!$viewer->isLoggedIn()) { + return false; + } + + if (!$viewer->isUserActivated()) { + return false; + } + + // Don't show menus for users with partial sessions. This usually means + // they have logged in but have not made it through MFA, so we don't want + // to show notification counts, saved queries, etc. + if (!$viewer->hasSession()) { + return false; + } + + if ($viewer->getSession()->getIsPartial()) { + return false; + } + + return true; + } + + abstract public function buildMainMenus(); + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + + final public static function getAllEnabledExtensions() { + $extensions = self::getAllExtensions(); + + foreach ($extensions as $key => $extension) { + if (!$extension->isExtensionEnabled()) { + unset($extensions[$key]); + } + } + + return $extensions; + } + +} diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 1dc8466df3..54ba622f80 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -30,7 +30,7 @@ final class PhabricatorMainMenuView extends AphrontView { require_celerity_resource('sprite-main-header-css'); $header_id = celerity_generate_unique_node_id(); - $menus = array(); + $menu_bar = array(); $alerts = array(); $search_button = ''; $app_button = ''; @@ -41,7 +41,7 @@ final class PhabricatorMainMenuView extends AphrontView { if (array_filter($menu)) { $alerts[] = $menu; } - $menus = array_merge($menus, $dropdowns); + $menu_bar = array_merge($menu_bar, $dropdowns); $app_button = $this->renderApplicationMenuButton($header_id); $search_button = $this->renderSearchMenuButton($header_id); } else { @@ -73,13 +73,69 @@ final class PhabricatorMainMenuView extends AphrontView { } $applications = PhabricatorApplication::getAllInstalledApplications(); + + $menus = array(); + $controller = $this->getController(); foreach ($applications as $application) { - $menus[] = $application->buildMainMenuExtraNodes( + $app_actions = $application->buildMainMenuItems( $user, - $this->getController()); + $controller); + $app_extra = $application->buildMainMenuExtraNodes( + $user, + $controller); + + foreach ($app_actions as $action) { + $menus[] = id(new PHUIMainMenuView()) + ->setMenuBarItem($action) + ->setOrder($action->getOrder()); + } + + if ($app_extra !== null) { + $menus[] = id(new PHUIMainMenuView()) + ->appendChild($app_extra); + } } - $application_menu = $this->renderApplicationMenu(); + $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); + foreach ($extensions as $extension) { + $extension->setViewer($user); + + $controller = $this->getController(); + if ($controller) { + $extension->setController($controller); + $application = $controller->getCurrentApplication(); + if ($application) { + $extension->setApplication($application); + } + } + } + + foreach ($extensions as $key => $extension) { + if (!$extension->isExtensionEnabledForViewer($extension->getViewer())) { + unset($extensions[$key]); + } + } + + foreach ($extensions as $extension) { + foreach ($extension->buildMainMenus() as $menu) { + $menus[] = $menu; + } + } + + $menus = msort($menus, 'getOrder'); + $bar_items = array(); + foreach ($menus as $menu) { + $menu_bar[] = $menu; + + $item = $menu->getMenuBarItem(); + if ($item === null) { + continue; + } + + $bar_items[] = $item; + } + + $application_menu = $this->renderApplicationMenu($bar_items); $classes = array(); $classes[] = 'phabricator-main-menu sprite-main-header'; $classes[] = 'phabricator-main-menu-background'; @@ -98,7 +154,7 @@ final class PhabricatorMainMenuView extends AphrontView { $aural, $application_menu, $search_menu, - $menus, + $menu_bar, )); } @@ -174,21 +230,8 @@ final class PhabricatorMainMenuView extends AphrontView { '')); } - public function renderApplicationMenu() { + private function renderApplicationMenu(array $bar_items) { $user = $this->getUser(); - $controller = $this->getController(); - - $applications = PhabricatorApplication::getAllInstalledApplications(); - - $actions = array(); - foreach ($applications as $application) { - $app_actions = $application->buildMainMenuItems($user, $controller); - foreach ($app_actions as $action) { - $actions[] = $action; - } - } - - $actions = msort($actions, 'getOrder'); $view = $this->getApplicationMenu(); @@ -199,13 +242,13 @@ final class PhabricatorMainMenuView extends AphrontView { $view->addClass('phabricator-dark-menu'); $view->addClass('phabricator-application-menu'); - if ($actions) { + if ($bar_items) { $view->addMenuItem( id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_LABEL) ->setName(pht('Actions'))); - foreach ($actions as $action) { - $view->addMenuItem($action); + foreach ($bar_items as $bar_item) { + $view->addMenuItem($bar_item); } } diff --git a/src/view/phui/PHUIMainMenuView.php b/src/view/phui/PHUIMainMenuView.php new file mode 100644 index 0000000000..7d5910dd7b --- /dev/null +++ b/src/view/phui/PHUIMainMenuView.php @@ -0,0 +1,31 @@ +menuItem = $menu_item; + return $this; + } + + public function getMenuBarItem() { + return $this->menuItem; + } + + public function setOrder($order) { + $this->order = $order; + return $this; + } + + public function getOrder() { + return $this->order; + } + + public function render() { + return $this->renderChildren(); + } + +} From 07e2596aa14c4487a5f919b3deb1e4b313c7a3ee Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 4 Jan 2016 08:00:13 -0800 Subject: [PATCH 46/83] Move generateDiffusionURI() into PhabricatorRepository Summary: Ref T4245. This further reduces the reliance on callsigns in Diffusion. Test Plan: - Pretty reasonable test coverage already exists. - Browsed repository list, browse view, history view, content view, change view, commit view, tag view, branch view of repositories. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14937 --- .../DiffusionExternalController.php | 16 +- .../diffusion/request/DiffusionRequest.php | 175 +----------------- .../__tests__/DiffusionURITestCase.php | 19 +- .../PhabricatorOwnersDetailController.php | 3 +- .../PhabricatorRepositorySearchEngine.php | 3 +- .../storage/PhabricatorRepository.php | 135 ++++++++++++++ 6 files changed, 155 insertions(+), 196 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionExternalController.php b/src/applications/diffusion/controller/DiffusionExternalController.php index 8faa9275d1..b62ee94e27 100644 --- a/src/applications/diffusion/controller/DiffusionExternalController.php +++ b/src/applications/diffusion/controller/DiffusionExternalController.php @@ -45,13 +45,13 @@ final class DiffusionExternalController extends DiffusionController { if ($best_match) { $repository = $repositories[$best_match]; - $redirect = DiffusionRequest::generateDiffusionURI( + $redirect = $repository->generateURI( array( - 'action' => 'browse', - 'repository' => $repository, - 'branch' => $repository->getDefaultBranch(), - 'commit' => $id, + 'action' => 'browse', + 'branch' => $repository->getDefaultBranch(), + 'commit' => $id, )); + return id(new AphrontRedirectResponse())->setURI($redirect); } } @@ -83,10 +83,9 @@ final class DiffusionExternalController extends DiffusionController { } else if (count($commits) == 1) { $commit = head($commits); $repo = $repositories[$commit->getRepositoryID()]; - $redirect = DiffusionRequest::generateDiffusionURI( + $redirect = $repo->generateURI( array( 'action' => 'browse', - 'repository' => $repo, 'branch' => $repo->getDefaultBranch(), 'commit' => $commit->getCommitIdentifier(), )); @@ -96,10 +95,9 @@ final class DiffusionExternalController extends DiffusionController { $rows = array(); foreach ($commits as $commit) { $repo = $repositories[$commit->getRepositoryID()]; - $href = DiffusionRequest::generateDiffusionURI( + $href = $repo->generateURI( array( 'action' => 'browse', - 'repository' => $repo, 'branch' => $repo->getDefaultBranch(), 'commit' => $commit->getCommitIdentifier(), )); diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 9df5efc2c8..5f10617105 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -448,15 +448,6 @@ abstract class DiffusionRequest extends Phobject { /* -( Managing Diffusion URIs )-------------------------------------------- */ - /** - * Generate a Diffusion URI using this request to provide defaults. See - * @{method:generateDiffusionURI} for details. This method is the same, but - * preserves the request parameters if they are not overridden. - * - * @param map See @{method:generateDiffusionURI}. - * @return PhutilURI Generated URI. - * @task uri - */ public function generateURI(array $params) { if (empty($params['stable'])) { $default_commit = $this->getSymbolicCommit(); @@ -465,181 +456,21 @@ abstract class DiffusionRequest extends Phobject { } $defaults = array( - 'repository' => $this->getRepository(), 'path' => $this->getPath(), 'branch' => $this->getBranch(), 'commit' => $default_commit, 'lint' => idx($params, 'lint', $this->getLint()), ); + foreach ($defaults as $key => $val) { if (!isset($params[$key])) { // Overwrite NULL. $params[$key] = $val; } } - return self::generateDiffusionURI($params); + + return $this->getRepository()->generateURI($params); } - - /** - * Generate a Diffusion URI from a parameter map. Applies the correct encoding - * and formatting to the URI. Parameters are: - * - * - `action` One of `history`, `browse`, `change`, `lastmodified`, - * `branch`, `tags`, `branches`, or `revision-ref`. The action specified - * by the URI. - * - `repository` Repository. - * - `callsign` Repository callsign. - * - `branch` Optional if action is not `branch`, branch name. - * - `path` Optional, path to file. - * - `commit` Optional, commit identifier. - * - `line` Optional, line range. - * - `lint` Optional, lint code. - * - `params` Optional, query parameters. - * - * The function generates the specified URI and returns it. - * - * @param map See documentation. - * @return PhutilURI Generated URI. - * @task uri - */ - public static function generateDiffusionURI(array $params) { - $action = idx($params, 'action'); - - $repository = idx($params, 'repository'); - - if ($repository) { - $callsign = $repository->getCallsign(); - } else { - $callsign = idx($params, 'callsign'); - } - - $path = idx($params, 'path'); - $branch = idx($params, 'branch'); - $commit = idx($params, 'commit'); - $line = idx($params, 'line'); - - if (strlen($callsign)) { - $callsign = phutil_escape_uri_path_component($callsign).'/'; - } - - if (strlen($branch)) { - $branch = phutil_escape_uri_path_component($branch).'/'; - } - - if (strlen($path)) { - $path = ltrim($path, '/'); - $path = str_replace(array(';', '$'), array(';;', '$$'), $path); - $path = phutil_escape_uri($path); - } - - $path = "{$branch}{$path}"; - - if (strlen($commit)) { - $commit = str_replace('$', '$$', $commit); - $commit = ';'.phutil_escape_uri($commit); - } - - if (strlen($line)) { - $line = '$'.phutil_escape_uri($line); - } - - $req_callsign = false; - $req_branch = false; - $req_commit = false; - - switch ($action) { - case 'history': - case 'browse': - case 'change': - case 'lastmodified': - case 'tags': - case 'branches': - case 'lint': - case 'refs': - $req_callsign = true; - break; - case 'branch': - $req_callsign = true; - $req_branch = true; - break; - case 'commit': - $req_callsign = true; - $req_commit = true; - break; - } - - if ($req_callsign && !strlen($callsign)) { - throw new Exception( - pht( - "Diffusion URI action '%s' requires callsign!", - $action)); - } - - if ($req_commit && !strlen($commit)) { - throw new Exception( - pht( - "Diffusion URI action '%s' requires commit!", - $action)); - } - - switch ($action) { - case 'change': - case 'history': - case 'browse': - case 'lastmodified': - case 'tags': - case 'branches': - case 'lint': - case 'pathtree': - case 'refs': - $uri = "/diffusion/{$callsign}{$action}/{$path}{$commit}{$line}"; - break; - case 'branch': - if (strlen($path)) { - $uri = "/diffusion/{$callsign}repository/{$path}"; - } else { - $uri = "/diffusion/{$callsign}"; - } - break; - case 'external': - $commit = ltrim($commit, ';'); - $uri = "/diffusion/external/{$commit}/"; - break; - case 'rendering-ref': - // This isn't a real URI per se, it's passed as a query parameter to - // the ajax changeset stuff but then we parse it back out as though - // it came from a URI. - $uri = rawurldecode("{$path}{$commit}"); - break; - case 'commit': - $commit = ltrim($commit, ';'); - $callsign = rtrim($callsign, '/'); - $uri = "/r{$callsign}{$commit}"; - break; - default: - throw new Exception(pht("Unknown Diffusion URI action '%s'!", $action)); - } - - if ($action == 'rendering-ref') { - return $uri; - } - - $uri = new PhutilURI($uri); - - if (isset($params['lint'])) { - $params['params'] = idx($params, 'params', array()) + array( - 'lint' => $params['lint'], - ); - } - - if (idx($params, 'params')) { - $uri->setQueryParams($params['params']); - } - - return $uri; - } - - /** * Internal. Public only for unit tests. * diff --git a/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php b/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php index 43a2bd3404..b7899de9cd 100644 --- a/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php +++ b/src/applications/diffusion/request/__tests__/DiffusionURITestCase.php @@ -86,10 +86,15 @@ final class DiffusionURITestCase extends PhutilTestCase { } public function testURIGeneration() { + $actor = PhabricatorUser::getOmnipotentUser(); + + $repository = PhabricatorRepository::initializeNewRepository($actor) + ->setCallsign('A') + ->makeEphemeral(); + $map = array( '/diffusion/A/browse/branch/path.ext;abc$1' => array( 'action' => 'browse', - 'callsign' => 'A', 'branch' => 'branch', 'path' => 'path.ext', 'commit' => 'abc', @@ -97,24 +102,20 @@ final class DiffusionURITestCase extends PhutilTestCase { ), '/diffusion/A/browse/a%252Fb/path.ext' => array( 'action' => 'browse', - 'callsign' => 'A', 'branch' => 'a/b', 'path' => 'path.ext', ), '/diffusion/A/browse/%2B/%20%21' => array( 'action' => 'browse', - 'callsign' => 'A', 'path' => '+/ !', ), '/diffusion/A/browse/money/%24%24100$2' => array( 'action' => 'browse', - 'callsign' => 'A', 'path' => 'money/$100', 'line' => '2', ), '/diffusion/A/browse/path/to/file.ext?view=things' => array( 'action' => 'browse', - 'callsign' => 'A', 'path' => 'path/to/file.ext', 'params' => array( 'view' => 'things', @@ -122,7 +123,6 @@ final class DiffusionURITestCase extends PhutilTestCase { ), '/diffusion/A/repository/master/' => array( 'action' => 'branch', - 'callsign' => 'A', 'branch' => 'master', ), 'path/to/file.ext;abc' => array( @@ -132,7 +132,6 @@ final class DiffusionURITestCase extends PhutilTestCase { ), '/diffusion/A/browse/branch/path.ext$3-5%2C7-12%2C14' => array( 'action' => 'browse', - 'callsign' => 'A', 'branch' => 'branch', 'path' => 'path.ext', 'line' => '3-5,7-12,14', @@ -140,10 +139,8 @@ final class DiffusionURITestCase extends PhutilTestCase { ); foreach ($map as $expect => $input) { - $actual = DiffusionRequest::generateDiffusionURI($input); - $this->assertEqual( - $expect, - (string)$actual); + $actual = $repository->generateURI($input); + $this->assertEqual($expect, (string)$actual); } } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index f87a4bb59b..7565da6287 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -268,9 +268,8 @@ final class PhabricatorOwnersDetailController if (!$repo) { continue; } - $href = DiffusionRequest::generateDiffusionURI( + $href = $repo->generateURI( array( - 'repository' => $repo, 'branch' => $repo->getDefaultBranch(), 'path' => $path->getPath(), 'action' => 'browse', diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 1e15228630..0587e314b7 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -175,9 +175,8 @@ final class PhabricatorRepositorySearchEngine $size = $repository->getCommitCount(); if ($size) { - $history_uri = DiffusionRequest::generateDiffusionURI( + $history_uri = $repository->generateURI( array( - 'repository' => $repository, 'action' => 'history', )); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 6996571b94..97b9684d0b 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -610,6 +610,141 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return "/r{$callsign}{$identifier}"; } + public function generateURI(array $params) { + $req_branch = false; + $req_commit = false; + + $action = idx($params, 'action'); + switch ($action) { + case 'history': + case 'browse': + case 'change': + case 'lastmodified': + case 'tags': + case 'branches': + case 'lint': + case 'pathtree': + case 'refs': + break; + case 'branch': + $req_branch = true; + break; + case 'commit': + case 'rendering-ref': + $req_commit = true; + break; + default: + throw new Exception( + pht( + 'Action "%s" is not a valid repository URI action.', + $action)); + } + + $path = idx($params, 'path'); + $branch = idx($params, 'branch'); + $commit = idx($params, 'commit'); + $line = idx($params, 'line'); + + if ($req_commit && !strlen($commit)) { + throw new Exception( + pht( + 'Diffusion URI action "%s" requires commit!', + $action)); + } + + if ($req_branch && !strlen($branch)) { + throw new Exception( + pht( + 'Diffusion URI action "%s" requires branch!', + $action)); + } + + if ($action === 'commit') { + return $this->getCommitURI($commit); + } + + + $identifier = $this->getID(); + + $callsign = $this->getCallsign(); + if ($callsign !== null) { + $identifier = $callsign; + } + + if (strlen($identifier)) { + $identifier = phutil_escape_uri_path_component($identifier); + } + + if (strlen($path)) { + $path = ltrim($path, '/'); + $path = str_replace(array(';', '$'), array(';;', '$$'), $path); + $path = phutil_escape_uri($path); + } + + if (strlen($branch)) { + $branch = phutil_escape_uri_path_component($branch); + $path = "{$branch}/{$path}"; + } + + if (strlen($commit)) { + $commit = str_replace('$', '$$', $commit); + $commit = ';'.phutil_escape_uri($commit); + } + + if (strlen($line)) { + $line = '$'.phutil_escape_uri($line); + } + + switch ($action) { + case 'change': + case 'history': + case 'browse': + case 'lastmodified': + case 'tags': + case 'branches': + case 'lint': + case 'pathtree': + case 'refs': + $uri = "/diffusion/{$identifier}/{$action}/{$path}{$commit}{$line}"; + break; + case 'branch': + if (strlen($path)) { + $uri = "/diffusion/{$identifier}/repository/{$path}"; + } else { + $uri = "/diffusion/{$identifier}/"; + } + break; + case 'external': + $commit = ltrim($commit, ';'); + $uri = "/diffusion/external/{$commit}/"; + break; + case 'rendering-ref': + // This isn't a real URI per se, it's passed as a query parameter to + // the ajax changeset stuff but then we parse it back out as though + // it came from a URI. + $uri = rawurldecode("{$path}{$commit}"); + break; + } + + if ($action == 'rendering-ref') { + return $uri; + } + + $uri = new PhutilURI($uri); + + if (isset($params['lint'])) { + $params['params'] = idx($params, 'params', array()) + array( + 'lint' => $params['lint'], + ); + } + + if (idx($params, 'params')) { + $uri->setQueryParams($params['params']); + } + + return $uri; + } + public function getNormalizedPath() { $uri = (string)$this->getCloneURIObject(); From fb3b4ee532acc7bf1ec22c5568318d5bab51495e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 04:42:07 -0800 Subject: [PATCH 47/83] Make CommitController more flexible about handling URIs Summary: Ref T4245. This adds support for both ID-based and callsign-based routes, although the ID-based routes don't occur anywhere. Also moves toward simplifying the DiffusionRequest stuff. Test Plan: Visited normal callsign-based commit pages; visited new ID-based commit pages. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14940 --- .../PhabricatorDiffusionApplication.php | 7 ++- .../controller/DiffusionCommitController.php | 18 +++----- .../controller/DiffusionController.php | 45 ++++++++++++++++++- .../diffusion/request/DiffusionRequest.php | 6 ++- .../query/PhabricatorRepositoryQuery.php | 2 +- .../storage/PhabricatorRepositoryCommit.php | 8 +++- 6 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index cbbfff8763..db967bad63 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -47,8 +47,13 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/r(?P[A-Z]+)(?P[a-z0-9]+)' + '/(?:'. + 'r(?P[A-Z]+)'. + '|'. + 'R(?P[1-9]\d*):'. + ')(?P[a-f0-9]+)' => 'DiffusionCommitController', + '/diffusion/' => array( '(?:query/(?P[^/]+)/)?' => 'DiffusionRepositoryListController', diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 42a64f416f..2b0e589687 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -17,20 +17,16 @@ final class DiffusionCommitController extends DiffusionController { return true; } - protected function shouldLoadDiffusionRequest() { - return false; - } + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); - protected function processDiffusionRequest(AphrontRequest $request) { $user = $request->getUser(); - // This controller doesn't use blob/path stuff, just pass the dictionary - // in directly instead of using the AphrontRequest parsing mechanism. - $data = $request->getURIMap(); - $data['user'] = $user; - $drequest = DiffusionRequest::newFromDictionary($data); - $this->diffusionRequest = $drequest; - if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); } diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 89446e132f..daff943438 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -35,7 +35,7 @@ abstract class DiffusionController extends PhabricatorController { return true; } - final public function handleRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { if ($request->getURIData('callsign') && $this->shouldLoadDiffusionRequest()) { try { @@ -48,10 +48,51 @@ abstract class DiffusionController extends PhabricatorController { } $this->setDiffusionRequest($drequest); } + return $this->processDiffusionRequest($request); } - abstract protected function processDiffusionRequest(AphrontRequest $request); + protected function loadDiffusionContext() { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $identifier = $request->getURIData('repositoryCallsign'); + if (!strlen($identifier)) { + $identifier = (int)$request->getURIData('repositoryID'); + } + + $blob = $request->getURIData('dblob'); + if (strlen($blob)) { + $parsed = DiffusionRequest::parseRequestBlob($blob); + } else { + $parsed = array( + 'commit' => $request->getURIData('commit'), + 'path' => $request->getURIData('path'), + 'line' => $request->getURIData('line'), + 'branch' => $request->getURIData('branch'), + 'lint' => $request->getStr('lint'), + ); + } + + $params = array( + 'repository' => $identifier, + 'user' => $viewer, + ) + $parsed; + + $drequest = DiffusionRequest::newFromDictionary($params); + + if (!$drequest) { + return new Aphront404Response(); + } + + $this->diffusionRequest = $drequest; + + return null; + } + + protected function processDiffusionRequest(AphrontRequest $request) { + throw new PhutilMethodNotImplementedException(); + } public function buildCrumbs(array $spec = array()) { $crumbs = $this->buildApplicationCrumbs(); diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 5f10617105..a9a062167b 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -105,6 +105,10 @@ abstract class DiffusionRequest extends Phobject { $object = self::newFromRepository($repository); } + if (!$object) { + return null; + } + $object->initializeFromDictionary($data); return $object; @@ -175,7 +179,7 @@ abstract class DiffusionRequest extends Phobject { ->executeOne(); if (!$repository) { - throw new Exception(pht("No such repository '%s'.", $identifier)); + return null; } return self::newFromRepository($repository); diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 9bb15d1804..38fa5bf56c 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -55,7 +55,7 @@ final class PhabricatorRepositoryQuery $monograms = array(); foreach ($identifiers as $identifier) { - if (ctype_digit($identifier)) { + if (ctype_digit((string)$identifier)) { $ids[$identifier] = $identifier; } else if (preg_match('/^(r[A-Z]+)|(R[1-9]\d*)\z/', $identifier)) { $monograms[$identifier] = $identifier; diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 1db4b71d5c..987c42492a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -252,8 +252,12 @@ final class PhabricatorRepositoryCommit $repository = $this->getRepository(); $callsign = $repository->getCallsign(); $identifier = $this->getCommitIdentifier(); - - return "r{$callsign}{$identifier}"; + if ($callsign !== null) { + return "r{$callsign}{$identifier}"; + } else { + $id = $repository->getID(); + return "R{$id}:{$identifier}"; + } } public function getDisplayName() { From 7de17fb75ec344671a7c9add7f2161d760631d5c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 05:00:22 -0800 Subject: [PATCH 48/83] Modernize tag and branch controllers in Diffusion Summary: Ref T4245. Prepares these controllers to accept alternate identifers, plus minor spacing and layout fixes. Test Plan: Viewed tags, viewed branches. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14941 --- .../PhabricatorDiffusionApplication.php | 2 +- .../DiffusionBranchTableController.php | 37 ++++++++++-------- .../controller/DiffusionController.php | 27 +++++++------ .../controller/DiffusionTagListController.php | 39 +++++++++++-------- .../diffusion/request/DiffusionRequest.php | 6 +++ 5 files changed, 63 insertions(+), 48 deletions(-) diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index db967bad63..6be8f8ffe8 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -64,7 +64,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', ), - '(?P[A-Z]+)/' => array( + '(?P(?P[A-Z]+))/' => array( '' => 'DiffusionRepositoryController', 'repository/(?P.*)' => 'DiffusionRepositoryController', diff --git a/src/applications/diffusion/controller/DiffusionBranchTableController.php b/src/applications/diffusion/controller/DiffusionBranchTableController.php index 98157f8056..a040c8084e 100644 --- a/src/applications/diffusion/controller/DiffusionBranchTableController.php +++ b/src/applications/diffusion/controller/DiffusionBranchTableController.php @@ -6,15 +6,18 @@ final class DiffusionBranchTableController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->getDiffusionRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $pager = new PHUIPagerView(); - $pager->setURI($request->getRequestURI(), 'offset'); - $pager->setOffset($request->getInt('offset')); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); // TODO: Add support for branches that contain commit $branches = $this->callConduitWithDiffusionRequest( @@ -57,18 +60,20 @@ final class DiffusionBranchTableController extends DiffusionController { 'branches' => true, )); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - $pager, - ), - array( - 'title' => array( + $pager_box = $this->renderTablePagerBox($pager); + + return $this->newPage() + ->setTitle( + array( pht('Branches'), $repository->getDisplayName(), - ), - )); + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, + $pager_box, + )); } } diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index daff943438..dc916c7e9e 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -61,23 +61,16 @@ abstract class DiffusionController extends PhabricatorController { $identifier = (int)$request->getURIData('repositoryID'); } - $blob = $request->getURIData('dblob'); - if (strlen($blob)) { - $parsed = DiffusionRequest::parseRequestBlob($blob); - } else { - $parsed = array( - 'commit' => $request->getURIData('commit'), - 'path' => $request->getURIData('path'), - 'line' => $request->getURIData('line'), - 'branch' => $request->getURIData('branch'), - 'lint' => $request->getStr('lint'), - ); - } - $params = array( 'repository' => $identifier, 'user' => $viewer, - ) + $parsed; + 'blob' => $request->getURIData('dblob'), + 'commit' => $request->getURIData('commit'), + 'path' => $request->getURIData('path'), + 'line' => $request->getURIData('line'), + 'branch' => $request->getURIData('branch'), + 'lint' => $request->getStr('lint'), + ); $drequest = DiffusionRequest::newFromDictionary($params); @@ -286,4 +279,10 @@ abstract class DiffusionController extends PhabricatorController { ->appendChild($body); } + protected function renderTablePagerBox(PHUIPagerView $pager) { + return id(new PHUIBoxView()) + ->addMargin(PHUI::MARGIN_LARGE) + ->appendChild($pager); + } + } diff --git a/src/applications/diffusion/controller/DiffusionTagListController.php b/src/applications/diffusion/controller/DiffusionTagListController.php index a5e90bd46c..4749675bbb 100644 --- a/src/applications/diffusion/controller/DiffusionTagListController.php +++ b/src/applications/diffusion/controller/DiffusionTagListController.php @@ -6,22 +6,25 @@ final class DiffusionTagListController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->getDiffusionRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $pager = new PHUIPagerView(); - $pager->setURI($request->getRequestURI(), 'offset'); - $pager->setOffset($request->getInt('offset')); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); $params = array( 'limit' => $pager->getPageSize() + 1, 'offset' => $pager->getOffset(), ); - if ($drequest->getSymbolicCommit()) { + if (strlen($drequest->getSymbolicCommit())) { $is_commit = true; $params['commit'] = $drequest->getSymbolicCommit(); } else { @@ -79,18 +82,20 @@ final class DiffusionTagListController extends DiffusionController { 'commit' => $drequest->getSymbolicCommit(), )); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - $pager, - ), - array( - 'title' => array( + $pager_box = $this->renderTablePagerBox($pager); + + return $this->newPage() + ->setTitle( + array( pht('Tags'), $repository->getDisplayName(), - ), - )); + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, + $pager_box, + )); } } diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index a9a062167b..08b7c876c2 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -225,6 +225,12 @@ abstract class DiffusionRequest extends Phobject { * @task new */ final private function initializeFromDictionary(array $data) { + $blob = idx($data, 'blob'); + if (strlen($blob)) { + $blob = self::parseRequestBlob($blob, $this->supportsBranches()); + $data = $blob + $data; + } + $this->path = idx($data, 'path'); $this->line = idx($data, 'line'); $this->initFromConduit = idx($data, 'initFromConduit', true); From 8b6edaa4e238a809fe78e6d14ad0705545f8179f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 05:18:59 -0800 Subject: [PATCH 49/83] Merge and modernize Browse controllers in Diffusion Summary: Ref T4245. Browsing is huge and currently split across 5 files using controller delegation. Although having a huge file isn't great, I think the way it is split up is currently worse, and it gets weird with more flexible repository identifiers. So this is mostly merging five controllers into one, then a bit of modernization. I think this can probably be split up better by pulling some of it out into views, instead of using delegation. Test Plan: Browsed files, directories, and search results. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14942 --- src/__phutil_library_map__.php | 8 - .../PhabricatorDiffusionApplication.php | 2 +- .../controller/DiffusionBrowseController.php | 1484 ++++++++++++++++- .../DiffusionBrowseDirectoryController.php | 106 -- .../DiffusionBrowseFileController.php | 1134 ------------- .../DiffusionBrowseMainController.php | 39 - .../DiffusionBrowseSearchController.php | 231 --- 7 files changed, 1480 insertions(+), 1524 deletions(-) delete mode 100644 src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php delete mode 100644 src/applications/diffusion/controller/DiffusionBrowseFileController.php delete mode 100644 src/applications/diffusion/controller/DiffusionBrowseMainController.php delete mode 100644 src/applications/diffusion/controller/DiffusionBrowseSearchController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 22f7832780..a531b964be 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -533,12 +533,8 @@ phutil_register_library_map(array( 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', 'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php', - 'DiffusionBrowseDirectoryController' => 'applications/diffusion/controller/DiffusionBrowseDirectoryController.php', - 'DiffusionBrowseFileController' => 'applications/diffusion/controller/DiffusionBrowseFileController.php', - 'DiffusionBrowseMainController' => 'applications/diffusion/controller/DiffusionBrowseMainController.php', 'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php', 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', - 'DiffusionBrowseSearchController' => 'applications/diffusion/controller/DiffusionBrowseSearchController.php', 'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php', 'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', @@ -4501,12 +4497,8 @@ phutil_register_library_map(array( 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', - 'DiffusionBrowseDirectoryController' => 'DiffusionBrowseController', - 'DiffusionBrowseFileController' => 'DiffusionBrowseController', - 'DiffusionBrowseMainController' => 'DiffusionBrowseController', 'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBrowseResultSet' => 'Phobject', - 'DiffusionBrowseSearchController' => 'DiffusionBrowseController', 'DiffusionBrowseTableView' => 'DiffusionView', 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionChangeController' => 'DiffusionController', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 6be8f8ffe8..20b6f5e4cd 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -70,7 +70,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { 'repository/(?P.*)' => 'DiffusionRepositoryController', 'change/(?P.*)' => 'DiffusionChangeController', 'history/(?P.*)' => 'DiffusionHistoryController', - 'browse/(?P.*)' => 'DiffusionBrowseMainController', + 'browse/(?P.*)' => 'DiffusionBrowseController', 'lastmodified/(?P.*)' => 'DiffusionLastModifiedController', 'diff/' => 'DiffusionDiffController', 'tags/(?P.*)' => 'DiffusionTagListController', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index e6120b6670..4a04078d26 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1,11 +1,1485 @@ loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + + // Figure out if we're browsing a directory, a file, or a search result + // list. + + $grep = $request->getStr('grep'); + $find = $request->getStr('find'); + if (strlen($grep) || strlen($find)) { + return $this->browseSearch(); + } + + $results = DiffusionBrowseResultSet::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.browsequery', + array( + 'path' => $drequest->getPath(), + 'commit' => $drequest->getStableCommit(), + ))); + $reason = $results->getReasonForEmptyResultSet(); + $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); + + if ($is_file) { + return $this->browseFile($results); + } else { + return $this->browseDirectory($results); + } + } + + private function browseSearch() { + $drequest = $this->getDiffusionRequest(); + + $actions = $this->buildActionView($drequest); + $properties = $this->buildPropertyView($drequest, $actions); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeader($this->buildHeaderView($drequest)) + ->addPropertyList($properties); + + $content = array(); + + $content[] = $object_box; + $content[] = $this->renderSearchForm($collapsed = false); + $content[] = $this->renderSearchResults(); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'browse', + )); + + return $this->newPage() + ->setTitle( + array( + nonempty(basename($drequest->getPath()), '/'), + $drequest->getRepository()->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); + } + + private function browseFile() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $before = $request->getStr('before'); + if ($before) { + return $this->buildBeforeResponse($before); + } + + $path = $drequest->getPath(); + + $preferences = $viewer->loadPreferences(); + + $show_blame = $request->getBool( + 'blame', + $preferences->getPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, + false)); + $show_color = $request->getBool( + 'color', + $preferences->getPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, + true)); + + $view = $request->getStr('view'); + if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { + $preferences->setPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, + $show_blame); + $preferences->setPreference( + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, + $show_color); + $preferences->save(); + + $uri = $request->getRequestURI() + ->alter('blame', null) + ->alter('color', null); + + return id(new AphrontRedirectResponse())->setURI($uri); + } + + // We need the blame information if blame is on and we're building plain + // text, or blame is on and this is an Ajax request. If blame is on and + // this is a colorized request, we don't show blame at first (we ajax it + // in afterward) so we don't need to query for it. + $needs_blame = ($show_blame && !$show_color) || + ($show_blame && $request->isAjax()); + + $params = array( + 'commit' => $drequest->getCommit(), + 'path' => $drequest->getPath(), + 'needsBlame' => $needs_blame, + ); + + $byte_limit = null; + if ($view !== 'raw') { + $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); + $time_limit = 10; + + $params += array( + 'timeout' => $time_limit, + 'byteLimit' => $byte_limit, + ); + } + + $file_content = DiffusionFileContent::newFromConduit( + $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + $params)); + $data = $file_content->getCorpus(); + + if ($view === 'raw') { + return $this->buildRawResponse($path, $data); + } + + $this->loadLintMessages(); + $this->coverage = $drequest->loadCoverage(); + + if ($byte_limit && (strlen($data) == $byte_limit)) { + $corpus = $this->buildErrorCorpus( + pht( + 'This file is larger than %s byte(s), and too large to display '. + 'in the web UI.', + $byte_limit)); + } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { + $file = $this->loadFileForData($path, $data); + $file_uri = $file->getBestURI(); + + if ($file->isViewableImage()) { + $corpus = $this->buildImageCorpus($file_uri); + } else { + $corpus = $this->buildBinaryCorpus($file_uri, $data); + } + } else { + // Build the content of the file. + $corpus = $this->buildCorpus( + $show_blame, + $show_color, + $file_content, + $needs_blame, + $drequest, + $path, + $data); + } + + if ($request->isAjax()) { + return id(new AphrontAjaxResponse())->setContent($corpus); + } + + require_celerity_resource('diffusion-source-css'); + + // Render the page. + $view = $this->buildActionView($drequest); + $action_list = $this->enrichActionView( + $view, + $drequest, + $show_blame, + $show_color); + + $properties = $this->buildPropertyView($drequest, $action_list); + $object_box = id(new PHUIObjectBoxView()) + ->setHeader($this->buildHeaderView($drequest)) + ->addPropertyList($properties); + + $content = array(); + $content[] = $object_box; + + $follow = $request->getStr('follow'); + if ($follow) { + $notice = new PHUIInfoView(); + $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); + $notice->setTitle(pht('Unable to Continue')); + switch ($follow) { + case 'first': + $notice->appendChild( + pht( + 'Unable to continue tracing the history of this file because '. + 'this commit is the first commit in the repository.')); + break; + case 'created': + $notice->appendChild( + pht( + 'Unable to continue tracing the history of this file because '. + 'this commit created the file.')); + break; + } + $content[] = $notice; + } + + $renamed = $request->getStr('renamed'); + if ($renamed) { + $notice = new PHUIInfoView(); + $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); + $notice->setTitle(pht('File Renamed')); + $notice->appendChild( + pht( + 'File history passes through a rename from "%s" to "%s".', + $drequest->getPath(), + $renamed)); + $content[] = $notice; + } + + $content[] = $corpus; + $content[] = $this->buildOpenRevisions(); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'browse', + )); + + $basename = basename($this->getDiffusionRequest()->getPath()); + + return $this->newPage() + ->setTitle( + array( + $basename, + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); + } + + public function browseDirectory(DiffusionBrowseResultSet $results) { + $request = $this->getRequest(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $reason = $results->getReasonForEmptyResultSet(); + + $content = array(); + $actions = $this->buildActionView($drequest); + $properties = $this->buildPropertyView($drequest, $actions); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeader($this->buildHeaderView($drequest)) + ->addPropertyList($properties); + + $content[] = $object_box; + $content[] = $this->renderSearchForm($collapsed = true); + + if (!$results->isValidResults()) { + $empty_result = new DiffusionEmptyResultView(); + $empty_result->setDiffusionRequest($drequest); + $empty_result->setDiffusionBrowseResultSet($results); + $empty_result->setView($request->getStr('view')); + $content[] = $empty_result; + } else { + $phids = array(); + foreach ($results->getPaths() as $result) { + $data = $result->getLastCommitData(); + if ($data) { + if ($data->getCommitDetail('authorPHID')) { + $phids[$data->getCommitDetail('authorPHID')] = true; + } + } + } + + $phids = array_keys($phids); + $handles = $this->loadViewerHandles($phids); + + $browse_table = new DiffusionBrowseTableView(); + $browse_table->setDiffusionRequest($drequest); + $browse_table->setHandles($handles); + $browse_table->setPaths($results->getPaths()); + $browse_table->setUser($request->getUser()); + + $browse_panel = new PHUIObjectBoxView(); + $browse_panel->setHeaderText($drequest->getPath(), '/'); + $browse_panel->setTable($browse_table); + + $content[] = $browse_panel; + } + + $content[] = $this->buildOpenRevisions(); + + + $readme_path = $results->getReadmePath(); + if ($readme_path) { + $readme_content = $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + array( + 'path' => $readme_path, + 'commit' => $drequest->getStableCommit(), + )); + if ($readme_content) { + $content[] = id(new DiffusionReadmeView()) + ->setUser($this->getViewer()) + ->setPath($readme_path) + ->setContent($readme_content['corpus']); + } + } + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'browse', + )); + + return $this->newPage() + ->setTitle( + array( + nonempty(basename($drequest->getPath()), '/'), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); + } + + private function renderSearchResults() { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $results = array(); + + $limit = 100; + $page = $this->getRequest()->getInt('page', 0); + $pager = new PHUIPagerView(); + $pager->setPageSize($limit); + $pager->setOffset($page); + $pager->setURI($this->getRequest()->getRequestURI(), 'page'); + + $search_mode = null; + + switch ($repository->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + $results = array(); + break; + default: + if (strlen($this->getRequest()->getStr('grep'))) { + $search_mode = 'grep'; + $query_string = $this->getRequest()->getStr('grep'); + $results = $this->callConduitWithDiffusionRequest( + 'diffusion.searchquery', + array( + 'grep' => $query_string, + 'commit' => $drequest->getStableCommit(), + 'path' => $drequest->getPath(), + 'limit' => $limit + 1, + 'offset' => $page, + )); + } else { // Filename search. + $search_mode = 'find'; + $query_string = $this->getRequest()->getStr('find'); + $results = $this->callConduitWithDiffusionRequest( + 'diffusion.querypaths', + array( + 'pattern' => $query_string, + 'commit' => $drequest->getStableCommit(), + 'path' => $drequest->getPath(), + 'limit' => $limit + 1, + 'offset' => $page, + )); + } + break; + } + + $results = $pager->sliceResults($results); + + if ($search_mode == 'grep') { + $table = $this->renderGrepResults($results, $query_string); + $header = pht( + 'File content matching "%s" under "%s"', + $query_string, + nonempty($drequest->getPath(), '/')); + } else { + $table = $this->renderFindResults($results); + $header = pht( + 'Paths matching "%s" under "%s"', + $query_string, + nonempty($drequest->getPath(), '/')); + } + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($header) + ->setTable($table); + + $pager_box = $this->renderTablePagerBox($pager); + + return array($box, $pager_box); + } + + private function renderGrepResults(array $results, $pattern) { + $drequest = $this->getDiffusionRequest(); + + require_celerity_resource('phabricator-search-results-css'); + + $rows = array(); + foreach ($results as $result) { + list($path, $line, $string) = $result; + + $href = $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $path, + 'line' => $line, + )); + + $matches = null; + $count = @preg_match_all( + '('.$pattern.')u', + $string, + $matches, + PREG_OFFSET_CAPTURE); + + if (!$count) { + $output = ltrim($string); + } else { + $output = array(); + $cursor = 0; + $length = strlen($string); + foreach ($matches[0] as $match) { + $offset = $match[1]; + if ($cursor != $offset) { + $output[] = array( + 'text' => substr($string, $cursor, $offset), + 'highlight' => false, + ); + } + $output[] = array( + 'text' => $match[0], + 'highlight' => true, + ); + $cursor = $offset + strlen($match[0]); + } + if ($cursor != $length) { + $output[] = array( + 'text' => substr($string, $cursor), + 'highlight' => false, + ); + } + + if ($output) { + $output[0]['text'] = ltrim($output[0]['text']); + } + + foreach ($output as $key => $segment) { + if ($segment['highlight']) { + $output[$key] = phutil_tag('strong', array(), $segment['text']); + } else { + $output[$key] = $segment['text']; + } + } + } + + $string = phutil_tag( + 'pre', + array('class' => 'PhabricatorMonospaced phui-source-fragment'), + $output); + + $path = Filesystem::readablePath($path, $drequest->getPath()); + + $rows[] = array( + phutil_tag('a', array('href' => $href), $path), + $line, + $string, + ); + } + + $table = id(new AphrontTableView($rows)) + ->setClassName('remarkup-code') + ->setHeaders(array(pht('Path'), pht('Line'), pht('String'))) + ->setColumnClasses(array('', 'n', 'wide')) + ->setNoDataString( + pht( + 'The pattern you searched for was not found in the content of any '. + 'files.')); + + return $table; + } + + private function renderFindResults(array $results) { + $drequest = $this->getDiffusionRequest(); + + $rows = array(); + foreach ($results as $result) { + $href = $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $result, + )); + + $readable = Filesystem::readablePath($result, $drequest->getPath()); + + $rows[] = array( + phutil_tag('a', array('href' => $href), $readable), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders(array(pht('Path'))) + ->setColumnClasses(array('wide')) + ->setNoDataString( + pht( + 'The pattern you searched for did not match the names of any '. + 'files.')); + + return $table; + } + + private function loadLintMessages() { + $drequest = $this->getDiffusionRequest(); + $branch = $drequest->loadBranch(); + + if (!$branch || !$branch->getLintCommit()) { + return; + } + + $this->lintCommit = $branch->getLintCommit(); + + $conn = id(new PhabricatorRepository())->establishConnection('r'); + + $where = ''; + if ($drequest->getLint()) { + $where = qsprintf( + $conn, + 'AND code = %s', + $drequest->getLint()); + } + + $this->lintMessages = queryfx_all( + $conn, + 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', + PhabricatorRepository::TABLE_LINTMESSAGE, + $branch->getID(), + $where, + '/'.$drequest->getPath()); + } + + private function buildCorpus( + $show_blame, + $show_color, + DiffusionFileContent $file_content, + $needs_blame, + DiffusionRequest $drequest, + $path, + $data) { + + if (!$show_color) { + $style = + 'border: none; width: 100%; height: 80em; font-family: monospace'; + if (!$show_blame) { + $corpus = phutil_tag( + 'textarea', + array( + 'style' => $style, + ), + $file_content->getCorpus()); + } else { + $text_list = $file_content->getTextList(); + $rev_list = $file_content->getRevList(); + $blame_dict = $file_content->getBlameDict(); + + $rows = array(); + foreach ($text_list as $k => $line) { + $rev = $rev_list[$k]; + $author = $blame_dict[$rev]['author']; + $rows[] = + sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line); + } + + $corpus = phutil_tag( + 'textarea', + array( + 'style' => $style, + ), + implode("\n", $rows)); + } + } else { + require_celerity_resource('syntax-highlighting-css'); + $text_list = $file_content->getTextList(); + $rev_list = $file_content->getRevList(); + $blame_dict = $file_content->getBlameDict(); + + $text_list = implode("\n", $text_list); + $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( + $path, + $text_list); + $text_list = explode("\n", $text_list); + + $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, + $needs_blame, $drequest, $show_blame, $show_color); + + $corpus_table = javelin_tag( + 'table', + array( + 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', + 'sigil' => 'phabricator-source', + ), + $rows); + + if ($this->getRequest()->isAjax()) { + return $corpus_table; + } + + $id = celerity_generate_unique_node_id(); + + $repo = $drequest->getRepository(); + $symbol_repos = nonempty($repo->getSymbolSources(), array()); + $symbol_repos[] = $repo->getPHID(); + + $lang = last(explode('.', $drequest->getPath())); + $repo_languages = $repo->getSymbolLanguages(); + $repo_languages = nonempty($repo_languages, array()); + $repo_languages = array_fill_keys($repo_languages, true); + + $needs_symbols = true; + if ($repo_languages && $symbol_repos) { + $have_symbols = id(new DiffusionSymbolQuery()) + ->existsSymbolsInRepository($repo->getPHID()); + if (!$have_symbols) { + $needs_symbols = false; + } + } + + if ($needs_symbols && $repo_languages) { + $needs_symbols = isset($repo_languages[$lang]); + } + + if ($needs_symbols) { + Javelin::initBehavior( + 'repository-crossreference', + array( + 'container' => $id, + 'lang' => $lang, + 'repositories' => $symbol_repos, + )); + } + + $corpus = phutil_tag( + 'div', + array( + 'id' => $id, + ), + $corpus_table); + + Javelin::initBehavior('load-blame', array('id' => $id)); + } + + $edit = $this->renderEditButton(); + $file = $this->renderFileButton(); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('File Contents')) + ->addActionLink($edit) + ->addActionLink($file); + + $corpus = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($corpus) + ->setCollapsed(true); + + return $corpus; + } + + private function enrichActionView( + PhabricatorActionListView $view, + DiffusionRequest $drequest, + $show_blame, + $show_color) { + + $viewer = $this->getRequest()->getUser(); + $base_uri = $this->getRequest()->getRequestURI(); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Show Last Change')) + ->setHref( + $drequest->generateURI( + array( + 'action' => 'change', + ))) + ->setIcon('fa-backward')); + + if ($show_blame) { + $blame_text = pht('Disable Blame'); + $blame_icon = 'fa-exclamation-circle lightgreytext'; + $blame_value = 0; + } else { + $blame_text = pht('Enable Blame'); + $blame_icon = 'fa-exclamation-circle'; + $blame_value = 1; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($blame_text) + ->setHref($base_uri->alter('blame', $blame_value)) + ->setIcon($blame_icon) + ->setUser($viewer) + ->setRenderAsForm($viewer->isLoggedIn())); + + if ($show_color) { + $highlight_text = pht('Disable Highlighting'); + $highlight_icon = 'fa-star-o grey'; + $highlight_value = 0; + } else { + $highlight_text = pht('Enable Highlighting'); + $highlight_icon = 'fa-star'; + $highlight_value = 1; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($highlight_text) + ->setHref($base_uri->alter('color', $highlight_value)) + ->setIcon($highlight_icon) + ->setUser($viewer) + ->setRenderAsForm($viewer->isLoggedIn())); + + $href = null; + if ($this->getRequest()->getStr('lint') !== null) { + $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); + $href = $base_uri->alter('lint', null); + + } else if ($this->lintCommit === null) { + $lint_text = pht('Lint not Available'); + } else { + $lint_text = pht( + 'Show %d Lint Message(s)', + count($this->lintMessages)); + $href = $this->getDiffusionRequest()->generateURI(array( + 'action' => 'browse', + 'commit' => $this->lintCommit, + ))->alter('lint', ''); + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($lint_text) + ->setHref($href) + ->setIcon('fa-exclamation-triangle') + ->setDisabled(!$href)); + + return $view; + } + + private function renderEditButton() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $drequest = $this->getDiffusionRequest(); + + $repository = $drequest->getRepository(); + $path = $drequest->getPath(); + $line = nonempty((int)$drequest->getLine(), 1); + + $editor_link = $user->loadEditorLink($path, $line, $repository); + $template = $user->loadEditorLink($path, '%l', $repository); + + $icon_edit = id(new PHUIIconView()) + ->setIconFont('fa-pencil'); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Open in Editor')) + ->setHref($editor_link) + ->setIcon($icon_edit) + ->setID('editor_link') + ->setMetadata(array('link_template' => $template)) + ->setDisabled(!$editor_link); + + return $button; + } + + private function renderFileButton($file_uri = null) { + + $base_uri = $this->getRequest()->getRequestURI(); + + if ($file_uri) { + $text = pht('Download Raw File'); + $href = $file_uri; + $icon = 'fa-download'; + } else { + $text = pht('View Raw File'); + $href = $base_uri->alter('view', 'raw'); + $icon = 'fa-file-text'; + } + + $iconview = id(new PHUIIconView()) + ->setIconFont($icon); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText($text) + ->setHref($href) + ->setIcon($iconview); + + return $button; + } + + + private function buildDisplayRows( + array $text_list, + array $rev_list, + array $blame_dict, + $needs_blame, + DiffusionRequest $drequest, + $show_blame, + $show_color) { + + $handles = array(); + if ($blame_dict) { + $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); + $epoch_min = min($epoch_list); + $epoch_max = max($epoch_list); + $epoch_range = ($epoch_max - $epoch_min) + 1; + + $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); + $handles = $this->loadViewerHandles($author_phids); + } + + $line_arr = array(); + $line_str = $drequest->getLine(); + $ranges = explode(',', $line_str); + foreach ($ranges as $range) { + if (strpos($range, '-') !== false) { + list($min, $max) = explode('-', $range, 2); + $line_arr[] = array( + 'min' => min($min, $max), + 'max' => max($min, $max), + ); + } else if (strlen($range)) { + $line_arr[] = array( + 'min' => $range, + 'max' => $range, + ); + } + } + + $display = array(); + + $line_number = 1; + $last_rev = null; + $color = null; + foreach ($text_list as $k => $line) { + $display_line = array( + 'epoch' => null, + 'commit' => null, + 'author' => null, + 'target' => null, + 'highlighted' => null, + 'line' => $line_number, + 'data' => $line, + ); + + if ($show_blame) { + // If the line's rev is same as the line above, show empty content + // with same color; otherwise generate blame info. The newer a change + // is, the more saturated the color. + + $rev = idx($rev_list, $k, $last_rev); + + if ($last_rev == $rev) { + $display_line['color'] = $color; + } else { + $blame = $blame_dict[$rev]; + + if (!isset($blame['epoch'])) { + $color = '#ffd'; // Render as warning. + } else { + $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; + $color_value = 0xE6 * (1.0 - $color_ratio); + $color = sprintf( + '#%02x%02x%02x', + $color_value, + 0xF6, + $color_value); + } + + $display_line['epoch'] = idx($blame, 'epoch'); + $display_line['color'] = $color; + $display_line['commit'] = $rev; + + $author_phid = idx($blame, 'authorPHID'); + if ($author_phid && $handles[$author_phid]) { + $author_link = $handles[$author_phid]->renderLink(); + } else { + $author_link = $blame['author']; + } + $display_line['author'] = $author_link; + + $last_rev = $rev; + } + } + + if ($line_arr) { + if ($line_number == $line_arr[0]['min']) { + $display_line['target'] = true; + } + foreach ($line_arr as $range) { + if ($line_number >= $range['min'] && + $line_number <= $range['max']) { + $display_line['highlighted'] = true; + } + } + } + + $display[] = $display_line; + ++$line_number; + } + + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $commits = array_filter(ipull($display, 'commit')); + if ($commits) { + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepository($drequest->getRepository()) + ->withIdentifiers($commits) + ->execute(); + $commits = mpull($commits, null, 'getCommitIdentifier'); + } + + $revision_ids = id(new DifferentialRevision()) + ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); + $revisions = array(); + if ($revision_ids) { + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withIDs($revision_ids) + ->execute(); + } + + $phids = array(); + foreach ($commits as $commit) { + if ($commit->getAuthorPHID()) { + $phids[] = $commit->getAuthorPHID(); + } + } + foreach ($revisions as $revision) { + if ($revision->getAuthorPHID()) { + $phids[] = $revision->getAuthorPHID(); + } + } + $handles = $this->loadViewerHandles($phids); + + Javelin::initBehavior('phabricator-oncopy', array()); + + $engine = null; + $inlines = array(); + if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { + $engine = new PhabricatorMarkupEngine(); + $engine->setViewer($viewer); + + foreach ($this->lintMessages as $message) { + $inline = id(new PhabricatorAuditInlineComment()) + ->setSyntheticAuthor( + ArcanistLintSeverity::getStringForSeverity($message['severity']). + ' '.$message['code'].' ('.$message['name'].')') + ->setLineNumber($message['line']) + ->setContent($message['description']); + $inlines[$message['line']][] = $inline; + + $engine->addObject( + $inline, + PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); + } + + $engine->process(); + require_celerity_resource('differential-changeset-view-css'); + } + + $rows = $this->renderInlines( + idx($inlines, 0, array()), + $show_blame, + (bool)$this->coverage, + $engine); + + foreach ($display as $line) { + + $line_href = $drequest->generateURI( + array( + 'action' => 'browse', + 'line' => $line['line'], + 'stable' => true, + )); + + $blame = array(); + $style = null; + if (array_key_exists('color', $line)) { + if ($line['color']) { + $style = 'background: '.$line['color'].';'; + } + + $before_link = null; + $commit_link = null; + $revision_link = null; + if (idx($line, 'commit')) { + $commit = $line['commit']; + + if (idx($commits, $commit)) { + $tooltip = $this->renderCommitTooltip( + $commits[$commit], + $handles, + $line['author']); + } else { + $tooltip = null; + } + + Javelin::initBehavior('phabricator-tooltips', array()); + require_celerity_resource('aphront-tooltip-css'); + + $commit_link = javelin_tag( + 'a', + array( + 'href' => $drequest->generateURI( + array( + 'action' => 'commit', + 'commit' => $line['commit'], + )), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(9) + ->setTerminator('') + ->truncateString($line['commit'])); + + $revision_id = null; + if (idx($commits, $commit)) { + $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); + } + + if ($revision_id) { + $revision = idx($revisions, $revision_id); + if ($revision) { + $tooltip = $this->renderRevisionTooltip($revision, $handles); + $revision_link = javelin_tag( + 'a', + array( + 'href' => '/D'.$revision->getID(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + 'D'.$revision->getID()); + } + } + + $uri = $line_href->alter('before', $commit); + $before_link = javelin_tag( + 'a', + array( + 'href' => $uri->setQueryParam('view', 'blame'), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => pht('Skip Past This Commit'), + 'align' => 'E', + 'size' => 300, + ), + ), + "\xC2\xAB"); + } + + $blame[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-link', + ), + $before_link); + + $object_links = array(); + $object_links[] = $commit_link; + if ($revision_link) { + $object_links[] = phutil_tag('span', array(), '/'); + $object_links[] = $revision_link; + } + + $blame[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-rev-link', + ), + $object_links); + } + + $line_link = phutil_tag( + 'a', + array( + 'href' => $line_href, + 'style' => $style, + ), + $line['line']); + + $blame[] = javelin_tag( + 'th', + array( + 'class' => 'diffusion-line-link', + 'sigil' => 'phabricator-source-line', + 'style' => $style, + ), + $line_link); + + Javelin::initBehavior('phabricator-line-linker'); + + if ($line['target']) { + Javelin::initBehavior( + 'diffusion-jump-to', + array( + 'target' => 'scroll_target', + )); + $anchor_text = phutil_tag( + 'a', + array( + 'id' => 'scroll_target', + ), + ''); + } else { + $anchor_text = null; + } + + $blame[] = phutil_tag( + 'td', + array( + ), + array( + $anchor_text, + + // NOTE: See phabricator-oncopy behavior. + "\xE2\x80\x8B", + + // TODO: [HTML] Not ideal. + phutil_safe_html(str_replace("\t", ' ', $line['data'])), + )); + + if ($this->coverage) { + require_celerity_resource('differential-changeset-view-css'); + $cov_index = $line['line'] - 1; + + if (isset($this->coverage[$cov_index])) { + $cov_class = $this->coverage[$cov_index]; + } else { + $cov_class = 'N'; + } + + $blame[] = phutil_tag( + 'td', + array( + 'class' => 'cov cov-'.$cov_class, + ), + ''); + } + + $rows[] = phutil_tag( + 'tr', + array( + 'class' => ($line['highlighted'] ? + 'phabricator-source-highlight' : + null), + ), + $blame); + + $cur_inlines = $this->renderInlines( + idx($inlines, $line['line'], array()), + $show_blame, + $this->coverage, + $engine); + foreach ($cur_inlines as $cur_inline) { + $rows[] = $cur_inline; + } + } + + return $rows; + } + + private function renderInlines( + array $inlines, + $needs_blame, + $has_coverage, + $engine) { + + $rows = array(); + foreach ($inlines as $inline) { + + // TODO: This should use modern scaffolding code. + + $inline_view = id(new PHUIDiffInlineCommentDetailView()) + ->setUser($this->getViewer()) + ->setMarkupEngine($engine) + ->setInlineComment($inline) + ->render(); + + $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); + + $row[] = phutil_tag('td', array(), $inline_view); + + if ($has_coverage) { + $row[] = phutil_tag( + 'td', + array( + 'class' => 'cov cov-I', + )); + } + + $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); + } + + return $rows; + } + + private function loadFileForData($path, $data) { + $file = PhabricatorFile::buildFromFileDataOrHash( + $data, + array( + 'name' => basename($path), + 'ttl' => time() + 60 * 60 * 24, + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file->attachToObject( + $this->getDiffusionRequest()->getRepository()->getPHID()); + unset($unguarded); + + return $file; + } + + private function buildRawResponse($path, $data) { + $file = $this->loadFileForData($path, $data); + return $file->getRedirectResponse(); + } + + private function buildImageCorpus($file_uri) { + $properties = new PHUIPropertyListView(); + + $properties->addImageContent( + phutil_tag( + 'img', + array( + 'src' => $file_uri, + ))); + + $file = $this->renderFileButton($file_uri); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Image')) + ->addActionLink($file); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); + } + + private function buildBinaryCorpus($file_uri, $data) { + + $size = new PhutilNumber(strlen($data)); + $text = pht('This is a binary file. It is %s byte(s) in length.', $size); + $text = id(new PHUIBoxView()) + ->addPadding(PHUI::PADDING_LARGE) + ->appendChild($text); + + $file = $this->renderFileButton($file_uri); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Details')) + ->addActionLink($file); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($text); + + return $box; + } + + private function buildErrorCorpus($message) { + $text = id(new PHUIBoxView()) + ->addPadding(PHUI::PADDING_LARGE) + ->appendChild($message); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Details')); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($text); + + return $box; + } + + private function buildBeforeResponse($before) { + $request = $this->getRequest(); + $drequest = $this->getDiffusionRequest(); + + // NOTE: We need to get the grandparent so we can capture filename changes + // in the parent. + + $parent = $this->loadParentCommitOf($before); + $old_filename = null; + $was_created = false; + if ($parent) { + $grandparent = $this->loadParentCommitOf($parent); + + if ($grandparent) { + $rename_query = new DiffusionRenameHistoryQuery(); + $rename_query->setRequest($drequest); + $rename_query->setOldCommit($grandparent); + $rename_query->setViewer($request->getUser()); + $old_filename = $rename_query->loadOldFilename(); + $was_created = $rename_query->getWasCreated(); + } + } + + $follow = null; + if ($was_created) { + // If the file was created in history, that means older commits won't + // have it. Since we know it existed at 'before', it must have been + // created then; jump there. + $target_commit = $before; + $follow = 'created'; + } else if ($parent) { + // If we found a parent, jump to it. This is the normal case. + $target_commit = $parent; + } else { + // If there's no parent, this was probably created in the initial commit? + // And the "was_created" check will fail because we can't identify the + // grandparent. Keep the user at 'before'. + $target_commit = $before; + $follow = 'first'; + } + + $path = $drequest->getPath(); + $renamed = null; + if ($old_filename !== null && + $old_filename !== '/'.$path) { + $renamed = $path; + $path = $old_filename; + } + + $line = null; + // If there's a follow error, drop the line so the user sees the message. + if (!$follow) { + $line = $this->getBeforeLineNumber($target_commit); + } + + $before_uri = $drequest->generateURI( + array( + 'action' => 'browse', + 'commit' => $target_commit, + 'line' => $line, + 'path' => $path, + )); + + $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); + $before_uri = $before_uri->alter('before', null); + $before_uri = $before_uri->alter('renamed', $renamed); + $before_uri = $before_uri->alter('follow', $follow); + + return id(new AphrontRedirectResponse())->setURI($before_uri); + } + + private function getBeforeLineNumber($target_commit) { + $drequest = $this->getDiffusionRequest(); + + $line = $drequest->getLine(); + if (!$line) { + return null; + } + + $raw_diff = $this->callConduitWithDiffusionRequest( + 'diffusion.rawdiffquery', + array( + 'commit' => $drequest->getCommit(), + 'path' => $drequest->getPath(), + 'againstCommit' => $target_commit, + )); + $old_line = 0; + $new_line = 0; + + foreach (explode("\n", $raw_diff) as $text) { + if ($text[0] == '-' || $text[0] == ' ') { + $old_line++; + } + if ($text[0] == '+' || $text[0] == ' ') { + $new_line++; + } + if ($new_line == $line) { + return $old_line; + } + } + + // We didn't find the target line. + return $line; + } + + private function loadParentCommitOf($commit) { + $drequest = $this->getDiffusionRequest(); + $user = $this->getRequest()->getUser(); + + $before_req = DiffusionRequest::newFromDictionary( + array( + 'user' => $user, + 'repository' => $drequest->getRepository(), + 'commit' => $commit, + )); + + $parents = DiffusionQuery::callConduitWithDiffusionRequest( + $user, + $before_req, + 'diffusion.commitparentsquery', + array( + 'commit' => $commit, + )); + + return head($parents); + } + + private function renderRevisionTooltip( + DifferentialRevision $revision, + array $handles) { + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($revision->getDateModified(), $viewer); + $id = $revision->getID(); + $title = $revision->getTitle(); + $header = "D{$id} {$title}"; + + $author = $handles[$revision->getAuthorPHID()]->getName(); + + return "{$header}\n{$date} \xC2\xB7 {$author}"; + } + + private function renderCommitTooltip( + PhabricatorRepositoryCommit $commit, + array $handles, + $author) { + + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); + + if ($commit->getAuthorPHID()) { + $author = $handles[$commit->getAuthorPHID()]->getName(); + } + + return "{$summary}\n{$date} \xC2\xB7 {$author}"; + } + protected function renderSearchForm($collapsed) { $drequest = $this->getDiffusionRequest(); @@ -204,8 +1678,8 @@ abstract class DiffusionBrowseController extends DiffusionController { return $view; } - protected function buildOpenRevisions() { - $user = $this->getRequest()->getUser(); + private function buildOpenRevisions() { + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -220,7 +1694,7 @@ abstract class DiffusionBrowseController extends DiffusionController { $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPath($repository->getID(), $path_id) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->withUpdatedEpochBetween($recent, null) @@ -243,7 +1717,7 @@ abstract class DiffusionBrowseController extends DiffusionController { $view = id(new DifferentialRevisionListView()) ->setHeader($header) ->setRevisions($revisions) - ->setUser($user); + ->setUser($viewer); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); diff --git a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php b/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php deleted file mode 100644 index b465d454e0..0000000000 --- a/src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php +++ /dev/null @@ -1,106 +0,0 @@ -browseQueryResults = $results; - return $this; - } - - public function getBrowseQueryResults() { - return $this->browseQueryResults; - } - - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->diffusionRequest; - - $results = $this->getBrowseQueryResults(); - $reason = $results->getReasonForEmptyResultSet(); - - $content = array(); - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = true); - - if (!$results->isValidResults()) { - $empty_result = new DiffusionEmptyResultView(); - $empty_result->setDiffusionRequest($drequest); - $empty_result->setDiffusionBrowseResultSet($results); - $empty_result->setView($request->getStr('view')); - $content[] = $empty_result; - } else { - $phids = array(); - foreach ($results->getPaths() as $result) { - $data = $result->getLastCommitData(); - if ($data) { - if ($data->getCommitDetail('authorPHID')) { - $phids[$data->getCommitDetail('authorPHID')] = true; - } - } - } - - $phids = array_keys($phids); - $handles = $this->loadViewerHandles($phids); - - $browse_table = new DiffusionBrowseTableView(); - $browse_table->setDiffusionRequest($drequest); - $browse_table->setHandles($handles); - $browse_table->setPaths($results->getPaths()); - $browse_table->setUser($request->getUser()); - - $browse_panel = new PHUIObjectBoxView(); - $browse_panel->setHeaderText($drequest->getPath(), '/'); - $browse_panel->setTable($browse_table); - - $content[] = $browse_panel; - } - - $content[] = $this->buildOpenRevisions(); - - - $readme_path = $results->getReadmePath(); - if ($readme_path) { - $readme_content = $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - array( - 'path' => $readme_path, - 'commit' => $drequest->getStableCommit(), - )); - if ($readme_content) { - $content[] = id(new DiffusionReadmeView()) - ->setUser($this->getViewer()) - ->setPath($readme_path) - ->setContent($readme_content['corpus']); - } - } - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'browse', - )); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => array( - nonempty(basename($drequest->getPath()), '/'), - $drequest->getRepository()->getDisplayName(), - ), - )); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php deleted file mode 100644 index 91944112fc..0000000000 --- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php +++ /dev/null @@ -1,1134 +0,0 @@ -getDiffusionRequest(); - $viewer = $request->getUser(); - - $before = $request->getStr('before'); - if ($before) { - return $this->buildBeforeResponse($before); - } - - $path = $drequest->getPath(); - - $preferences = $viewer->loadPreferences(); - - $show_blame = $request->getBool( - 'blame', - $preferences->getPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, - false)); - $show_color = $request->getBool( - 'color', - $preferences->getPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, - true)); - - $view = $request->getStr('view'); - if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { - $preferences->setPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, - $show_blame); - $preferences->setPreference( - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, - $show_color); - $preferences->save(); - - $uri = $request->getRequestURI() - ->alter('blame', null) - ->alter('color', null); - - return id(new AphrontRedirectResponse())->setURI($uri); - } - - // We need the blame information if blame is on and we're building plain - // text, or blame is on and this is an Ajax request. If blame is on and - // this is a colorized request, we don't show blame at first (we ajax it - // in afterward) so we don't need to query for it. - $needs_blame = ($show_blame && !$show_color) || - ($show_blame && $request->isAjax()); - - $params = array( - 'commit' => $drequest->getCommit(), - 'path' => $drequest->getPath(), - 'needsBlame' => $needs_blame, - ); - - $byte_limit = null; - if ($view !== 'raw') { - $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); - $time_limit = 10; - - $params += array( - 'timeout' => $time_limit, - 'byteLimit' => $byte_limit, - ); - } - - $file_content = DiffusionFileContent::newFromConduit( - $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - $params)); - $data = $file_content->getCorpus(); - - if ($view === 'raw') { - return $this->buildRawResponse($path, $data); - } - - $this->loadLintMessages(); - $this->coverage = $drequest->loadCoverage(); - - if ($byte_limit && (strlen($data) == $byte_limit)) { - $corpus = $this->buildErrorCorpus( - pht( - 'This file is larger than %s byte(s), and too large to display '. - 'in the web UI.', - $byte_limit)); - } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { - $file = $this->loadFileForData($path, $data); - $file_uri = $file->getBestURI(); - - if ($file->isViewableImage()) { - $corpus = $this->buildImageCorpus($file_uri); - } else { - $corpus = $this->buildBinaryCorpus($file_uri, $data); - } - } else { - // Build the content of the file. - $corpus = $this->buildCorpus( - $show_blame, - $show_color, - $file_content, - $needs_blame, - $drequest, - $path, - $data); - } - - if ($request->isAjax()) { - return id(new AphrontAjaxResponse())->setContent($corpus); - } - - require_celerity_resource('diffusion-source-css'); - - // Render the page. - $view = $this->buildActionView($drequest); - $action_list = $this->enrichActionView( - $view, - $drequest, - $show_blame, - $show_color); - - $properties = $this->buildPropertyView($drequest, $action_list); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content = array(); - $content[] = $object_box; - - $follow = $request->getStr('follow'); - if ($follow) { - $notice = new PHUIInfoView(); - $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); - $notice->setTitle(pht('Unable to Continue')); - switch ($follow) { - case 'first': - $notice->appendChild( - pht( - 'Unable to continue tracing the history of this file because '. - 'this commit is the first commit in the repository.')); - break; - case 'created': - $notice->appendChild( - pht( - 'Unable to continue tracing the history of this file because '. - 'this commit created the file.')); - break; - } - $content[] = $notice; - } - - $renamed = $request->getStr('renamed'); - if ($renamed) { - $notice = new PHUIInfoView(); - $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - $notice->setTitle(pht('File Renamed')); - $notice->appendChild( - pht( - "File history passes through a rename from '%s' to '%s'.", - $drequest->getPath(), $renamed)); - $content[] = $notice; - } - - $content[] = $corpus; - $content[] = $this->buildOpenRevisions(); - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'browse', - )); - - $basename = basename($this->getDiffusionRequest()->getPath()); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => $basename, - )); - } - - private function loadLintMessages() { - $drequest = $this->getDiffusionRequest(); - $branch = $drequest->loadBranch(); - - if (!$branch || !$branch->getLintCommit()) { - return; - } - - $this->lintCommit = $branch->getLintCommit(); - - $conn = id(new PhabricatorRepository())->establishConnection('r'); - - $where = ''; - if ($drequest->getLint()) { - $where = qsprintf( - $conn, - 'AND code = %s', - $drequest->getLint()); - } - - $this->lintMessages = queryfx_all( - $conn, - 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', - PhabricatorRepository::TABLE_LINTMESSAGE, - $branch->getID(), - $where, - '/'.$drequest->getPath()); - } - - private function buildCorpus( - $show_blame, - $show_color, - DiffusionFileContent $file_content, - $needs_blame, - DiffusionRequest $drequest, - $path, - $data) { - - if (!$show_color) { - $style = - 'border: none; width: 100%; height: 80em; font-family: monospace'; - if (!$show_blame) { - $corpus = phutil_tag( - 'textarea', - array( - 'style' => $style, - ), - $file_content->getCorpus()); - } else { - $text_list = $file_content->getTextList(); - $rev_list = $file_content->getRevList(); - $blame_dict = $file_content->getBlameDict(); - - $rows = array(); - foreach ($text_list as $k => $line) { - $rev = $rev_list[$k]; - $author = $blame_dict[$rev]['author']; - $rows[] = - sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line); - } - - $corpus = phutil_tag( - 'textarea', - array( - 'style' => $style, - ), - implode("\n", $rows)); - } - } else { - require_celerity_resource('syntax-highlighting-css'); - $text_list = $file_content->getTextList(); - $rev_list = $file_content->getRevList(); - $blame_dict = $file_content->getBlameDict(); - - $text_list = implode("\n", $text_list); - $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( - $path, - $text_list); - $text_list = explode("\n", $text_list); - - $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, - $needs_blame, $drequest, $show_blame, $show_color); - - $corpus_table = javelin_tag( - 'table', - array( - 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', - 'sigil' => 'phabricator-source', - ), - $rows); - - if ($this->getRequest()->isAjax()) { - return $corpus_table; - } - - $id = celerity_generate_unique_node_id(); - - $repo = $drequest->getRepository(); - $symbol_repos = nonempty($repo->getSymbolSources(), array()); - $symbol_repos[] = $repo->getPHID(); - - $lang = last(explode('.', $drequest->getPath())); - $repo_languages = $repo->getSymbolLanguages(); - $repo_languages = nonempty($repo_languages, array()); - $repo_languages = array_fill_keys($repo_languages, true); - - $needs_symbols = true; - if ($repo_languages && $symbol_repos) { - $have_symbols = id(new DiffusionSymbolQuery()) - ->existsSymbolsInRepository($repo->getPHID()); - if (!$have_symbols) { - $needs_symbols = false; - } - } - - if ($needs_symbols && $repo_languages) { - $needs_symbols = isset($repo_languages[$lang]); - } - - if ($needs_symbols) { - Javelin::initBehavior( - 'repository-crossreference', - array( - 'container' => $id, - 'lang' => $lang, - 'repositories' => $symbol_repos, - )); - } - - $corpus = phutil_tag( - 'div', - array( - 'id' => $id, - ), - $corpus_table); - - Javelin::initBehavior('load-blame', array('id' => $id)); - } - - $edit = $this->renderEditButton(); - $file = $this->renderFileButton(); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('File Contents')) - ->addActionLink($edit) - ->addActionLink($file); - - $corpus = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->appendChild($corpus) - ->setCollapsed(true); - - return $corpus; - } - - private function enrichActionView( - PhabricatorActionListView $view, - DiffusionRequest $drequest, - $show_blame, - $show_color) { - - $viewer = $this->getRequest()->getUser(); - $base_uri = $this->getRequest()->getRequestURI(); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Show Last Change')) - ->setHref( - $drequest->generateURI( - array( - 'action' => 'change', - ))) - ->setIcon('fa-backward')); - - if ($show_blame) { - $blame_text = pht('Disable Blame'); - $blame_icon = 'fa-exclamation-circle lightgreytext'; - $blame_value = 0; - } else { - $blame_text = pht('Enable Blame'); - $blame_icon = 'fa-exclamation-circle'; - $blame_value = 1; - } - - $view->addAction( - id(new PhabricatorActionView()) - ->setName($blame_text) - ->setHref($base_uri->alter('blame', $blame_value)) - ->setIcon($blame_icon) - ->setUser($viewer) - ->setRenderAsForm($viewer->isLoggedIn())); - - if ($show_color) { - $highlight_text = pht('Disable Highlighting'); - $highlight_icon = 'fa-star-o grey'; - $highlight_value = 0; - } else { - $highlight_text = pht('Enable Highlighting'); - $highlight_icon = 'fa-star'; - $highlight_value = 1; - } - - $view->addAction( - id(new PhabricatorActionView()) - ->setName($highlight_text) - ->setHref($base_uri->alter('color', $highlight_value)) - ->setIcon($highlight_icon) - ->setUser($viewer) - ->setRenderAsForm($viewer->isLoggedIn())); - - $href = null; - if ($this->getRequest()->getStr('lint') !== null) { - $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); - $href = $base_uri->alter('lint', null); - - } else if ($this->lintCommit === null) { - $lint_text = pht('Lint not Available'); - } else { - $lint_text = pht( - 'Show %d Lint Message(s)', - count($this->lintMessages)); - $href = $this->getDiffusionRequest()->generateURI(array( - 'action' => 'browse', - 'commit' => $this->lintCommit, - ))->alter('lint', ''); - } - - $view->addAction( - id(new PhabricatorActionView()) - ->setName($lint_text) - ->setHref($href) - ->setIcon('fa-exclamation-triangle') - ->setDisabled(!$href)); - - return $view; - } - - private function renderEditButton() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $drequest = $this->getDiffusionRequest(); - - $repository = $drequest->getRepository(); - $path = $drequest->getPath(); - $line = nonempty((int)$drequest->getLine(), 1); - - $editor_link = $user->loadEditorLink($path, $line, $repository); - $template = $user->loadEditorLink($path, '%l', $repository); - - $icon_edit = id(new PHUIIconView()) - ->setIconFont('fa-pencil'); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Open in Editor')) - ->setHref($editor_link) - ->setIcon($icon_edit) - ->setID('editor_link') - ->setMetadata(array('link_template' => $template)) - ->setDisabled(!$editor_link); - - return $button; - } - - private function renderFileButton($file_uri = null) { - - $base_uri = $this->getRequest()->getRequestURI(); - - if ($file_uri) { - $text = pht('Download Raw File'); - $href = $file_uri; - $icon = 'fa-download'; - } else { - $text = pht('View Raw File'); - $href = $base_uri->alter('view', 'raw'); - $icon = 'fa-file-text'; - } - - $iconview = id(new PHUIIconView()) - ->setIconFont($icon); - $button = id(new PHUIButtonView()) - ->setTag('a') - ->setText($text) - ->setHref($href) - ->setIcon($iconview); - - return $button; - } - - - private function buildDisplayRows( - array $text_list, - array $rev_list, - array $blame_dict, - $needs_blame, - DiffusionRequest $drequest, - $show_blame, - $show_color) { - - $handles = array(); - if ($blame_dict) { - $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); - $epoch_min = min($epoch_list); - $epoch_max = max($epoch_list); - $epoch_range = ($epoch_max - $epoch_min) + 1; - - $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); - $handles = $this->loadViewerHandles($author_phids); - } - - $line_arr = array(); - $line_str = $drequest->getLine(); - $ranges = explode(',', $line_str); - foreach ($ranges as $range) { - if (strpos($range, '-') !== false) { - list($min, $max) = explode('-', $range, 2); - $line_arr[] = array( - 'min' => min($min, $max), - 'max' => max($min, $max), - ); - } else if (strlen($range)) { - $line_arr[] = array( - 'min' => $range, - 'max' => $range, - ); - } - } - - $display = array(); - - $line_number = 1; - $last_rev = null; - $color = null; - foreach ($text_list as $k => $line) { - $display_line = array( - 'epoch' => null, - 'commit' => null, - 'author' => null, - 'target' => null, - 'highlighted' => null, - 'line' => $line_number, - 'data' => $line, - ); - - if ($show_blame) { - // If the line's rev is same as the line above, show empty content - // with same color; otherwise generate blame info. The newer a change - // is, the more saturated the color. - - $rev = idx($rev_list, $k, $last_rev); - - if ($last_rev == $rev) { - $display_line['color'] = $color; - } else { - $blame = $blame_dict[$rev]; - - if (!isset($blame['epoch'])) { - $color = '#ffd'; // Render as warning. - } else { - $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; - $color_value = 0xE6 * (1.0 - $color_ratio); - $color = sprintf( - '#%02x%02x%02x', - $color_value, - 0xF6, - $color_value); - } - - $display_line['epoch'] = idx($blame, 'epoch'); - $display_line['color'] = $color; - $display_line['commit'] = $rev; - - $author_phid = idx($blame, 'authorPHID'); - if ($author_phid && $handles[$author_phid]) { - $author_link = $handles[$author_phid]->renderLink(); - } else { - $author_link = $blame['author']; - } - $display_line['author'] = $author_link; - - $last_rev = $rev; - } - } - - if ($line_arr) { - if ($line_number == $line_arr[0]['min']) { - $display_line['target'] = true; - } - foreach ($line_arr as $range) { - if ($line_number >= $range['min'] && - $line_number <= $range['max']) { - $display_line['highlighted'] = true; - } - } - } - - $display[] = $display_line; - ++$line_number; - } - - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $commits = array_filter(ipull($display, 'commit')); - if ($commits) { - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withRepository($drequest->getRepository()) - ->withIdentifiers($commits) - ->execute(); - $commits = mpull($commits, null, 'getCommitIdentifier'); - } - - $revision_ids = id(new DifferentialRevision()) - ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); - $revisions = array(); - if ($revision_ids) { - $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($viewer) - ->withIDs($revision_ids) - ->execute(); - } - - $phids = array(); - foreach ($commits as $commit) { - if ($commit->getAuthorPHID()) { - $phids[] = $commit->getAuthorPHID(); - } - } - foreach ($revisions as $revision) { - if ($revision->getAuthorPHID()) { - $phids[] = $revision->getAuthorPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - - Javelin::initBehavior('phabricator-oncopy', array()); - - $engine = null; - $inlines = array(); - if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { - $engine = new PhabricatorMarkupEngine(); - $engine->setViewer($viewer); - - foreach ($this->lintMessages as $message) { - $inline = id(new PhabricatorAuditInlineComment()) - ->setSyntheticAuthor( - ArcanistLintSeverity::getStringForSeverity($message['severity']). - ' '.$message['code'].' ('.$message['name'].')') - ->setLineNumber($message['line']) - ->setContent($message['description']); - $inlines[$message['line']][] = $inline; - - $engine->addObject( - $inline, - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); - } - - $engine->process(); - require_celerity_resource('differential-changeset-view-css'); - } - - $rows = $this->renderInlines( - idx($inlines, 0, array()), - $show_blame, - (bool)$this->coverage, - $engine); - - foreach ($display as $line) { - - $line_href = $drequest->generateURI( - array( - 'action' => 'browse', - 'line' => $line['line'], - 'stable' => true, - )); - - $blame = array(); - $style = null; - if (array_key_exists('color', $line)) { - if ($line['color']) { - $style = 'background: '.$line['color'].';'; - } - - $before_link = null; - $commit_link = null; - $revision_link = null; - if (idx($line, 'commit')) { - $commit = $line['commit']; - - if (idx($commits, $commit)) { - $tooltip = $this->renderCommitTooltip( - $commits[$commit], - $handles, - $line['author']); - } else { - $tooltip = null; - } - - Javelin::initBehavior('phabricator-tooltips', array()); - require_celerity_resource('aphront-tooltip-css'); - - $commit_link = javelin_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $line['commit'], - )), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(9) - ->setTerminator('') - ->truncateString($line['commit'])); - - $revision_id = null; - if (idx($commits, $commit)) { - $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); - } - - if ($revision_id) { - $revision = idx($revisions, $revision_id); - if ($revision) { - $tooltip = $this->renderRevisionTooltip($revision, $handles); - $revision_link = javelin_tag( - 'a', - array( - 'href' => '/D'.$revision->getID(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - 'D'.$revision->getID()); - } - } - - $uri = $line_href->alter('before', $commit); - $before_link = javelin_tag( - 'a', - array( - 'href' => $uri->setQueryParam('view', 'blame'), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => pht('Skip Past This Commit'), - 'align' => 'E', - 'size' => 300, - ), - ), - "\xC2\xAB"); - } - - $blame[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-link', - ), - $before_link); - - $object_links = array(); - $object_links[] = $commit_link; - if ($revision_link) { - $object_links[] = phutil_tag('span', array(), '/'); - $object_links[] = $revision_link; - } - - $blame[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-rev-link', - ), - $object_links); - } - - $line_link = phutil_tag( - 'a', - array( - 'href' => $line_href, - 'style' => $style, - ), - $line['line']); - - $blame[] = javelin_tag( - 'th', - array( - 'class' => 'diffusion-line-link', - 'sigil' => 'phabricator-source-line', - 'style' => $style, - ), - $line_link); - - Javelin::initBehavior('phabricator-line-linker'); - - if ($line['target']) { - Javelin::initBehavior( - 'diffusion-jump-to', - array( - 'target' => 'scroll_target', - )); - $anchor_text = phutil_tag( - 'a', - array( - 'id' => 'scroll_target', - ), - ''); - } else { - $anchor_text = null; - } - - $blame[] = phutil_tag( - 'td', - array( - ), - array( - $anchor_text, - - // NOTE: See phabricator-oncopy behavior. - "\xE2\x80\x8B", - - // TODO: [HTML] Not ideal. - phutil_safe_html(str_replace("\t", ' ', $line['data'])), - )); - - if ($this->coverage) { - require_celerity_resource('differential-changeset-view-css'); - $cov_index = $line['line'] - 1; - - if (isset($this->coverage[$cov_index])) { - $cov_class = $this->coverage[$cov_index]; - } else { - $cov_class = 'N'; - } - - $blame[] = phutil_tag( - 'td', - array( - 'class' => 'cov cov-'.$cov_class, - ), - ''); - } - - $rows[] = phutil_tag( - 'tr', - array( - 'class' => ($line['highlighted'] ? - 'phabricator-source-highlight' : - null), - ), - $blame); - - $cur_inlines = $this->renderInlines( - idx($inlines, $line['line'], array()), - $show_blame, - $this->coverage, - $engine); - foreach ($cur_inlines as $cur_inline) { - $rows[] = $cur_inline; - } - } - - return $rows; - } - - private function renderInlines( - array $inlines, - $needs_blame, - $has_coverage, - $engine) { - - $rows = array(); - foreach ($inlines as $inline) { - - // TODO: This should use modern scaffolding code. - - $inline_view = id(new PHUIDiffInlineCommentDetailView()) - ->setUser($this->getViewer()) - ->setMarkupEngine($engine) - ->setInlineComment($inline) - ->render(); - - $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); - - $row[] = phutil_tag('td', array(), $inline_view); - - if ($has_coverage) { - $row[] = phutil_tag( - 'td', - array( - 'class' => 'cov cov-I', - )); - } - - $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); - } - - return $rows; - } - - private function loadFileForData($path, $data) { - $file = PhabricatorFile::buildFromFileDataOrHash( - $data, - array( - 'name' => basename($path), - 'ttl' => time() + 60 * 60 * 24, - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - )); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $file->attachToObject( - $this->getDiffusionRequest()->getRepository()->getPHID()); - unset($unguarded); - - return $file; - } - - private function buildRawResponse($path, $data) { - $file = $this->loadFileForData($path, $data); - return $file->getRedirectResponse(); - } - - private function buildImageCorpus($file_uri) { - $properties = new PHUIPropertyListView(); - - $properties->addImageContent( - phutil_tag( - 'img', - array( - 'src' => $file_uri, - ))); - - $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Image')) - ->addActionLink($file); - - return id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - } - - private function buildBinaryCorpus($file_uri, $data) { - - $size = new PhutilNumber(strlen($data)); - $text = pht('This is a binary file. It is %s byte(s) in length.', $size); - $text = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_LARGE) - ->appendChild($text); - - $file = $this->renderFileButton($file_uri); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')) - ->addActionLink($file); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->appendChild($text); - - return $box; - } - - private function buildErrorCorpus($message) { - $text = id(new PHUIBoxView()) - ->addPadding(PHUI::PADDING_LARGE) - ->appendChild($message); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Details')); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->appendChild($text); - - return $box; - } - - private function buildBeforeResponse($before) { - $request = $this->getRequest(); - $drequest = $this->getDiffusionRequest(); - - // NOTE: We need to get the grandparent so we can capture filename changes - // in the parent. - - $parent = $this->loadParentCommitOf($before); - $old_filename = null; - $was_created = false; - if ($parent) { - $grandparent = $this->loadParentCommitOf($parent); - - if ($grandparent) { - $rename_query = new DiffusionRenameHistoryQuery(); - $rename_query->setRequest($drequest); - $rename_query->setOldCommit($grandparent); - $rename_query->setViewer($request->getUser()); - $old_filename = $rename_query->loadOldFilename(); - $was_created = $rename_query->getWasCreated(); - } - } - - $follow = null; - if ($was_created) { - // If the file was created in history, that means older commits won't - // have it. Since we know it existed at 'before', it must have been - // created then; jump there. - $target_commit = $before; - $follow = 'created'; - } else if ($parent) { - // If we found a parent, jump to it. This is the normal case. - $target_commit = $parent; - } else { - // If there's no parent, this was probably created in the initial commit? - // And the "was_created" check will fail because we can't identify the - // grandparent. Keep the user at 'before'. - $target_commit = $before; - $follow = 'first'; - } - - $path = $drequest->getPath(); - $renamed = null; - if ($old_filename !== null && - $old_filename !== '/'.$path) { - $renamed = $path; - $path = $old_filename; - } - - $line = null; - // If there's a follow error, drop the line so the user sees the message. - if (!$follow) { - $line = $this->getBeforeLineNumber($target_commit); - } - - $before_uri = $drequest->generateURI( - array( - 'action' => 'browse', - 'commit' => $target_commit, - 'line' => $line, - 'path' => $path, - )); - - $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); - $before_uri = $before_uri->alter('before', null); - $before_uri = $before_uri->alter('renamed', $renamed); - $before_uri = $before_uri->alter('follow', $follow); - - return id(new AphrontRedirectResponse())->setURI($before_uri); - } - - private function getBeforeLineNumber($target_commit) { - $drequest = $this->getDiffusionRequest(); - - $line = $drequest->getLine(); - if (!$line) { - return null; - } - - $raw_diff = $this->callConduitWithDiffusionRequest( - 'diffusion.rawdiffquery', - array( - 'commit' => $drequest->getCommit(), - 'path' => $drequest->getPath(), - 'againstCommit' => $target_commit, - )); - $old_line = 0; - $new_line = 0; - - foreach (explode("\n", $raw_diff) as $text) { - if ($text[0] == '-' || $text[0] == ' ') { - $old_line++; - } - if ($text[0] == '+' || $text[0] == ' ') { - $new_line++; - } - if ($new_line == $line) { - return $old_line; - } - } - - // We didn't find the target line. - return $line; - } - - private function loadParentCommitOf($commit) { - $drequest = $this->getDiffusionRequest(); - $user = $this->getRequest()->getUser(); - - $before_req = DiffusionRequest::newFromDictionary( - array( - 'user' => $user, - 'repository' => $drequest->getRepository(), - 'commit' => $commit, - )); - - $parents = DiffusionQuery::callConduitWithDiffusionRequest( - $user, - $before_req, - 'diffusion.commitparentsquery', - array( - 'commit' => $commit, - )); - - return head($parents); - } - - private function renderRevisionTooltip( - DifferentialRevision $revision, - array $handles) { - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($revision->getDateModified(), $viewer); - $id = $revision->getID(); - $title = $revision->getTitle(); - $header = "D{$id} {$title}"; - - $author = $handles[$revision->getAuthorPHID()]->getName(); - - return "{$header}\n{$date} \xC2\xB7 {$author}"; - } - - private function renderCommitTooltip( - PhabricatorRepositoryCommit $commit, - array $handles, - $author) { - - $viewer = $this->getRequest()->getUser(); - - $date = phabricator_date($commit->getEpoch(), $viewer); - $summary = trim($commit->getSummary()); - - if ($commit->getAuthorPHID()) { - $author = $handles[$commit->getAuthorPHID()]->getName(); - } - - return "{$summary}\n{$date} \xC2\xB7 {$author}"; - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseMainController.php b/src/applications/diffusion/controller/DiffusionBrowseMainController.php deleted file mode 100644 index 8c0318a870..0000000000 --- a/src/applications/diffusion/controller/DiffusionBrowseMainController.php +++ /dev/null @@ -1,39 +0,0 @@ -diffusionRequest; - - // Figure out if we're browsing a directory, a file, or a search result - // list. Then delegate to the appropriate controller. - - $grep = $request->getStr('grep'); - $find = $request->getStr('find'); - if (strlen($grep) || strlen($find)) { - $controller = new DiffusionBrowseSearchController(); - } else { - $results = DiffusionBrowseResultSet::newFromConduit( - $this->callConduitWithDiffusionRequest( - 'diffusion.browsequery', - array( - 'path' => $drequest->getPath(), - 'commit' => $drequest->getStableCommit(), - ))); - $reason = $results->getReasonForEmptyResultSet(); - $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); - - if ($is_file) { - $controller = new DiffusionBrowseFileController($request); - } else { - $controller = new DiffusionBrowseDirectoryController($request); - $controller->setBrowseQueryResults($results); - } - } - - $controller->setDiffusionRequest($drequest); - $controller->setCurrentApplication($this->getCurrentApplication()); - return $this->delegateToController($controller); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionBrowseSearchController.php b/src/applications/diffusion/controller/DiffusionBrowseSearchController.php deleted file mode 100644 index e27468ab77..0000000000 --- a/src/applications/diffusion/controller/DiffusionBrowseSearchController.php +++ /dev/null @@ -1,231 +0,0 @@ -diffusionRequest; - - $actions = $this->buildActionView($drequest); - $properties = $this->buildPropertyView($drequest, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($this->buildHeaderView($drequest)) - ->addPropertyList($properties); - - $content = array(); - - $content[] = $object_box; - $content[] = $this->renderSearchForm($collapsed = false); - $content[] = $this->renderSearchResults(); - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'browse', - )); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => array( - nonempty(basename($drequest->getPath()), '/'), - $drequest->getRepository()->getDisplayName(), - ), - )); - } - - private function renderSearchResults() { - $drequest = $this->getDiffusionRequest(); - $repository = $drequest->getRepository(); - $results = array(); - - $limit = 100; - $page = $this->getRequest()->getInt('page', 0); - $pager = new PHUIPagerView(); - $pager->setPageSize($limit); - $pager->setOffset($page); - $pager->setURI($this->getRequest()->getRequestURI(), 'page'); - - $search_mode = null; - - switch ($repository->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $results = array(); - break; - default: - if (strlen($this->getRequest()->getStr('grep'))) { - $search_mode = 'grep'; - $query_string = $this->getRequest()->getStr('grep'); - $results = $this->callConduitWithDiffusionRequest( - 'diffusion.searchquery', - array( - 'grep' => $query_string, - 'commit' => $drequest->getStableCommit(), - 'path' => $drequest->getPath(), - 'limit' => $limit + 1, - 'offset' => $page, - )); - } else { // Filename search. - $search_mode = 'find'; - $query_string = $this->getRequest()->getStr('find'); - $results = $this->callConduitWithDiffusionRequest( - 'diffusion.querypaths', - array( - 'pattern' => $query_string, - 'commit' => $drequest->getStableCommit(), - 'path' => $drequest->getPath(), - 'limit' => $limit + 1, - 'offset' => $page, - )); - } - break; - } - - $results = $pager->sliceResults($results); - - if ($search_mode == 'grep') { - $table = $this->renderGrepResults($results, $query_string); - $header = pht( - 'File content matching "%s" under "%s"', - $query_string, - nonempty($drequest->getPath(), '/')); - } else { - $table = $this->renderFindResults($results); - $header = pht( - 'Paths matching "%s" under "%s"', - $query_string, - nonempty($drequest->getPath(), '/')); - } - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) - ->setTable($table); - - $pager_box = id(new PHUIBoxView()) - ->addMargin(PHUI::MARGIN_LARGE) - ->appendChild($pager); - - return array($box, $pager_box); - } - - private function renderGrepResults(array $results, $pattern) { - $drequest = $this->getDiffusionRequest(); - - require_celerity_resource('phabricator-search-results-css'); - - $rows = array(); - foreach ($results as $result) { - list($path, $line, $string) = $result; - - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $path, - 'line' => $line, - )); - - $matches = null; - $count = @preg_match_all( - '('.$pattern.')u', - $string, - $matches, - PREG_OFFSET_CAPTURE); - - if (!$count) { - $output = ltrim($string); - } else { - $output = array(); - $cursor = 0; - $length = strlen($string); - foreach ($matches[0] as $match) { - $offset = $match[1]; - if ($cursor != $offset) { - $output[] = array( - 'text' => substr($string, $cursor, $offset), - 'highlight' => false, - ); - } - $output[] = array( - 'text' => $match[0], - 'highlight' => true, - ); - $cursor = $offset + strlen($match[0]); - } - if ($cursor != $length) { - $output[] = array( - 'text' => substr($string, $cursor), - 'highlight' => false, - ); - } - - if ($output) { - $output[0]['text'] = ltrim($output[0]['text']); - } - - foreach ($output as $key => $segment) { - if ($segment['highlight']) { - $output[$key] = phutil_tag('strong', array(), $segment['text']); - } else { - $output[$key] = $segment['text']; - } - } - } - - $string = phutil_tag( - 'pre', - array('class' => 'PhabricatorMonospaced phui-source-fragment'), - $output); - - $path = Filesystem::readablePath($path, $drequest->getPath()); - - $rows[] = array( - phutil_tag('a', array('href' => $href), $path), - $line, - $string, - ); - } - - $table = id(new AphrontTableView($rows)) - ->setClassName('remarkup-code') - ->setHeaders(array(pht('Path'), pht('Line'), pht('String'))) - ->setColumnClasses(array('', 'n', 'wide')) - ->setNoDataString( - pht( - 'The pattern you searched for was not found in the content of any '. - 'files.')); - - return $table; - } - - private function renderFindResults(array $results) { - $drequest = $this->getDiffusionRequest(); - - $rows = array(); - foreach ($results as $result) { - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $result, - )); - - $readable = Filesystem::readablePath($result, $drequest->getPath()); - - $rows[] = array( - phutil_tag('a', array('href' => $href), $readable), - ); - } - - $table = id(new AphrontTableView($rows)) - ->setHeaders(array(pht('Path'))) - ->setColumnClasses(array('wide')) - ->setNoDataString( - pht( - 'The pattern you searched for did not match the names of any '. - 'files.')); - - return $table; - } - -} From f1c298203a8401768a7643735147fe5429759a01 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 05:56:35 -0800 Subject: [PATCH 50/83] Return no results from `grep` repository queries on error Summary: Fixes T7852. Although `1` could also indicate other kinds of problems, assume it means "no results". Test Plan: Searched for nonsense strings in Git and Mercurial. Searched for valid strings in Git and Mercurial. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7852 Differential Revision: https://secure.phabricator.com/D14943 --- .../DiffusionQueryPathsConduitAPIMethod.php | 16 ++++++------- .../DiffusionSearchQueryConduitAPIMethod.php | 18 +++++++------- .../controller/DiffusionBrowseController.php | 24 ++++++++----------- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php index 6061825444..5ca2783838 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php @@ -44,7 +44,6 @@ final class DiffusionQueryPathsConduitAPIMethod $commit, $path); - $lines = id(new LinesOfALargeExecFuture($future))->setDelimiter("\0"); return $this->filterResults($lines, $request); } @@ -86,16 +85,15 @@ final class DiffusionQueryPathsConduitAPIMethod $results = array(); $count = 0; foreach ($lines as $line) { - if (!$pattern || preg_match($pattern, $line)) { - if ($count >= $offset) { - $results[] = $line; - } + if (strlen($pattern) && !preg_match($pattern, $line)) { + continue; + } - $count++; + $results[] = $line; + $count++; - if ($limit && ($count >= ($offset + $limit))) { - break; - } + if ($limit && ($count >= ($offset + $limit))) { + break; } } diff --git a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php index 16b9762e1d..e9b8f8d15e 100644 --- a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php @@ -25,18 +25,19 @@ final class DiffusionSearchQueryConduitAPIMethod ); } - protected function defineCustomErrorTypes() { - return array( - 'ERR-GREP-COMMAND' => pht('Grep command failed.'), - ); - } - protected function getResult(ConduitAPIRequest $request) { try { $results = parent::getResult($request); } catch (CommandException $ex) { - throw id(new ConduitException('ERR-GREP-COMMAND')) - ->setErrorDescription($ex->getStderr()); + $err = $ex->getError(); + + if ($err === 1) { + // `git grep` and `hg grep` exit with 1 if there are no matches; + // assume we just didn't get any hits. + return array(); + } + + throw $ex; } $offset = $request->getValue('offset'); @@ -63,6 +64,7 @@ final class DiffusionSearchQueryConduitAPIMethod $binary_pattern = '/Binary file [^:]*:(.+) matches/'; $lines = new LinesOfALargeExecFuture($future); + foreach ($lines as $line) { $result = null; if (preg_match('/[^:]*:(.+)\0(.+)\0(.*)/', $line, $result)) { diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 4a04078d26..d512ad035f 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -351,19 +351,16 @@ final class DiffusionBrowseController extends DiffusionController { } private function renderSearchResults() { + $request = $this->getRequest(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $results = array(); - $limit = 100; - $page = $this->getRequest()->getInt('page', 0); - $pager = new PHUIPagerView(); - $pager->setPageSize($limit); - $pager->setOffset($page); - $pager->setURI($this->getRequest()->getRequestURI(), 'page'); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); $search_mode = null; - switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $results = array(); @@ -371,32 +368,31 @@ final class DiffusionBrowseController extends DiffusionController { default: if (strlen($this->getRequest()->getStr('grep'))) { $search_mode = 'grep'; - $query_string = $this->getRequest()->getStr('grep'); + $query_string = $request->getStr('grep'); $results = $this->callConduitWithDiffusionRequest( 'diffusion.searchquery', array( 'grep' => $query_string, 'commit' => $drequest->getStableCommit(), 'path' => $drequest->getPath(), - 'limit' => $limit + 1, - 'offset' => $page, + 'limit' => $pager->getPageSize() + 1, + 'offset' => $pager->getOffset(), )); } else { // Filename search. $search_mode = 'find'; - $query_string = $this->getRequest()->getStr('find'); + $query_string = $request->getStr('find'); $results = $this->callConduitWithDiffusionRequest( 'diffusion.querypaths', array( 'pattern' => $query_string, 'commit' => $drequest->getStableCommit(), 'path' => $drequest->getPath(), - 'limit' => $limit + 1, - 'offset' => $page, + 'limit' => $pager->getPageSize() + 1, + 'offset' => $pager->getOffset(), )); } break; } - $results = $pager->sliceResults($results); if ($search_mode == 'grep') { From 2bfc5ff92ec25329937522d15a260bade3fcdc8b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 08:13:05 -0800 Subject: [PATCH 51/83] Modernize more Diffusion controllers Summary: Ref T4245. Standardize how context is read, minor updates / modernizations / consistency tweaks. Test Plan: - Viewed a change. - Viewed brnaches. - Edited a commit. - Viewed tags. - Viewed history. - Added, edited and deleted a mirror. - Viewed push events. - Viewed a particular event. - Viewed ref disambiguation. - Viewed repository list. - Ran automation test. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14944 --- .../controller/DiffusionChangeController.php | 32 ++++++---- .../DiffusionCommitBranchesController.php | 7 ++- .../DiffusionCommitEditController.php | 59 ++++++++++--------- .../DiffusionCommitTagsController.php | 7 ++- .../controller/DiffusionHistoryController.php | 54 ++++++++--------- .../DiffusionMirrorDeleteController.php | 11 +++- .../DiffusionMirrorEditController.php | 11 +++- .../DiffusionPushEventViewController.php | 23 ++++---- .../DiffusionPushLogListController.php | 27 ++------- .../DiffusionRefTableController.php | 27 +++++---- .../DiffusionRepositoryListController.php | 26 ++------ ...sionRepositoryTestAutomationController.php | 9 ++- 12 files changed, 144 insertions(+), 149 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionChangeController.php b/src/applications/diffusion/controller/DiffusionChangeController.php index 064e5cc750..86b371c38b 100644 --- a/src/applications/diffusion/controller/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/DiffusionChangeController.php @@ -6,9 +6,14 @@ final class DiffusionChangeController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->diffusionRequest; - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $content = array(); @@ -89,15 +94,18 @@ final class DiffusionChangeController extends DiffusionController { ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $content, - ), - array( - 'title' => pht('Change'), - )); + return $this->newPage() + ->setTitle( + array( + basename($drequest->getPath()), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $content, + )); } private function buildActionView(DiffusionRequest $drequest) { diff --git a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php index 4f12e17656..1e5cc0ac31 100644 --- a/src/applications/diffusion/controller/DiffusionCommitBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionCommitBranchesController.php @@ -6,7 +6,12 @@ final class DiffusionCommitBranchesController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); diff --git a/src/applications/diffusion/controller/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php index ffaf892e75..895eda4c6d 100644 --- a/src/applications/diffusion/controller/DiffusionCommitEditController.php +++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php @@ -2,18 +2,24 @@ final class DiffusionCommitEditController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->getDiffusionRequest(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $commit = $drequest->loadCommit(); - $data = $commit->loadCommitData(); - $page_title = pht('Edit Diffusion Commit'); + $commit = $drequest->loadCommit(); if (!$commit) { return new Aphront404Response(); } + $data = $commit->loadCommitData(); + $page_title = pht('Edit Diffusion Commit'); + $commit_phid = $commit->getPHID(); $edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $current_proj_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( @@ -27,18 +33,21 @@ final class DiffusionCommitEditController extends DiffusionController { ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $edge_type) ->setNewValue(array('=' => array_fuse($proj_phids))); + $editor = id(new PhabricatorAuditEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); - $xactions = $editor->applyTransactions($commit, $xactions); + + $editor->applyTransactions($commit, $xactions); + return id(new AphrontRedirectResponse()) ->setURI($commit->getURI()); } $tokenizer_id = celerity_generate_unique_node_id(); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setAction($request->getRequestURI()->getPath()) ->appendControl( id(new AphrontFormTokenizerControl()) @@ -87,33 +96,25 @@ final class DiffusionCommitEditController extends DiffusionController { ->setValue(array($desc, " \xC2\xB7 ", $doc_link))); } + $form->appendControl( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save')) + ->addCancelButton($commit->getURI())); - Javelin::initBehavior('project-create', array( - 'tokenizerID' => $tokenizer_id, - )); - - $submit = id(new AphrontFormSubmitControl()) - ->setValue(pht('Save')) - ->addCancelButton($commit->getURI()); - $form->appendChild($submit); - - $crumbs = $this->buildCrumbs(array( - 'commit' => true, - )); + $crumbs = $this->buildCrumbs( + array( + 'commit' => true, + )); $crumbs->addTextCrumb(pht('Edit')); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $page_title, - )); + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionCommitTagsController.php b/src/applications/diffusion/controller/DiffusionCommitTagsController.php index 3c698f9a70..0f825a9b64 100644 --- a/src/applications/diffusion/controller/DiffusionCommitTagsController.php +++ b/src/applications/diffusion/controller/DiffusionCommitTagsController.php @@ -6,7 +6,12 @@ final class DiffusionCommitTagsController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index 6cca484207..edbce636c2 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -6,19 +6,24 @@ final class DiffusionHistoryController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->diffusionRequest; - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $page_size = $request->getInt('pagesize', 100); - $offset = $request->getInt('offset', 0); + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); $params = array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), - 'offset' => $offset, - 'limit' => $page_size + 1, + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, ); if (!$request->getBool('copies')) { @@ -32,13 +37,8 @@ final class DiffusionHistoryController extends DiffusionController { $history = DiffusionPathChange::newFromConduit( $history_results['pathChanges']); - $pager = new PHUIPagerView(); - $pager->setPageSize($page_size); - $pager->setOffset($offset); $history = $pager->sliceResults($history); - $pager->setURI($request->getRequestURI(), 'offset'); - $show_graph = !strlen($drequest->getPath()); $content = array(); @@ -51,7 +51,7 @@ final class DiffusionHistoryController extends DiffusionController { if ($show_graph) { $history_table->setParents($history_results['parents']); - $history_table->setIsHead($offset == 0); + $history_table->setIsHead(!$pager->getOffset()); } $history_panel = new PHUIObjectBoxView(); @@ -79,23 +79,21 @@ final class DiffusionHistoryController extends DiffusionController { 'view' => 'history', )); - $pager = id(new PHUIBoxView()) - ->addClass('ml') - ->appendChild($pager); + $pager_box = $this->renderTablePagerBox($pager); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $content, - $pager, - ), - array( - 'title' => array( + return $this->newPage() + ->setTitle( + array( pht('History'), - $drequest->getRepository()->getDisplayName(), - ), - )); + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $content, + $pager_box, + )); } private function buildActionView(DiffusionRequest $drequest) { diff --git a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php b/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php index 3a4300690e..c49cdb7517 100644 --- a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php +++ b/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php @@ -3,9 +3,14 @@ final class DiffusionMirrorDeleteController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $mirror = id(new PhabricatorRepositoryMirrorQuery()) diff --git a/src/applications/diffusion/controller/DiffusionMirrorEditController.php b/src/applications/diffusion/controller/DiffusionMirrorEditController.php index 4d5e9f139f..f5793f2afe 100644 --- a/src/applications/diffusion/controller/DiffusionMirrorEditController.php +++ b/src/applications/diffusion/controller/DiffusionMirrorEditController.php @@ -3,9 +3,14 @@ final class DiffusionMirrorEditController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); PhabricatorPolicyFilter::requireCapability( diff --git a/src/applications/diffusion/controller/DiffusionPushEventViewController.php b/src/applications/diffusion/controller/DiffusionPushEventViewController.php index 3c5861e512..027cf16bbe 100644 --- a/src/applications/diffusion/controller/DiffusionPushEventViewController.php +++ b/src/applications/diffusion/controller/DiffusionPushEventViewController.php @@ -7,8 +7,8 @@ final class DiffusionPushEventViewController return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $event = id(new PhabricatorRepositoryPushEventQuery()) ->setViewer($viewer) @@ -57,16 +57,15 @@ final class DiffusionPushEventViewController ->setHeaderText(pht('All Pushed Updates')) ->setTable($updates_table); - return $this->buildApplicationPage( - array( - $crumbs, - $detail_box, - $commits_box, - $update_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $detail_box, + $commits_box, + $update_box, + )); } private function buildPropertyList(PhabricatorRepositoryPushEvent $event) { diff --git a/src/applications/diffusion/controller/DiffusionPushLogListController.php b/src/applications/diffusion/controller/DiffusionPushLogListController.php index 4f2460be89..5b58881470 100644 --- a/src/applications/diffusion/controller/DiffusionPushLogListController.php +++ b/src/applications/diffusion/controller/DiffusionPushLogListController.php @@ -6,29 +6,10 @@ final class DiffusionPushLogListController extends DiffusionPushLogController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new PhabricatorRepositoryPushLogSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $viewer = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhabricatorRepositoryPushLogSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + public function handleRequest(AphrontRequest $request) { + return id(new PhabricatorRepositoryPushLogSearchEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/diffusion/controller/DiffusionRefTableController.php b/src/applications/diffusion/controller/DiffusionRefTableController.php index dac0c3a136..8a4e274819 100644 --- a/src/applications/diffusion/controller/DiffusionRefTableController.php +++ b/src/applications/diffusion/controller/DiffusionRefTableController.php @@ -6,9 +6,13 @@ final class DiffusionRefTableController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -132,18 +136,15 @@ final class DiffusionRefTableController extends DiffusionController { $crumbs = $this->buildCrumbs(array()); $crumbs->addTextCrumb(pht('Refs')); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => array( - pht('Refs'), - $repository->getMonogram(), + return $this->newPage() + ->setTitle( + array( $ref_name, - ), - )); + pht('Ref'), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryListController.php b/src/applications/diffusion/controller/DiffusionRepositoryListController.php index ec93edbc41..5897d2f162 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryListController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryListController.php @@ -6,28 +6,10 @@ final class DiffusionRepositoryListController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new PhabricatorRepositorySearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $viewer = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhabricatorRepositorySearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + public function handleRequest(AphrontRequest $request) { + return id(new PhabricatorRepositorySearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php index 6a9039889e..691564c68e 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php @@ -3,9 +3,14 @@ final class DiffusionRepositoryTestAutomationController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); - $drequest = $this->diffusionRequest; + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $repository = id(new PhabricatorRepositoryQuery()) From 649f8827205aeaf6e4f8a88c41902f759f158f28 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 08:49:10 -0800 Subject: [PATCH 52/83] Slightly modernize all Diffusion edit endpoints Summary: Ref T4245. Prepares edit endpoints for more flexible repository identifiers. Test Plan: - Added, edited, deleted mirror. - Created repository. - Edited basic repository information. - Edited policies for a repository. - Activated/deactivated repository. - Updated a repository. - Hit "Delete" dialog for a repository. - Edited hosting. - Toggled dangerous changes. - Edited branches. - Edited automation. - Tested automation. - Edited storage. - Edited staging. - Edited encoding. - Edited symbols. - Edited branches. - Edited actions. - Tried to do edits as an unprivileged user. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14945 --- .../controller/DiffusionController.php | 13 ++++- .../DiffusionMirrorDeleteController.php | 6 +-- .../DiffusionMirrorEditController.php | 6 +-- .../DiffusionRepositoryCreateController.php | 19 +++---- .../DiffusionRepositoryDefaultController.php | 12 ++++- ...ffusionRepositoryEditActionsController.php | 37 +++++--------- ...fusionRepositoryEditActivateController.php | 36 ++++--------- ...sionRepositoryEditAutomationController.php | 36 +++++-------- ...DiffusionRepositoryEditBasicController.php | 49 +++++++----------- ...fusionRepositoryEditBranchesController.php | 37 +++++--------- ...usionRepositoryEditDangerousController.php | 35 ++++--------- ...iffusionRepositoryEditDeleteController.php | 30 ++++------- ...fusionRepositoryEditEncodingController.php | 37 +++++--------- ...ffusionRepositoryEditHostingController.php | 50 +++++++------------ .../DiffusionRepositoryEditMainController.php | 16 +++--- ...ffusionRepositoryEditStagingController.php | 39 ++++++--------- ...ffusionRepositoryEditStorageController.php | 41 ++++++--------- ...sionRepositoryEditSubversionController.php | 37 +++++--------- ...iffusionRepositoryEditUpdateController.php | 24 +++------ .../DiffusionRepositorySymbolsController.php | 41 ++++++--------- ...sionRepositoryTestAutomationController.php | 15 +----- .../diffusion/request/DiffusionRequest.php | 23 +++++++-- 22 files changed, 236 insertions(+), 403 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index dc916c7e9e..63f3897b7f 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -52,7 +52,18 @@ abstract class DiffusionController extends PhabricatorController { return $this->processDiffusionRequest($request); } + protected function loadDiffusionContextForEdit() { + return $this->loadContext( + array( + 'edit' => true, + )); + } + protected function loadDiffusionContext() { + return $this->loadContext(array()); + } + + private function loadContext(array $options) { $request = $this->getRequest(); $viewer = $this->getViewer(); @@ -61,7 +72,7 @@ abstract class DiffusionController extends PhabricatorController { $identifier = (int)$request->getURIData('repositoryID'); } - $params = array( + $params = $options + array( 'repository' => $identifier, 'user' => $viewer, 'blob' => $request->getURIData('dblob'), diff --git a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php b/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php index c49cdb7517..a049146cb5 100644 --- a/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php +++ b/src/applications/diffusion/controller/DiffusionMirrorDeleteController.php @@ -33,16 +33,12 @@ final class DiffusionMirrorDeleteController return id(new AphrontReloadResponse())->setURI($edit_uri); } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle(pht('Really delete mirror?')) ->appendChild( pht('Phabricator will stop pushing updates to this mirror.')) ->addSubmitButton(pht('Delete Mirror')) ->addCancelButton($edit_uri); - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } diff --git a/src/applications/diffusion/controller/DiffusionMirrorEditController.php b/src/applications/diffusion/controller/DiffusionMirrorEditController.php index f5793f2afe..d438a9e6d2 100644 --- a/src/applications/diffusion/controller/DiffusionMirrorEditController.php +++ b/src/applications/diffusion/controller/DiffusionMirrorEditController.php @@ -117,17 +117,13 @@ final class DiffusionMirrorEditController ->setError($e_credentials) ->setOptions($credentials)); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + return $this->newDialog() ->setTitle($title) ->setWidth(AphrontDialogView::WIDTH_FORM) ->appendChild($form_errors) ->appendChild($form) ->addSubmitButton($submit) ->addCancelButton($edit_uri); - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php index 79c617f220..c6e2796fc1 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php @@ -6,7 +6,7 @@ final class DiffusionRepositoryCreateController private $edit; private $repository; - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { $viewer = $request->getUser(); $this->edit = $request->getURIData('edit'); @@ -19,6 +19,11 @@ final class DiffusionRepositoryCreateController switch ($this->edit) { case 'remote': case 'policy': + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + $repository = $this->getDiffusionRequest()->getRepository(); // Make sure we have CAN_EDIT. @@ -275,14 +280,10 @@ final class DiffusionRepositoryCreateController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $form, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php b/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php index 9b72c66eea..efebd12c6f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php @@ -2,7 +2,12 @@ final class DiffusionRepositoryDefaultController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + // NOTE: This controller is just here to make sure we call // willBeginExecution() on any /diffusion/X/ URI, so we can intercept // `git`, `hg` and `svn` HTTP protocol requests. @@ -11,7 +16,10 @@ final class DiffusionRepositoryDefaultController extends DiffusionController { // clone URI with "/anything.git" at the end into their web browser. // Send them to the canonical repository URI. + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + return id(new AphrontRedirectResponse()) - ->setURI($this->getDiffusionRequest()->getRepository()->getURI()); + ->setURI($repository->getURI()); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php index 2b0bc674be..8459498374 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActionsController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditActionsController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); // NOTE: We're inverting these here, because the storage is silly. @@ -109,14 +100,10 @@ final class DiffusionRepositoryEditActionsController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php index 2342530195..c8428833ee 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditActivateController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditActivateController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); if ($request->isFormPost()) { @@ -38,28 +29,21 @@ final class DiffusionRepositoryEditActivateController return id(new AphrontReloadResponse())->setURI($edit_uri); } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer); - if ($repository->isTracked()) { - $dialog + return $this->newDialog() ->setTitle(pht('Deactivate Repository?')) ->appendChild( pht('Deactivate this repository?')) ->addSubmitButton(pht('Deactivate Repository')) ->addCancelButton($edit_uri); } else { - $dialog + return $this->newDialog() ->setTitle(pht('Activate Repository?')) ->appendChild( pht('Activate this repository?')) ->addSubmitButton(pht('Activate Repository')) ->addCancelButton($edit_uri); } - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } - } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php index 8fe5b70f97..e47bde3902 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php @@ -3,24 +3,16 @@ final class DiffusionRepositoryEditAutomationController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + if (!$repository->supportsAutomation()) { return new Aphront404Response(); } @@ -81,14 +73,10 @@ final class DiffusionRepositoryEditAutomationController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index 80771f65c4..5b4429b61a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -3,31 +3,24 @@ final class DiffusionRepositoryEditBasicController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->needProjectPHIDs(true) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $request->getUser(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_name = $repository->getName(); $v_desc = $repository->getDetail('description'); $v_clone_name = $repository->getDetail('clone-name'); + $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( + $repository->getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $e_name = true; $errors = array(); @@ -81,7 +74,7 @@ final class DiffusionRepositoryEditBasicController id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) - ->setActor($user) + ->setActor($viewer) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); @@ -94,7 +87,7 @@ final class DiffusionRepositoryEditBasicController $title = pht('Edit %s', $repository->getName()); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') @@ -118,7 +111,7 @@ final class DiffusionRepositoryEditBasicController $form ->appendChild( id(new PhabricatorRemarkupControl()) - ->setUser($user) + ->setUser($viewer) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) @@ -127,7 +120,7 @@ final class DiffusionRepositoryEditBasicController ->setDatasource(new PhabricatorProjectDatasource()) ->setName('projectPHIDs') ->setLabel(pht('Projects')) - ->setValue($repository->getProjectPHIDs())) + ->setValue($v_projects)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) @@ -140,14 +133,10 @@ final class DiffusionRepositoryEditBasicController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } private function getReadmeInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php index a175fba941..c256336ec2 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditBranchesController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $request = $this->getRequest(); - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $is_git = false; $is_hg = false; @@ -226,14 +217,10 @@ final class DiffusionRepositoryEditBranchesController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } private function processBranches($string) { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php index 076c2580e5..26d8b57f33 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditDangerousController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + if (!$repository->canAllowDangerousChanges()) { return new Aphront400Response(); } @@ -42,13 +33,10 @@ final class DiffusionRepositoryEditDangerousController return id(new AphrontReloadResponse())->setURI($edit_uri); } - $dialog = id(new AphrontDialogView()) - ->setUser($viewer); - $force = phutil_tag('tt', array(), '--force'); if ($repository->shouldAllowDangerousChanges()) { - $dialog + return $this->newDialog() ->setTitle(pht('Prevent Dangerous changes?')) ->appendChild( pht( @@ -58,7 +46,7 @@ final class DiffusionRepositoryEditDangerousController ->addSubmitButton(pht('Prevent Dangerous Changes')) ->addCancelButton($edit_uri); } else { - $dialog + return $this->newDialog() ->setTitle(pht('Allow Dangerous Changes?')) ->appendChild( pht( @@ -69,9 +57,6 @@ final class DiffusionRepositoryEditDangerousController ->addSubmitButton(pht('Allow Dangerous Changes')) ->addCancelButton($edit_uri); } - - return id(new AphrontDialogResponse()) - ->setDialog($dialog); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php index f39708fe1b..074e79445b 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php @@ -3,24 +3,16 @@ final class DiffusionRepositoryEditDeleteController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $dialog = new AphrontDialogView(); @@ -45,14 +37,10 @@ final class DiffusionRepositoryEditDeleteController phutil_tag('p', array(), $text_2), )); - $dialog = id(new AphrontDialogView()) - ->setUser($request->getUser()) + return $this->newDialog() ->setTitle(pht('Really want to delete the repository?')) ->appendChild($body) ->addCancelButton($edit_uri, pht('Okay')); - - return id(new AphrontDialogResponse())->setDialog($dialog); } - } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php index f71516e6ca..0059c9d6c0 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditEncodingController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $user = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_encoding = $repository->getDetail('encoding'); @@ -79,14 +70,10 @@ final class DiffusionRepositoryEditEncodingController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } private function getEncodingInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php index 1fcf366967..be40b794b3 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php @@ -5,25 +5,17 @@ final class DiffusionRepositoryEditHostingController private $serve; - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - $this->serve = $request->getURIData('serve'); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $this->serve = $request->getURIData('serve'); + if (!$this->serve) { return $this->handleHosting($repository); } else { @@ -107,14 +99,10 @@ final class DiffusionRepositoryEditHostingController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } public function handleProtocols(PhabricatorRepository $repository) { @@ -272,14 +260,10 @@ final class DiffusionRepositoryEditHostingController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 780e83d089..1e19d45e7c 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -3,15 +3,15 @@ final class DiffusionRepositoryEditMainController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; + } - PhabricatorPolicyFilter::requireCapability( - $viewer, - $repository, - PhabricatorPolicyCapability::CAN_EDIT); + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); $is_svn = false; $is_git = false; diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php index 9fcfd767c0..deb8fad669 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php @@ -3,23 +3,16 @@ final class DiffusionRepositoryEditStagingController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); - } if (!$repository->supportsStaging()) { return new Aphront404Response(); @@ -43,7 +36,7 @@ final class DiffusionRepositoryEditStagingController id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) - ->setActor($user) + ->setActor($viewer) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); @@ -55,7 +48,7 @@ final class DiffusionRepositoryEditStagingController $title = pht('Edit %s', $repository->getName()); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendRemarkupInstructions( pht( "To make it easier to run integration tests and builds on code ". @@ -79,14 +72,10 @@ final class DiffusionRepositoryEditStagingController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php index 4afa594ed9..711844188a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditStorageController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_local = $repository->getHumanReadableDetail('local-path'); @@ -44,7 +35,7 @@ final class DiffusionRepositoryEditStorageController } $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Storage Service')) @@ -61,7 +52,7 @@ final class DiffusionRepositoryEditStorageController sprintf( 'phabricator/ $ ./bin/repository edit %s --as %s --local-path ...', $repository->getMonogram(), - $user->getUsername()))) + $viewer->getUsername()))) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($edit_uri, pht('Done'))); @@ -71,14 +62,10 @@ final class DiffusionRepositoryEditStorageController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php index b125f26cae..93b9193f14 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php @@ -3,25 +3,16 @@ final class DiffusionRepositoryEditSubversionController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: @@ -108,14 +99,10 @@ final class DiffusionRepositoryEditSubversionController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php index 74df9c488b..303959d098 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php @@ -3,24 +3,16 @@ final class DiffusionRepositoryEditUpdateController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); if ($request->isFormPost()) { diff --git a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php index 133ee38813..6076d2df65 100644 --- a/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php +++ b/src/applications/diffusion/controller/DiffusionRepositorySymbolsController.php @@ -3,25 +3,16 @@ final class DiffusionRepositorySymbolsController extends DiffusionRepositoryEditController { - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; - $repository = $drequest->getRepository(); - - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - - if (!$repository) { - return new Aphront404Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContextForEdit(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_sources = $repository->getSymbolSources(); @@ -55,7 +46,7 @@ final class DiffusionRepositorySymbolsController id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) - ->setActor($user) + ->setActor($viewer) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); @@ -71,7 +62,7 @@ final class DiffusionRepositorySymbolsController $title = pht('Edit %s', $repository->getName()); $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendRemarkupInstructions($this->getInstructions()) ->appendChild( id(new AphrontFormTextControl()) @@ -99,14 +90,10 @@ final class DiffusionRepositorySymbolsController ->setForm($form) ->setFormErrors($errors); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($object_box); } private function getInstructions() { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php index 691564c68e..c52b0a14e9 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php @@ -4,7 +4,7 @@ final class DiffusionRepositoryTestAutomationController extends DiffusionRepositoryEditController { public function handleRequest(AphrontRequest $request) { - $response = $this->loadDiffusionContext(); + $response = $this->loadDiffusionContextForEdit(); if ($response) { return $response; } @@ -13,19 +13,6 @@ final class DiffusionRepositoryTestAutomationController $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($repository->getID())) - ->executeOne(); - if (!$repository) { - return new Aphront404Response(); - } - $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); if (!$repository->canPerformAutomation()) { diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 08b7c876c2..72c911f5bb 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -100,7 +100,10 @@ abstract class DiffusionRequest extends Phobject { } if ($identifier !== null) { - $object = self::newFromIdentifier($identifier, $data[$viewer_key]); + $object = self::newFromIdentifier( + $identifier, + $data[$viewer_key], + idx($data, 'edit')); } else { $object = self::newFromRepository($repository); } @@ -171,12 +174,22 @@ abstract class DiffusionRequest extends Phobject { */ final private static function newFromIdentifier( $identifier, - PhabricatorUser $viewer) { + PhabricatorUser $viewer, + $need_edit = false) { - $repository = id(new PhabricatorRepositoryQuery()) + $query = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withIdentifiers(array($identifier)) - ->executeOne(); + ->withIdentifiers(array($identifier)); + + if ($need_edit) { + $query->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )); + } + + $repository = $query->executeOne(); if (!$repository) { return null; From 3cbc239bc640cd12c33da0f14c09a5a219a569c1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 10:34:04 -0800 Subject: [PATCH 53/83] Modernize most somewhat-weird Diffusion controllers Summary: Ref T4245. This gets everything else except serving HTTP requests (complicated) and lint (quite weird). Test Plan: - Viewed a diff. - Viewed externals. - Viewed history table to see last modified. - Did path completion and validation in Owners. - Did tree path search in Diffusion. - Viewed a repository. - Created a new repository. - Looked up symbols. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14947 --- .../controller/DiffusionController.php | 22 ++++++++++--- .../controller/DiffusionDiffController.php | 23 ++++--------- .../DiffusionExternalController.php | 26 +++++++-------- .../DiffusionLastModifiedController.php | 9 +++-- .../DiffusionPathCompleteController.php | 31 +++++++---------- .../DiffusionPathTreeController.php | 12 +++++-- .../DiffusionPathValidateController.php | 32 +++++++----------- .../DiffusionRepositoryController.php | 22 ++++++++----- .../DiffusionRepositoryNewController.php | 16 ++++----- .../controller/DiffusionSymbolController.php | 33 +++++++++---------- 10 files changed, 111 insertions(+), 115 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 63f3897b7f..5f1d8aa5fb 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -67,15 +67,12 @@ abstract class DiffusionController extends PhabricatorController { $request = $this->getRequest(); $viewer = $this->getViewer(); - $identifier = $request->getURIData('repositoryCallsign'); - if (!strlen($identifier)) { - $identifier = (int)$request->getURIData('repositoryID'); - } + $identifier = $this->getRepositoryIdentifierFromRequest($request); $params = $options + array( 'repository' => $identifier, 'user' => $viewer, - 'blob' => $request->getURIData('dblob'), + 'blob' => $this->getDiffusionBlobFromRequest($request), 'commit' => $request->getURIData('commit'), 'path' => $request->getURIData('path'), 'line' => $request->getURIData('line'), @@ -94,6 +91,21 @@ abstract class DiffusionController extends PhabricatorController { return null; } + protected function getDiffusionBlobFromRequest(AphrontRequest $request) { + return $request->getURIData('dblob'); + } + + protected function getRepositoryIdentifierFromRequest( + AphrontRequest $request) { + + $identifier = $request->getURIData('repositoryCallsign'); + if (strlen($identifier)) { + return $identifier; + } + + return (int)$request->getURIData('repositoryID'); + } + protected function processDiffusionRequest(AphrontRequest $request) { throw new PhutilMethodNotImplementedException(); } diff --git a/src/applications/diffusion/controller/DiffusionDiffController.php b/src/applications/diffusion/controller/DiffusionDiffController.php index bb9354c0f9..86409c6faa 100644 --- a/src/applications/diffusion/controller/DiffusionDiffController.php +++ b/src/applications/diffusion/controller/DiffusionDiffController.php @@ -6,27 +6,18 @@ final class DiffusionDiffController extends DiffusionController { return true; } - protected function shouldLoadDiffusionRequest() { - return false; + protected function getDiffusionBlobFromRequest(AphrontRequest $request) { + return $request->getStr('ref'); } - protected function processDiffusionRequest(AphrontRequest $request) { - $data = $request->getURIMap(); - $data = $data + array( - 'dblob' => $this->getRequest()->getStr('ref'), - ); - try { - $drequest = DiffusionRequest::newFromAphrontRequestDictionary( - $data, - $request); - } catch (Exception $ex) { - return id(new Aphront404Response()) - ->setRequest($request); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; } - $this->setDiffusionRequest($drequest); - $drequest = $this->getDiffusionRequest(); $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); if (!$request->isAjax()) { diff --git a/src/applications/diffusion/controller/DiffusionExternalController.php b/src/applications/diffusion/controller/DiffusionExternalController.php index b62ee94e27..09687f11d0 100644 --- a/src/applications/diffusion/controller/DiffusionExternalController.php +++ b/src/applications/diffusion/controller/DiffusionExternalController.php @@ -6,12 +6,7 @@ final class DiffusionExternalController extends DiffusionController { return true; } - protected function shouldLoadDiffusionRequest() { - return false; - } - - protected function processDiffusionRequest(AphrontRequest $request) { - + public function handleRequest(AphrontRequest $request) { $uri = $request->getStr('uri'); $id = $request->getStr('id'); @@ -64,10 +59,11 @@ final class DiffusionExternalController extends DiffusionController { if (empty($commits)) { $desc = null; - if ($uri) { - $desc = $uri.', at '; + if (strlen($uri)) { + $desc = pht('"%s", at "%s"', $uri, $id); + } else { + $desc = pht('"%s"', $id); } - $desc .= $id; $content = id(new PHUIInfoView()) ->setTitle(pht('Unknown External')) @@ -135,11 +131,13 @@ final class DiffusionExternalController extends DiffusionController { $content->setTable($table); } - return $this->buildApplicationPage( - $content, - array( - 'title' => pht('Unresolvable External'), - )); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('External')); + + return $this->newPage() + ->setTitle(pht('Unresolvable External')) + ->setCrumbs($crumbs) + ->appendChild($content); } } diff --git a/src/applications/diffusion/controller/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/DiffusionLastModifiedController.php index b23abf1903..0fea238249 100644 --- a/src/applications/diffusion/controller/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/DiffusionLastModifiedController.php @@ -6,9 +6,14 @@ final class DiffusionLastModifiedController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); - $viewer = $request->getUser(); $paths = $request->getStr('paths'); try { diff --git a/src/applications/diffusion/controller/DiffusionPathCompleteController.php b/src/applications/diffusion/controller/DiffusionPathCompleteController.php index 80cebaed03..999a04dbf8 100644 --- a/src/applications/diffusion/controller/DiffusionPathCompleteController.php +++ b/src/applications/diffusion/controller/DiffusionPathCompleteController.php @@ -2,21 +2,20 @@ final class DiffusionPathCompleteController extends DiffusionController { - protected function shouldLoadDiffusionRequest() { - return false; + protected function getRepositoryIdentifierFromRequest( + AphrontRequest $request) { + return $request->getStr('repositoryPHID'); } - protected function processDiffusionRequest(AphrontRequest $request) { - - $repository_phid = $request->getStr('repositoryPHID'); - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withPHIDs(array($repository_phid)) - ->executeOne(); - if (!$repository) { - return new Aphront400Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $query_path = $request->getStr('q'); if (preg_match('@/$@', $query_path)) { $query_dir = $query_path; @@ -25,19 +24,11 @@ final class DiffusionPathCompleteController extends DiffusionController { } $query_dir = ltrim($query_dir, '/'); - $drequest = DiffusionRequest::newFromDictionary( - array( - 'user' => $request->getUser(), - 'repository' => $repository, - 'path' => $query_dir, - )); - $this->setDiffusionRequest($drequest); - $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( - 'path' => $drequest->getPath(), + 'path' => $query_dir, 'commit' => $drequest->getCommit(), ))); $paths = $browse_results->getPaths(); diff --git a/src/applications/diffusion/controller/DiffusionPathTreeController.php b/src/applications/diffusion/controller/DiffusionPathTreeController.php index 01a9e42552..231c7b6208 100644 --- a/src/applications/diffusion/controller/DiffusionPathTreeController.php +++ b/src/applications/diffusion/controller/DiffusionPathTreeController.php @@ -2,10 +2,16 @@ final class DiffusionPathTreeController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $drequest = $this->getDiffusionRequest(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } - if (!$drequest->getRepository()->canUsePathTree()) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + if (!$repository->canUsePathTree()) { return new Aphront404Response(); } diff --git a/src/applications/diffusion/controller/DiffusionPathValidateController.php b/src/applications/diffusion/controller/DiffusionPathValidateController.php index d54ddd7af6..e6fbc41132 100644 --- a/src/applications/diffusion/controller/DiffusionPathValidateController.php +++ b/src/applications/diffusion/controller/DiffusionPathValidateController.php @@ -2,37 +2,29 @@ final class DiffusionPathValidateController extends DiffusionController { - protected function shouldLoadDiffusionRequest() { - return false; + protected function getRepositoryIdentifierFromRequest( + AphrontRequest $request) { + return $request->getStr('repositoryPHID'); } - protected function processDiffusionRequest(AphrontRequest $request) { - - $repository_phid = $request->getStr('repositoryPHID'); - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withPHIDs(array($repository_phid)) - ->executeOne(); - if (!$repository) { - return new Aphront400Response(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; } + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + $path = $request->getStr('path'); $path = ltrim($path, '/'); - $drequest = DiffusionRequest::newFromDictionary( - array( - 'user' => $request->getUser(), - 'repository' => $repository, - 'path' => $path, - )); - $this->setDiffusionRequest($drequest); - $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( - 'path' => $drequest->getPath(), + 'path' => $path, 'commit' => $drequest->getCommit(), 'needValidityOnly' => true, ))); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 21606e04ae..8b9bcb7b29 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -6,16 +6,19 @@ final class DiffusionRepositoryController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $content = array(); $crumbs = $this->buildCrumbs(); - $content[] = $crumbs; $content[] = $this->buildPropertiesTable($drequest->getRepository()); @@ -73,11 +76,14 @@ final class DiffusionRepositoryController extends DiffusionController { ->setErrors(array($empty_message)); } - return $this->buildApplicationPage( - $content, - array( - 'title' => $drequest->getRepository()->getName(), - )); + return $this->newPage() + ->setTitle( + array( + $repository->getName(), + $repository->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild($content); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryNewController.php b/src/applications/diffusion/controller/DiffusionRepositoryNewController.php index f7fac7b183..560d896aff 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryNewController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryNewController.php @@ -2,8 +2,8 @@ final class DiffusionRepositoryNewController extends DiffusionController { - protected function processDiffusionRequest(AphrontRequest $request) { - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); $this->requireApplicationCapability( DiffusionCreateRepositoriesCapability::CAPABILITY); @@ -70,14 +70,10 @@ final class DiffusionRepositoryNewController extends DiffusionController { ->setHeaderText(pht('Create or Import Repository')) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => pht('New Repository'), - )); + return $this->newPage() + ->setTitle(pht('New Repository')) + ->setCrumbs($crumbs) + ->appendChild($form_box); } } diff --git a/src/applications/diffusion/controller/DiffusionSymbolController.php b/src/applications/diffusion/controller/DiffusionSymbolController.php index 131e8774bd..defe09d58c 100644 --- a/src/applications/diffusion/controller/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/DiffusionSymbolController.php @@ -2,15 +2,13 @@ final class DiffusionSymbolController extends DiffusionController { - private $name; - - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $this->name = $request->getURIData('name'); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $name = $request->getURIData('name'); $query = id(new DiffusionSymbolQuery()) - ->setViewer($user) - ->setName($this->name); + ->setViewer($viewer) + ->setName($name); if ($request->getStr('context')) { $query->setContext($request->getStr('context')); @@ -48,9 +46,8 @@ final class DiffusionSymbolController extends DiffusionController { $symbols = $query->execute(); - $external_query = id(new DiffusionExternalSymbolQuery()) - ->withNames(array($this->name)); + ->withNames(array($name)); if ($request->getStr('context')) { $external_query->withContexts(array($request->getStr('context'))); @@ -137,15 +134,17 @@ final class DiffusionSymbolController extends DiffusionController { $table->setNoDataString( pht('No matching symbol could be found in any indexed repository.')); - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Similar Symbols')); - $panel->setTable($table); + $panel = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Similar Symbols')) + ->setTable($table); - return $this->buildApplicationPage( - $panel, - array( - 'title' => pht('Find Symbol'), - )); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Find Symbol')); + + return $this->newPage() + ->setTitle(pht('Find Symbol')) + ->setCrumbs($crumbs) + ->appendChild($panel); } } From 38f2008e68b4199831e762ee3e3639e05b9ba8c6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 12:28:38 -0800 Subject: [PATCH 54/83] Modernize Diffusion lint controllers Summary: Ref T4245. On their best day these don't work all that well, but I'm pretty sure I didn't make anything worse. Test Plan: - Viewed global lint. - Viewed lint for a repository. - Viewed lint details for a particular message. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14948 --- src/__phutil_library_map__.php | 2 - .../controller/DiffusionLintController.php | 285 ++++++++++++++---- .../DiffusionLintDetailsController.php | 143 --------- .../diffusion/request/DiffusionRequest.php | 5 +- 4 files changed, 230 insertions(+), 205 deletions(-) delete mode 100644 src/applications/diffusion/controller/DiffusionLintDetailsController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a531b964be..0be7b1db7f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -621,7 +621,6 @@ phutil_register_library_map(array( 'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php', 'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php', 'DiffusionLintCountQuery' => 'applications/diffusion/query/DiffusionLintCountQuery.php', - 'DiffusionLintDetailsController' => 'applications/diffusion/controller/DiffusionLintDetailsController.php', 'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php', 'DiffusionLookSoonConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php', 'DiffusionLowLevelCommitFieldsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php', @@ -4585,7 +4584,6 @@ phutil_register_library_map(array( 'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionLintController' => 'DiffusionController', 'DiffusionLintCountQuery' => 'PhabricatorQuery', - 'DiffusionLintDetailsController' => 'DiffusionController', 'DiffusionLintSaveRunner' => 'Phobject', 'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery', diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index ec5ed3a14e..dcbb217d99 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -6,76 +6,106 @@ final class DiffusionLintController extends DiffusionController { return true; } - protected function processDiffusionRequest(AphrontRequest $request) { - $user = $request->getUser(); - $drequest = $this->diffusionRequest; + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); - if ($request->getStr('lint') !== null) { - $controller = new DiffusionLintDetailsController(); - $controller->setDiffusionRequest($drequest); - $controller->setCurrentApplication($this->getCurrentApplication()); - return $this->delegateToController($controller); + if ($this->getRepositoryIdentifierFromRequest($request)) { + $response = $this->loadDiffusionContext(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + } else { + $drequest = null; + } + + $code = $request->getStr('lint'); + if (strlen($code)) { + return $this->buildDetailsResponse(); } $owners = array(); if (!$drequest) { if (!$request->getArr('owner')) { - $owners = array($user->getPHID()); + $owners = array($viewer->getPHID()); } else { $owners = array(head($request->getArr('owner'))); } } - $codes = $this->loadLintCodes($owners); - - if ($codes && !$drequest) { - // TODO: Build some real Query classes for this stuff. + $codes = $this->loadLintCodes($drequest, $owners); + if ($codes) { $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere( 'id IN (%Ld)', array_unique(ipull($codes, 'branchID'))); + $branches = mpull($branches, null, 'getID'); + } else { + $branches = array(); + } + if ($branches) { $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(mpull($branches, 'getRepositoryID')) ->execute(); - - $drequests = array(); - foreach ($branches as $id => $branch) { - if (empty($repositories[$branch->getRepositoryID()])) { - continue; - } - - $drequests[$id] = DiffusionRequest::newFromDictionary(array( - 'user' => $user, - 'repository' => $repositories[$branch->getRepositoryID()], - 'branch' => $branch->getName(), - )); - } + $repositories = mpull($repositories, null, 'getID'); + } else { + $repositories = array(); } + $rows = array(); $total = 0; foreach ($codes as $code) { - if (!$this->diffusionRequest) { - $drequest = idx($drequests, $code['branchID']); + $branch = idx($branches, $code['branchID']); + if (!$branch) { + continue; } - if (!$drequest) { + $repository = idx($repositories, $branch->getRepositoryID()); + if (!$repository) { continue; } $total += $code['n']; - $href_lint = $drequest->generateURI(array( - 'action' => 'lint', - 'lint' => $code['code'], - )); - $href_browse = $drequest->generateURI(array( - 'action' => 'browse', - 'lint' => $code['code'], - )); - $href_repo = $drequest->generateURI(array('action' => 'lint')); + if ($drequest) { + $href_lint = $drequest->generateURI( + array( + 'action' => 'lint', + 'lint' => $code['code'], + )); + + $href_browse = $drequest->generateURI( + array( + 'action' => 'browse', + 'lint' => $code['code'], + )); + + $href_repo = $drequest->generateURI( + array( + 'action' => 'lint', + )); + } else { + $href_lint = $repository->generateURI( + array( + 'action' => 'lint', + 'lint' => $code['code'], + )); + + $href_browse = $repository->generateURI( + array( + 'action' => 'browse', + 'lint' => $code['code'], + )); + + $href_repo = $repository->generateURI( + array( + 'action' => 'lint', + )); + } $rows[] = array( phutil_tag('a', array('href' => $href_lint), $code['n']), @@ -85,7 +115,7 @@ final class DiffusionLintController extends DiffusionController { array( 'href' => $href_repo, ), - $drequest->getRepository()->getDisplayName()), + $repository->getDisplayName()), ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']), $code['code'], $code['maxName'], @@ -103,14 +133,14 @@ final class DiffusionLintController extends DiffusionController { pht('Name'), pht('Example'), )) - ->setColumnVisibility(array(true, true, !$this->diffusionRequest)) + ->setColumnVisibility(array(true, true, !$drequest)) ->setColumnClasses(array('n', 'n', '', '', 'pri', '', '')); $content = array(); - if (!$this->diffusionRequest) { + if (!$drequest) { $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->setMethod('GET') ->appendControl( id(new AphrontFormTokenizerControl()) @@ -137,18 +167,18 @@ final class DiffusionLintController extends DiffusionController { 'view' => 'lint', )); - if ($this->diffusionRequest) { + if ($drequest) { $title[] = $drequest->getRepository()->getDisplayName(); } else { $crumbs->addTextCrumb(pht('All Lint')); } - if ($this->diffusionRequest) { + if ($drequest) { $branch = $drequest->loadBranch(); $header = id(new PHUIHeaderView()) ->setHeader($this->renderPathLinks($drequest, 'lint')) - ->setUser($user) + ->setUser($viewer) ->setPolicyObject($drequest->getRepository()); $actions = $this->buildActionView($drequest); $properties = $this->buildPropertyView( @@ -164,20 +194,17 @@ final class DiffusionLintController extends DiffusionController { $object_box = null; } - - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $content, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $content, + )); } - private function loadLintCodes(array $owner_phids) { - $drequest = $this->diffusionRequest; + private function loadLintCodes($drequest, array $owner_phids) { $conn = id(new PhabricatorRepository())->establishConnection('r'); $where = array('1 = 1'); @@ -342,4 +369,146 @@ final class DiffusionLintController extends DiffusionController { } + private function buildDetailsResponse() { + $request = $this->getRequest(); + + $limit = 500; + + $pager = id(new PHUIPagerView()) + ->readFromRequest($request) + ->setPageSize($limit); + + $offset = $pager->getOffset(); + + $drequest = $this->getDiffusionRequest(); + $branch = $drequest->loadBranch(); + $messages = $this->loadLintMessages($branch, $limit, $offset); + $is_dir = (substr('/'.$drequest->getPath(), -1) == '/'); + + $pager->setHasMorePages(count($messages) >= $limit); + + $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID')); + + $rows = array(); + foreach ($messages as $message) { + $path = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI(array( + 'action' => 'lint', + 'path' => $message['path'], + )), + ), + substr($message['path'], strlen($drequest->getPath()) + 1)); + + $line = phutil_tag( + 'a', + array( + 'href' => $drequest->generateURI(array( + 'action' => 'browse', + 'path' => $message['path'], + 'line' => $message['line'], + 'commit' => $branch->getLintCommit(), + )), + ), + $message['line']); + + $author = $message['authorPHID']; + if ($author && $authors[$author]) { + $author = $authors[$author]->renderLink(); + } + + $rows[] = array( + $path, + $line, + $author, + ArcanistLintSeverity::getStringForSeverity($message['severity']), + $message['name'], + $message['description'], + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders(array( + pht('Path'), + pht('Line'), + pht('Author'), + pht('Severity'), + pht('Name'), + pht('Description'), + )) + ->setColumnClasses(array('', 'n')) + ->setColumnVisibility(array($is_dir)); + + $content = array(); + + $content[] = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Lint Details')) + ->setTable($table); + + $crumbs = $this->buildCrumbs( + array( + 'branch' => true, + 'path' => true, + 'view' => 'lint', + )); + + $pager_box = $this->renderTablePagerBox($pager); + + return $this->newPage() + ->setTitle( + array( + pht('Lint'), + $drequest->getRepository()->getDisplayName(), + )) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, + $pager_box, + )); + } + + private function loadLintMessages( + PhabricatorRepositoryBranch $branch, + $limit, + $offset) { + + $drequest = $this->getDiffusionRequest(); + if (!$branch) { + return array(); + } + + $conn = $branch->establishConnection('r'); + + $where = array( + qsprintf($conn, 'branchID = %d', $branch->getID()), + ); + + if ($drequest->getPath() != '') { + $path = '/'.$drequest->getPath(); + $is_dir = (substr($path, -1) == '/'); + $where[] = ($is_dir + ? qsprintf($conn, 'path LIKE %>', $path) + : qsprintf($conn, 'path = %s', $path)); + } + + if ($drequest->getLint() != '') { + $where[] = qsprintf( + $conn, + 'code = %s', + $drequest->getLint()); + } + + return queryfx_all( + $conn, + 'SELECT * + FROM %T + WHERE %Q + ORDER BY path, code, line LIMIT %d OFFSET %d', + PhabricatorRepository::TABLE_LINTMESSAGE, + implode(' AND ', $where), + $limit, + $offset); + } } diff --git a/src/applications/diffusion/controller/DiffusionLintDetailsController.php b/src/applications/diffusion/controller/DiffusionLintDetailsController.php deleted file mode 100644 index 13ef9c5559..0000000000 --- a/src/applications/diffusion/controller/DiffusionLintDetailsController.php +++ /dev/null @@ -1,143 +0,0 @@ -getInt('offset', 0); - - $drequest = $this->getDiffusionRequest(); - $branch = $drequest->loadBranch(); - $messages = $this->loadLintMessages($branch, $limit, $offset); - $is_dir = (substr('/'.$drequest->getPath(), -1) == '/'); - - $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID')); - - $rows = array(); - foreach ($messages as $message) { - $path = phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI(array( - 'action' => 'lint', - 'path' => $message['path'], - )), - ), - substr($message['path'], strlen($drequest->getPath()) + 1)); - - $line = phutil_tag( - 'a', - array( - 'href' => $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $message['path'], - 'line' => $message['line'], - 'commit' => $branch->getLintCommit(), - )), - ), - $message['line']); - - $author = $message['authorPHID']; - if ($author && $authors[$author]) { - $author = $authors[$author]->renderLink(); - } - - $rows[] = array( - $path, - $line, - $author, - ArcanistLintSeverity::getStringForSeverity($message['severity']), - $message['name'], - $message['description'], - ); - } - - $table = id(new AphrontTableView($rows)) - ->setHeaders(array( - pht('Path'), - pht('Line'), - pht('Author'), - pht('Severity'), - pht('Name'), - pht('Description'), - )) - ->setColumnClasses(array('', 'n')) - ->setColumnVisibility(array($is_dir)); - - $content = array(); - - $pager = id(new PHUIPagerView()) - ->setPageSize($limit) - ->setOffset($offset) - ->setHasMorePages(count($messages) >= $limit) - ->setURI($request->getRequestURI(), 'offset'); - - $content[] = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Lint Details')) - ->setTable($table); - - $crumbs = $this->buildCrumbs( - array( - 'branch' => true, - 'path' => true, - 'view' => 'lint', - )); - - return $this->buildApplicationPage( - array( - $crumbs, - $content, - $pager, - ), - array( - 'title' => array( - pht('Lint'), - $drequest->getRepository()->getDisplayName(), - ), - )); - } - - private function loadLintMessages( - PhabricatorRepositoryBranch $branch, - $limit, - $offset) { - - $drequest = $this->getDiffusionRequest(); - if (!$branch) { - return array(); - } - - $conn = $branch->establishConnection('r'); - - $where = array( - qsprintf($conn, 'branchID = %d', $branch->getID()), - ); - - if ($drequest->getPath() != '') { - $path = '/'.$drequest->getPath(); - $is_dir = (substr($path, -1) == '/'); - $where[] = ($is_dir - ? qsprintf($conn, 'path LIKE %>', $path) - : qsprintf($conn, 'path = %s', $path)); - } - - if ($drequest->getLint() != '') { - $where[] = qsprintf( - $conn, - 'code = %s', - $drequest->getLint()); - } - - return queryfx_all( - $conn, - 'SELECT * - FROM %T - WHERE %Q - ORDER BY path, code, line LIMIT %d OFFSET %d', - PhabricatorRepository::TABLE_LINTMESSAGE, - implode(' AND ', $where), - $limit, - $offset); - } - -} diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 72c911f5bb..1373adfab1 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -244,9 +244,10 @@ abstract class DiffusionRequest extends Phobject { $data = $blob + $data; } - $this->path = idx($data, 'path'); - $this->line = idx($data, 'line'); + $this->path = idx($data, 'path'); + $this->line = idx($data, 'line'); $this->initFromConduit = idx($data, 'initFromConduit', true); + $this->lint = idx($data, 'lint'); $this->symbolicCommit = idx($data, 'commit'); if ($this->supportsBranches()) { From f9a5cd2bbd86d82842ab7873227d23bf67cad94b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 12:38:22 -0800 Subject: [PATCH 55/83] Fix all remaining weird Diffusion request processing Summary: Ref T4245. This is the last of it, and covers the clone/push stuff. Test Plan: - Cloned git. - Pushed git. - Cloned mercurial. - Pushed mercurial. - Visited a `blah.git` URL in my browser just because; got redirected to a human-facing UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4245 Differential Revision: https://secure.phabricator.com/D14949 --- .../PhabricatorDiffusionApplication.php | 5 +- .../controller/DiffusionController.php | 49 ++++++------------- .../DiffusionRepositoryEditController.php | 5 +- .../controller/DiffusionServeController.php | 27 +++------- .../diffusion/request/DiffusionRequest.php | 37 -------------- 5 files changed, 26 insertions(+), 97 deletions(-) diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 20b6f5e4cd..0f809e9983 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -64,7 +64,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'DiffusionPushLogListController', 'view/(?P\d+)/' => 'DiffusionPushEventViewController', ), - '(?P(?P[A-Z]+))/' => array( + '(?P[A-Z]+)/' => array( '' => 'DiffusionRepositoryController', 'repository/(?P.*)' => 'DiffusionRepositoryController', @@ -115,7 +115,8 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { // catch-all for serving repositories over HTTP. We must accept // requests without the trailing "/" because SVN commands don't // necessarily include it. - '(?P[A-Z]+)(/|$).*' => 'DiffusionRepositoryDefaultController', + '(?P[A-Z]+)(?:/.*)?' => + 'DiffusionRepositoryDefaultController', 'inline/' => array( 'edit/(?P[^/]+)/' => 'DiffusionInlineCommentController', diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 5f1d8aa5fb..80364fd2ef 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -2,56 +2,34 @@ abstract class DiffusionController extends PhabricatorController { - protected $diffusionRequest; - - public function setDiffusionRequest(DiffusionRequest $request) { - $this->diffusionRequest = $request; - return $this; - } + private $diffusionRequest; protected function getDiffusionRequest() { if (!$this->diffusionRequest) { - throw new Exception(pht('No Diffusion request object!')); + throw new PhutilInvalidStateException('loadDiffusionContext'); } return $this->diffusionRequest; } + protected function hasDiffusionRequest() { + return (bool)$this->diffusionRequest; + } + public function willBeginExecution() { $request = $this->getRequest(); // Check if this is a VCS request, e.g. from "git clone", "hg clone", or // "svn checkout". If it is, we jump off into repository serving code to // process the request. - if (DiffusionServeController::isVCSRequest($request)) { - $serve_controller = id(new DiffusionServeController()) - ->setCurrentApplication($this->getCurrentApplication()); + + $serve_controller = new DiffusionServeController(); + if ($serve_controller->isVCSRequest($request)) { return $this->delegateToController($serve_controller); } return parent::willBeginExecution(); } - protected function shouldLoadDiffusionRequest() { - return true; - } - - public function handleRequest(AphrontRequest $request) { - if ($request->getURIData('callsign') && - $this->shouldLoadDiffusionRequest()) { - try { - $drequest = DiffusionRequest::newFromAphrontRequestDictionary( - $request->getURIMap(), - $request); - } catch (Exception $ex) { - return id(new Aphront404Response()) - ->setRequest($request); - } - $this->setDiffusionRequest($drequest); - } - - return $this->processDiffusionRequest($request); - } - protected function loadDiffusionContextForEdit() { return $this->loadContext( array( @@ -103,11 +81,12 @@ abstract class DiffusionController extends PhabricatorController { return $identifier; } - return (int)$request->getURIData('repositoryID'); - } + $id = $request->getURIData('repositoryID'); + if (strlen($id)) { + return (int)$id; + } - protected function processDiffusionRequest(AphrontRequest $request) { - throw new PhutilMethodNotImplementedException(); + return null; } public function buildCrumbs(array $spec = array()) { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index 68749b1132..1e3cb553da 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -6,8 +6,9 @@ abstract class DiffusionRepositoryEditController protected function buildApplicationCrumbs($is_main = false) { $crumbs = parent::buildApplicationCrumbs(); - if ($this->diffusionRequest) { - $repository = $this->getDiffusionRequest()->getRepository(); + if ($this->hasDiffusionRequest()) { + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); $repo_uri = $repository->getURI(); $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 271ab36e91..ea5d4c209d 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -2,12 +2,9 @@ final class DiffusionServeController extends DiffusionController { - protected function shouldLoadDiffusionRequest() { - return false; - } - - public static function isVCSRequest(AphrontRequest $request) { - if (!self::getCallsign($request)) { + public function isVCSRequest(AphrontRequest $request) { + $identifier = $this->getRepositoryIdentifierFromRequest($request); + if ($identifier === null) { return null; } @@ -47,20 +44,8 @@ final class DiffusionServeController extends DiffusionController { return $vcs; } - private static function getCallsign(AphrontRequest $request) { - $uri = $request->getRequestURI(); - - $regex = '@^/diffusion/(?P[A-Z]+)(/|$)@'; - $matches = null; - if (!preg_match($regex, (string)$uri, $matches)) { - return null; - } - - return $matches['callsign']; - } - - protected function processDiffusionRequest(AphrontRequest $request) { - $callsign = self::getCallsign($request); + public function handleRequest(AphrontRequest $request) { + $identifier = $this->getRepositoryIdentifierFromRequest($request); // If authentication credentials have been provided, try to find a user // that actually matches those credentials. @@ -99,7 +84,7 @@ final class DiffusionServeController extends DiffusionController { try { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withCallsigns(array($callsign)) + ->withIdentifiers(array($identifier)) ->executeOne(); if (!$repository) { return new PhabricatorVCSResponse( diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php index 1373adfab1..8d326b81bc 100644 --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -117,43 +117,6 @@ abstract class DiffusionRequest extends Phobject { return $object; } - - /** - * Create a new request from an Aphront request dictionary. This is an - * internal method that you generally should not call directly; instead, - * call @{method:newFromDictionary}. - * - * @param map Map of Aphront request data. - * @return DiffusionRequest New request object. - * @task new - */ - final public static function newFromAphrontRequestDictionary( - array $data, - AphrontRequest $request) { - - $identifier = phutil_unescape_uri_path_component(idx($data, 'callsign')); - $object = self::newFromIdentifier($identifier, $request->getUser()); - - $use_branches = $object->supportsBranches(); - - if (isset($data['dblob'])) { - $parsed = self::parseRequestBlob(idx($data, 'dblob'), $use_branches); - } else { - $parsed = array( - 'commit' => idx($data, 'commit'), - 'path' => idx($data, 'path'), - 'line' => idx($data, 'line'), - 'branch' => idx($data, 'branch'), - ); - } - - $object->setUser($request->getUser()); - $object->initializeFromDictionary($parsed); - $object->lint = $request->getStr('lint'); - return $object; - } - - /** * Internal. * From d0cdf1efdb315a241d9f190943853f8926090a14 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 13:28:41 -0800 Subject: [PATCH 56/83] When a repository is importing, show it on the list view Summary: Fixes T9191. This is pretty fluff but doesn't hurt anything, I guess. Test Plan: Viewed repository list, saw an importing repository get a little icon. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9191 Differential Revision: https://secure.phabricator.com/D14950 --- .../repository/query/PhabricatorRepositorySearchEngine.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 0587e314b7..98175f88d7 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -204,6 +204,8 @@ final class PhabricatorRepositorySearchEngine if (!$repository->isTracked()) { $item->setDisabled(true); $item->addIcon('disable-grey', pht('Inactive')); + } else if ($repository->isImporting()) { + $item->addIcon('fa-clock-o violet', pht('Importing...')); } $list->addItem($item); From 94d79c11a93c952b8e0ad82b3d33cddba86ffc8c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 13:36:32 -0800 Subject: [PATCH 57/83] Show import progress on repository main page Summary: Fixes T9192. Test Plan: {F1055042} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9192 Differential Revision: https://secure.phabricator.com/D14951 --- .../DiffusionRepositoryController.php | 7 +++- .../DiffusionRepositoryEditMainController.php | 41 +------------------ .../PhabricatorRepositorySearchEngine.php | 2 +- .../storage/PhabricatorRepository.php | 41 +++++++++++++++++++ 4 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 8b9bcb7b29..5d65270c7c 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -223,7 +223,12 @@ final class DiffusionRepositoryController extends DiffusionController { if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); } else if ($repository->isImporting()) { - $header->setStatus('fa-clock-o', 'indigo', pht('Importing...')); + $ratio = $repository->loadImportProgress(); + $percentage = sprintf('%.2f%%', 100 * $ratio); + $header->setStatus( + 'fa-clock-o', + 'indigo', + pht('Importing (%s)...', $percentage)); } else { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 1e19d45e7c..bbf7152bf3 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -1137,45 +1137,8 @@ final class DiffusionRepositoryEditMainController } if ($repository->isImporting()) { - $progress = queryfx_all( - $repository->establishConnection('r'), - 'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d - GROUP BY importStatus', - id(new PhabricatorRepositoryCommit())->getTableName(), - $repository->getID()); - - $done = 0; - $total = 0; - foreach ($progress as $row) { - $total += $row['N'] * 4; - $status = $row['importStatus']; - if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) { - $done += $row['N']; - } - if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) { - $done += $row['N']; - } - if ($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS) { - $done += $row['N']; - } - if ($status & PhabricatorRepositoryCommit::IMPORTED_HERALD) { - $done += $row['N']; - } - } - - if ($total) { - $percentage = 100 * ($done / $total); - } else { - $percentage = 0; - } - - // Cap this at "99.99%", because it's confusing to users when the actual - // fraction is "99.996%" and it rounds up to "100.00%". - if ($percentage > 99.99) { - $percentage = 99.99; - } - - $percentage = sprintf('%.2f%%', $percentage); + $ratio = $repository->loadImportProgress(); + $percentage = sprintf('%.2f%%', 100 * $ratio); $view->addItem( id(new PHUIStatusItemView()) diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 98175f88d7..ad3b61c8df 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -205,7 +205,7 @@ final class PhabricatorRepositorySearchEngine $item->setDisabled(true); $item->addIcon('disable-grey', pht('Inactive')); } else if ($repository->isImporting()) { - $item->addIcon('fa-clock-o violet', pht('Importing...')); + $item->addIcon('fa-clock-o indigo', pht('Importing...')); } $list->addItem($item); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 97b9684d0b..c9455d38f8 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -867,6 +867,47 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO return (bool)$this->getDetail('importing', false); } + public function loadImportProgress() { + $progress = queryfx_all( + $this->establishConnection('r'), + 'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d + GROUP BY importStatus', + id(new PhabricatorRepositoryCommit())->getTableName(), + $this->getID()); + + $done = 0; + $total = 0; + foreach ($progress as $row) { + $total += $row['N'] * 4; + $status = $row['importStatus']; + if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) { + $done += $row['N']; + } + if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) { + $done += $row['N']; + } + if ($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS) { + $done += $row['N']; + } + if ($status & PhabricatorRepositoryCommit::IMPORTED_HERALD) { + $done += $row['N']; + } + } + + if ($total) { + $ratio = ($done / $total); + } else { + $ratio = 0; + } + + // Cap this at "99.99%", because it's confusing to users when the actual + // fraction is "99.996%" and it rounds up to "100.00%". + if ($ratio > 0.9999) { + $ratio = 0.9999; + } + + return $ratio; + } /** * Should this repository publish feed, notifications, audits, and email? From b471ebe9870bb0fc7699ea74db78e295aa553886 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 13:42:16 -0800 Subject: [PATCH 58/83] Document that hosted repositories should be backed up Summary: Fixes T8950. Test Plan: Reading. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8950 Differential Revision: https://secure.phabricator.com/D14952 --- .../configuration/configuring_backups.diviner | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/src/docs/user/configuration/configuring_backups.diviner b/src/docs/user/configuration/configuring_backups.diviner index b96531d9ff..1f13e0e17f 100644 --- a/src/docs/user/configuration/configuring_backups.diviner +++ b/src/docs/user/configuration/configuring_backups.diviner @@ -3,7 +3,9 @@ Advice for backing up Phabricator, or migrating from one machine to another. -= Overview = + +Overview +======== Phabricator does not currently have a comprehensive backup system, but creating backups is not particularly difficult and Phabricator does have a few basic @@ -11,6 +13,7 @@ tools which can help you set up a reasonable process. In particular, the things which needs to be backed up are: - the MySQL databases; + - hosted repositories; - uploaded files; and - your Phabricator configuration files. @@ -21,7 +24,9 @@ same steps you would if you were creating a backup and then restoring it, you will just backup the old machine and then restore the data onto the new machine. -= Backup: MySQL Databases = + +Backup: MySQL Databases +======================= Most of Phabricator's data is stored in MySQL, and it's the most important thing to back up. You can run `bin/storage dump` to get a dump of all the MySQL @@ -36,14 +41,35 @@ gzip prior to storage. For example: Then store the backup somewhere safe, like in a box buried under an old tree stump. No one will ever think to look for it there. -= Restore: MySQL = +Restore: MySQL +============== To restore a MySQL dump, just pipe it to `mysql` on a clean host. (You may need to uncompress it first, if you compressed it prior to storage.) $ gunzip -c backup.sql.gz | mysql -= Backup: Uploaded Files = + +Backup: Hosted Repositories +=========================== + +If you host repositories in Phabricator, you should back them up. You can use +`bin/repository list-paths` to show the local paths on disk for each +repository. To back them up, copy them elsewhere. + +You can also just clone them and keep the clones up to date, or use +{nav Add Mirror} to have the mirror somewhere automatically. + + +Restore: Hosted Repositories +============================ + +To restore hosted repositories, copy them back into the correct locations +as shown by `bin/repository list-paths`. + + +Backup: Uploaded Files +====================== Uploaded files may be stored in several different locations. The backup procedure depends on where files are stored: @@ -65,11 +91,15 @@ setting). For more information about configuring how files are stored, see @{article:Configuring File Storage}. -= Restore: Uploaded Files = + +Restore: Uploaded Files +======================= To restore a backup of local disk storage, just copy the backup into place. -= Backup: Configuration Files = + +Backup: Configuration Files +=========================== You should also backup your configuration files, and any scripts you use to deploy or administrate Phabricator (like a customized upgrade script). The best @@ -85,12 +115,15 @@ creates: This file contains all of the configuration settings that have been adjusted by using `bin/config set `. -= Restore: Configuration Files = + +Restore: Configuration Files +============================ To restore configuration files, just copy them into the right locations. Copy your backup of `local.json` to `phabricator/conf/local/local.json`. -= Security = +Security +======== MySQL dumps have no builtin encryption and most data in Phabricator is stored in a raw, accessible form, so giving a user access to backups is a lot like giving @@ -105,7 +138,9 @@ Some of this information is durable, so disclosure of even a very old backup may present a risk. If you restrict access to the Phabricator host or database, you should also restrict access to the backups. -= Next Steps = + +Next Steps +========== Continue by: From 744215d5ffe4f14f4ca3a488aa192aa2b9dde249 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 27 Dec 2015 20:19:39 -0800 Subject: [PATCH 59/83] Add Herald Adapters to Phame Summary: Adds a basic HeraldAdapter to Phame Blogs and Posts. Test Plan: Make a Herald rule to CC me on new posts or blogs automatically. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14897 --- src/__phutil_library_map__.php | 4 ++ .../phame/editor/PhameBlogEditor.php | 14 +++++ .../phame/editor/PhamePostEditor.php | 14 +++++ .../phame/herald/HeraldPhameBlogAdapter.php | 62 +++++++++++++++++++ .../phame/herald/HeraldPhamePostAdapter.php | 62 +++++++++++++++++++ 5 files changed, 156 insertions(+) create mode 100644 src/applications/phame/herald/HeraldPhameBlogAdapter.php create mode 100644 src/applications/phame/herald/HeraldPhamePostAdapter.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0be7b1db7f..bfba62e253 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1155,6 +1155,8 @@ phutil_register_library_map(array( 'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php', 'HeraldNotifyActionGroup' => 'applications/herald/action/HeraldNotifyActionGroup.php', 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php', + 'HeraldPhameBlogAdapter' => 'applications/phame/herald/HeraldPhameBlogAdapter.php', + 'HeraldPhamePostAdapter' => 'applications/phame/herald/HeraldPhamePostAdapter.php', 'HeraldPholioMockAdapter' => 'applications/pholio/herald/HeraldPholioMockAdapter.php', 'HeraldPonderQuestionAdapter' => 'applications/ponder/herald/HeraldPonderQuestionAdapter.php', 'HeraldPreCommitAdapter' => 'applications/diffusion/herald/HeraldPreCommitAdapter.php', @@ -5213,6 +5215,8 @@ phutil_register_library_map(array( 'HeraldNewObjectField' => 'HeraldField', 'HeraldNotifyActionGroup' => 'HeraldActionGroup', 'HeraldObjectTranscript' => 'Phobject', + 'HeraldPhameBlogAdapter' => 'HeraldAdapter', + 'HeraldPhamePostAdapter' => 'HeraldAdapter', 'HeraldPholioMockAdapter' => 'HeraldAdapter', 'HeraldPonderQuestionAdapter' => 'HeraldAdapter', 'HeraldPreCommitAdapter' => 'HeraldAdapter', diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index c73c2be0a4..a59ede32be 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -230,4 +230,18 @@ final class PhameBlogEditor return false; } + protected function shouldApplyHeraldRules( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function buildHeraldAdapter( + PhabricatorLiskDAO $object, + array $xactions) { + + return id(new HeraldPhameBlogAdapter()) + ->setBlog($object); + } + } diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 8978be89b6..fa43a131ff 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -264,4 +264,18 @@ final class PhamePostEditor return false; } + protected function shouldApplyHeraldRules( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function buildHeraldAdapter( + PhabricatorLiskDAO $object, + array $xactions) { + + return id(new HeraldPhamePostAdapter()) + ->setPost($object); + } + } diff --git a/src/applications/phame/herald/HeraldPhameBlogAdapter.php b/src/applications/phame/herald/HeraldPhameBlogAdapter.php new file mode 100644 index 0000000000..d8956ba4a6 --- /dev/null +++ b/src/applications/phame/herald/HeraldPhameBlogAdapter.php @@ -0,0 +1,62 @@ +blog = $this->newObject(); + } + + public function supportsApplicationEmail() { + return true; + } + + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::EVERY, + HeraldRepetitionPolicyConfig::FIRST, + ); + } + + public function supportsRuleType($rule_type) { + switch ($rule_type) { + case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: + case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: + return true; + case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: + default: + return false; + } + } + + public function setBlog(PhameBlog $blog) { + $this->blog = $blog; + return $this; + } + + public function getObject() { + return $this->blog; + } + + public function getAdapterContentName() { + return pht('Phame Blogs'); + } + + public function getHeraldName() { + return 'BLOG'.$this->getObject()->getID(); + } + +} diff --git a/src/applications/phame/herald/HeraldPhamePostAdapter.php b/src/applications/phame/herald/HeraldPhamePostAdapter.php new file mode 100644 index 0000000000..4776bbbdbc --- /dev/null +++ b/src/applications/phame/herald/HeraldPhamePostAdapter.php @@ -0,0 +1,62 @@ +post = $this->newObject(); + } + + public function supportsApplicationEmail() { + return true; + } + + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::EVERY, + HeraldRepetitionPolicyConfig::FIRST, + ); + } + + public function supportsRuleType($rule_type) { + switch ($rule_type) { + case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: + case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: + return true; + case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: + default: + return false; + } + } + + public function setPost(PhamePost $post) { + $this->post = $post; + return $this; + } + + public function getObject() { + return $this->post; + } + + public function getAdapterContentName() { + return pht('Phame Posts'); + } + + public function getHeraldName() { + return 'POST'.$this->getObject()->getID(); + } + +} From e068188ea147b8888105de6db56cda2988039d3e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 14:56:28 -0800 Subject: [PATCH 60/83] Mention !status explicitly in the documentation for !close Summary: Ref T10088. Test Plan: {F1055107} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10088 Differential Revision: https://secure.phabricator.com/D14953 --- .../maniphest/command/ManiphestClaimEmailCommand.php | 5 ++++- .../maniphest/command/ManiphestCloseEmailCommand.php | 6 +++++- .../maniphest/command/ManiphestStatusEmailCommand.php | 6 ++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php index 38919617c6..4a6a348dbb 100644 --- a/src/applications/maniphest/command/ManiphestClaimEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestClaimEmailCommand.php @@ -8,7 +8,10 @@ final class ManiphestClaimEmailCommand } public function getCommandSummary() { - return pht('Assign yourself as the owner of a task.'); + return pht( + 'Assign yourself as the owner of a task. To assign another user, '. + 'see `%s`.', + '!assign'); } public function buildTransactions( diff --git a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php index a30f3a35f0..8104fd8b8d 100644 --- a/src/applications/maniphest/command/ManiphestCloseEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestCloseEmailCommand.php @@ -8,7 +8,11 @@ final class ManiphestCloseEmailCommand } public function getCommandSummary() { - return pht('Close a task.'); + return pht( + 'Close a task. This changes the task status to the default closed '. + 'status. For a more powerful (but less concise) way to change task '. + 'statuses, see `%s`.', + '!status'); } public function buildTransactions( diff --git a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php index aa74e33bec..dace0cb255 100644 --- a/src/applications/maniphest/command/ManiphestStatusEmailCommand.php +++ b/src/applications/maniphest/command/ManiphestStatusEmailCommand.php @@ -36,9 +36,11 @@ final class ManiphestStatusEmailCommand "To change the status of a task, specify the desired status, like ". "`%s`. This table shows the configured names for statuses.\n\n%s\n\n". "If you specify an invalid status, the command is ignored. This ". - "command has no effect if you do not specify a status.", + "command has no effect if you do not specify a status.\n\n". + "To quickly close a task, see `%s`.", '!status invalid', - $table); + $table, + '!close'); } public function buildTransactions( From d326c6096e6e952e61a6a8807ce30e23b2ada558 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jan 2016 18:07:11 -0800 Subject: [PATCH 61/83] Make unsubscribing from a project have an effect Summary: Fixes T10089. This did work at one point, but was broken by D12868, which got too aggressive about mailing members. We don't want to send mail to all members by default, only those who are subscribed. The parent implementation of `getMailCC()` handles this for us. Test Plan: Joined a project as users A and B. Unsubscribed with B. Made an edit. Before patch: both A and B got mail. After patch: only A got mail. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10089 Differential Revision: https://secure.phabricator.com/D14955 --- .../editor/PhabricatorProjectTransactionEditor.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 86748f6287..54440a6ee9 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -597,12 +597,9 @@ final class PhabricatorProjectTransactionEditor } protected function getMailTo(PhabricatorLiskDAO $object) { - return $object->getMemberPHIDs(); - } - - protected function getMailCC(PhabricatorLiskDAO $object) { - $all = parent::getMailCC($object); - return array_diff($all, $object->getMemberPHIDs()); + return array( + $this->getActingAsPHID(), + ); } public function getMailTagsMap() { From f561dc172d4948c53affdb333b4236c7ebc7dbe4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 04:56:27 -0800 Subject: [PATCH 62/83] Implement a dedicated "diffusion.blame" API method Summary: Fixes T2451. Several motivations here, from strongest to weakest: - Currently, getting blame and file content are closely entwined. This makes fixing T9319 more difficult, and I want to fix it. I want to separate blame from content so there's more flexibility in how we approach this issue. - This makes pursuing T2450 easier, if it turns out to be a meaningful win. - If we can get a win on blame performance, we can do `arc blame` eventually if we want. Test Plan: - Blamed in SVN, Git and Mercurial. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2451 Differential Revision: https://secure.phabricator.com/D14957 --- src/__phutil_library_map__.php | 10 +++ .../DiffusionBlameConduitAPIMethod.php | 44 ++++++++++++ .../DiffusionQueryConduitAPIMethod.php | 7 ++ .../query/blame/DiffusionBlameQuery.php | 69 +++++++++++++++++++ .../query/blame/DiffusionGitBlameQuery.php | 34 +++++++++ .../blame/DiffusionMercurialBlameQuery.php | 36 ++++++++++ .../query/blame/DiffusionSvnBlameQuery.php | 36 ++++++++++ 7 files changed, 236 insertions(+) create mode 100644 src/applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php create mode 100644 src/applications/diffusion/query/blame/DiffusionBlameQuery.php create mode 100644 src/applications/diffusion/query/blame/DiffusionGitBlameQuery.php create mode 100644 src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php create mode 100644 src/applications/diffusion/query/blame/DiffusionSvnBlameQuery.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bfba62e253..465108ac7c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -528,6 +528,8 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', + 'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php', + 'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php', 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', @@ -601,6 +603,7 @@ phutil_register_library_map(array( 'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php', 'DiffusionGetLintMessagesConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetLintMessagesConduitAPIMethod.php', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php', + 'DiffusionGitBlameQuery' => 'applications/diffusion/query/blame/DiffusionGitBlameQuery.php', 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', @@ -632,6 +635,7 @@ phutil_register_library_map(array( 'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php', 'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php', 'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php', + 'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php', 'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php', 'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php', 'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php', @@ -743,6 +747,7 @@ phutil_register_library_map(array( 'DiffusionSubversionServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php', 'DiffusionSubversionWireProtocol' => 'applications/diffusion/protocol/DiffusionSubversionWireProtocol.php', 'DiffusionSubversionWireProtocolTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php', + 'DiffusionSvnBlameQuery' => 'applications/diffusion/query/blame/DiffusionSvnBlameQuery.php', 'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php', 'DiffusionSvnRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionSvnRawDiffQuery.php', 'DiffusionSvnRequest' => 'applications/diffusion/request/DiffusionSvnRequest.php', @@ -4493,6 +4498,8 @@ phutil_register_library_map(array( 'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsHeraldAction' => 'HeraldAction', + 'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionBlameQuery' => 'DiffusionQuery', 'DiffusionBlockHeraldAction' => 'HeraldAction', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', @@ -4566,6 +4573,7 @@ phutil_register_library_map(array( 'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetLintMessagesConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'DiffusionConduitAPIMethod', + 'DiffusionGitBlameQuery' => 'DiffusionBlameQuery', 'DiffusionGitBranch' => 'Phobject', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', @@ -4597,6 +4605,7 @@ phutil_register_library_map(array( 'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelQuery' => 'Phobject', 'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery', + 'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery', 'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionMercurialRequest' => 'DiffusionRequest', @@ -4708,6 +4717,7 @@ phutil_register_library_map(array( 'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow', 'DiffusionSubversionWireProtocol' => 'Phobject', 'DiffusionSubversionWireProtocolTestCase' => 'PhabricatorTestCase', + 'DiffusionSvnBlameQuery' => 'DiffusionBlameQuery', 'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionSvnRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionSvnRequest' => 'DiffusionRequest', diff --git a/src/applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php new file mode 100644 index 0000000000..7cbb379039 --- /dev/null +++ b/src/applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php @@ -0,0 +1,44 @@ +'; + } + + protected function defineCustomParamTypes() { + return array( + 'paths' => 'required list', + 'commit' => 'required string', + 'timeout' => 'optional int', + ); + } + + protected function getResult(ConduitAPIRequest $request) { + $drequest = $this->getDiffusionRequest(); + + $paths = $request->getValue('paths'); + + $blame_query = DiffusionBlameQuery::newFromDiffusionRequest($drequest) + ->setPaths($paths); + + $timeout = $request->getValue('timeout'); + if ($timeout) { + $blame_query->setTimeout($timeout); + } + + $blame = $blame_query->execute(); + + return $blame; + } + +} diff --git a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php index 4b3eb6721f..21e8da485f 100644 --- a/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php @@ -110,6 +110,13 @@ abstract class DiffusionQueryConduitAPIMethod 'commit' => $request->getValue('commit'), )); + if (!$drequest) { + throw new Exception( + pht( + 'Repository "%s" is not a valid repository.', + $identifier)); + } + // Figure out whether we're going to handle this request on this device, // or proxy it to another node in the cluster. diff --git a/src/applications/diffusion/query/blame/DiffusionBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php new file mode 100644 index 0000000000..6d83020dd2 --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php @@ -0,0 +1,69 @@ +timeout = $timeout; + return $this; + } + + public function getTimeout() { + return $this->timeout; + } + + public function setPaths(array $paths) { + $this->paths = $paths; + return $this; + } + + public function getPaths() { + return $this->paths; + } + + abstract protected function newBlameFuture(DiffusionRequest $request, $path); + + abstract protected function resolveBlameFuture(ExecFuture $future); + + final public static function newFromDiffusionRequest( + DiffusionRequest $request) { + return parent::newQueryObject(__CLASS__, $request); + } + + final protected function executeQuery() { + $paths = $this->getPaths(); + $request = $this->getRequest(); + $timeout = $this->getTimeout(); + + $futures = array(); + foreach ($paths as $path) { + $future = $this->newBlameFuture($request, $path); + + if ($timeout) { + $future->setTimeout($timeout); + } + + $futures[$path] = $future; + } + + + $blame = array(); + + if ($futures) { + $futures = id(new FutureIterator($futures)) + ->limit(4); + + foreach ($futures as $path => $future) { + $path_blame = $this->resolveBlameFuture($future); + if ($path_blame !== null) { + $blame[$path] = $path_blame; + } + } + } + + return $blame; + } + +} diff --git a/src/applications/diffusion/query/blame/DiffusionGitBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionGitBlameQuery.php new file mode 100644 index 0000000000..f98cc645a7 --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionGitBlameQuery.php @@ -0,0 +1,34 @@ +getRepository(); + + $commit = $request->getCommit(); + + return $repository->getLocalCommandFuture( + '--no-pager blame -s -l %s -- %s', + $commit, + $path); + } + + protected function resolveBlameFuture(ExecFuture $future) { + list($err, $stdout) = $future->resolve(); + + if ($err) { + return null; + } + + $result = array(); + + $lines = phutil_split_lines($stdout); + foreach ($lines as $line) { + list($commit) = explode(' ', $line, 2); + $result[] = $commit; + } + + return $result; + } + +} diff --git a/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php new file mode 100644 index 0000000000..60eeb31c4f --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php @@ -0,0 +1,36 @@ +getRepository(); + $commit = $request->getCommit(); + + // NOTE: We're using "--debug" to make "--changeset" give us the full + // commit hashes. + + return $repository->getLocalCommandFuture( + 'annotate --debug --changeset --rev %s -- %s', + $commit, + $path); + } + + protected function resolveBlameFuture(ExecFuture $future) { + list($err, $stdout) = $future->resolve(); + + if ($err) { + return null; + } + + $result = array(); + + $lines = phutil_split_lines($stdout); + foreach ($lines as $line) { + list($commit) = explode(':', $line, 2); + $result[] = $commit; + } + + return $result; + } + +} diff --git a/src/applications/diffusion/query/blame/DiffusionSvnBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionSvnBlameQuery.php new file mode 100644 index 0000000000..afa99e2bb3 --- /dev/null +++ b/src/applications/diffusion/query/blame/DiffusionSvnBlameQuery.php @@ -0,0 +1,36 @@ +getRepository(); + $commit = $request->getCommit(); + + return $repository->getRemoteCommandFuture( + 'blame --force %s', + $repository->getSubversionPathURI($path, $commit)); + } + + protected function resolveBlameFuture(ExecFuture $future) { + list($err, $stdout) = $future->resolve(); + + if ($err) { + return null; + } + + $result = array(); + $matches = null; + + $lines = phutil_split_lines($stdout); + foreach ($lines as $line) { + if (preg_match('/^\s*(\d+)/', $line, $matches)) { + $result[] = (int)$matches[1]; + } else { + $result[] = null; + } + } + + return $result; + } + +} From 9728c65e935813d706d4f0f71dad79ed042f2827 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 06:15:25 -0800 Subject: [PATCH 63/83] Drive blame generation through `diffusion.blame` Summary: Ref T2450. Ref T9319. This is still a bit messy, but not quite so bad as it was: instead of using a single call to get both blame information and file content, use `diffusion.blame` for blame information. This will make optimizations to both blame and file content easier. Test Plan: Viewed a bunch of blame (color on/off, blame on/off). Reviewers: chad Reviewed By: chad Maniphest Tasks: T2450, T9319 Differential Revision: https://secure.phabricator.com/D14958 --- src/__phutil_library_map__.php | 2 - .../diffusion/DiffusionLintSaveRunner.php | 6 +- ...fusionFileContentQueryConduitAPIMethod.php | 16 +- .../controller/DiffusionBrowseController.php | 206 +++++++++++------- .../DiffusionGitFileContentQueryTestCase.php | 32 --- .../filecontent/DiffusionFileContentQuery.php | 116 +--------- .../DiffusionGitFileContentQuery.php | 39 +--- .../DiffusionMercurialFileContentQuery.php | 58 +---- .../DiffusionSvnFileContentQuery.php | 15 +- .../storage/PhabricatorRepositoryCommit.php | 26 +++ 10 files changed, 183 insertions(+), 333 deletions(-) delete mode 100644 src/applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 465108ac7c..e3175a5440 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -607,7 +607,6 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', - 'DiffusionGitFileContentQueryTestCase' => 'applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php', 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php', 'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php', @@ -4577,7 +4576,6 @@ phutil_register_library_map(array( 'DiffusionGitBranch' => 'Phobject', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', - 'DiffusionGitFileContentQueryTestCase' => 'PhabricatorTestCase', 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionGitRequest' => 'DiffusionRequest', diff --git a/src/applications/diffusion/DiffusionLintSaveRunner.php b/src/applications/diffusion/DiffusionLintSaveRunner.php index b726089fe2..e32392be40 100644 --- a/src/applications/diffusion/DiffusionLintSaveRunner.php +++ b/src/applications/diffusion/DiffusionLintSaveRunner.php @@ -257,8 +257,10 @@ final class DiffusionLintSaveRunner extends Phobject { 'path' => $path, 'commit' => $this->lintCommit, )); - $query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) - ->setNeedsBlame(true); + + // TODO: Restore blame information / generally fix this workflow. + + $query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest); $queries[$path] = $query; $futures[$path] = $query->getFileContentFuture(); } diff --git a/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php index 61987b4d77..73ed4161fe 100644 --- a/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php @@ -19,7 +19,6 @@ final class DiffusionFileContentQueryConduitAPIMethod return array( 'path' => 'required string', 'commit' => 'required string', - 'needsBlame' => 'optional bool', 'timeout' => 'optional int', 'byteLimit' => 'optional int', ); @@ -27,12 +26,9 @@ final class DiffusionFileContentQueryConduitAPIMethod protected function getResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); - $needs_blame = $request->getValue('needsBlame'); - $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( - $drequest); - $file_query - ->setViewer($request->getUser()) - ->setNeedsBlame($needs_blame); + + $file_query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) + ->setViewer($request->getUser()); $timeout = $request->getValue('timeout'); if ($timeout) { @@ -46,11 +42,7 @@ final class DiffusionFileContentQueryConduitAPIMethod $file_content = $file_query->loadFileContent(); - if ($needs_blame) { - list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); - } else { - $text_list = $rev_list = $blame_dict = array(); - } + $text_list = $rev_list = $blame_dict = array(); $file_content ->setBlameDict($blame_dict) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index d512ad035f..033050402b 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -130,7 +130,6 @@ final class DiffusionBrowseController extends DiffusionController { $params = array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), - 'needsBlame' => $needs_blame, ); $byte_limit = null; @@ -572,7 +571,24 @@ final class DiffusionBrowseController extends DiffusionController { $path, $data) { + $viewer = $this->getViewer(); + + $blame_handles = array(); + if ($needs_blame) { + $blame = $this->loadBlame($path, $drequest->getCommit()); + if ($blame) { + $author_phids = mpull($blame, 'getAuthorPHID'); + $blame_handles = $viewer->loadHandles($author_phids); + } + } else { + $blame = array(); + } + + $file_corpus = $file_content->getCorpus(); + if (!$show_color) { + $lines = phutil_split_lines($file_corpus); + $style = 'border: none; width: 100%; height: 80em; font-family: monospace'; if (!$show_blame) { @@ -581,18 +597,24 @@ final class DiffusionBrowseController extends DiffusionController { array( 'style' => $style, ), - $file_content->getCorpus()); + $file_corpus); } else { - $text_list = $file_content->getTextList(); - $rev_list = $file_content->getRevList(); - $blame_dict = $file_content->getBlameDict(); - $rows = array(); - foreach ($text_list as $k => $line) { - $rev = $rev_list[$k]; - $author = $blame_dict[$rev]['author']; - $rows[] = - sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line); + foreach ($lines as $line_number => $line) { + $commit = idx($blame, $line_number); + if ($commit) { + $author = $commit->renderAuthorShortName($blame_handles); + $commit_name = $commit->getShortName(); + } else { + $author = null; + $commit_name = null; + } + + $rows[] = sprintf( + '%-10s %-20s %s', + $commit_name, + $author, + $line); } $corpus = phutil_tag( @@ -600,22 +622,21 @@ final class DiffusionBrowseController extends DiffusionController { array( 'style' => $style, ), - implode("\n", $rows)); + implode('', $rows)); } } else { require_celerity_resource('syntax-highlighting-css'); - $text_list = $file_content->getTextList(); - $rev_list = $file_content->getRevList(); - $blame_dict = $file_content->getBlameDict(); - $text_list = implode("\n", $text_list); - $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( + $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, - $text_list); - $text_list = explode("\n", $text_list); + $file_corpus); + $lines = phutil_split_lines($highlighted); - $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, - $needs_blame, $drequest, $show_blame, $show_color); + $rows = $this->buildDisplayRows( + $lines, + $blame, + $show_blame, + $show_color); $corpus_table = javelin_tag( 'table', @@ -824,25 +845,26 @@ final class DiffusionBrowseController extends DiffusionController { private function buildDisplayRows( - array $text_list, - array $rev_list, - array $blame_dict, - $needs_blame, - DiffusionRequest $drequest, - $show_blame, - $show_color) { + array $lines, + array $blame, + $show_color, + $show_blame) { + + $drequest = $this->getDiffusionRequest(); $handles = array(); - if ($blame_dict) { - $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); + if ($blame) { + $epoch_list = mpull($blame, 'getEpoch', 'getID'); + $epoch_list = array_filter($epoch_list); + $epoch_list = array_unique($epoch_list); + $epoch_list = array_values($epoch_list); + $epoch_min = min($epoch_list); $epoch_max = max($epoch_list); $epoch_range = ($epoch_max - $epoch_min) + 1; - - $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); - $handles = $this->loadViewerHandles($author_phids); } + $line_arr = array(); $line_str = $drequest->getLine(); $ranges = explode(',', $line_str); @@ -864,9 +886,9 @@ final class DiffusionBrowseController extends DiffusionController { $display = array(); $line_number = 1; - $last_rev = null; + $last_commit = null; $color = null; - foreach ($text_list as $k => $line) { + foreach ($lines as $line_index => $line) { $display_line = array( 'epoch' => null, 'commit' => null, @@ -882,17 +904,26 @@ final class DiffusionBrowseController extends DiffusionController { // with same color; otherwise generate blame info. The newer a change // is, the more saturated the color. - $rev = idx($rev_list, $k, $last_rev); + $commit = idx($blame, $line_index, $last_commit); - if ($last_rev == $rev) { + if ($commit && $last_commit && + ($last_commit->getID() == $commit->getID())) { $display_line['color'] = $color; } else { - $blame = $blame_dict[$rev]; - - if (!isset($blame['epoch'])) { - $color = '#ffd'; // Render as warning. + if ($commit) { + $epoch = $commit->getEpoch(); } else { - $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; + $epoch = null; + } + + if (!$epoch) { + if (!$blame) { + $color = '#f6f6f6'; + } else { + $color = '#ffd'; // Render as warning. + } + } else { + $color_ratio = ($epoch - $epoch_min) / $epoch_range; $color_value = 0xE6 * (1.0 - $color_ratio); $color = sprintf( '#%02x%02x%02x', @@ -901,19 +932,16 @@ final class DiffusionBrowseController extends DiffusionController { $color_value); } - $display_line['epoch'] = idx($blame, 'epoch'); + $display_line['epoch'] = $epoch; $display_line['color'] = $color; - $display_line['commit'] = $rev; - $author_phid = idx($blame, 'authorPHID'); - if ($author_phid && $handles[$author_phid]) { - $author_link = $handles[$author_phid]->renderLink(); + if ($commit) { + $display_line['commit'] = $commit; } else { - $author_link = $blame['author']; + $display_line['commit'] = null; } - $display_line['author'] = $author_link; - $last_rev = $rev; + $last_commit = $commit; } } @@ -936,15 +964,7 @@ final class DiffusionBrowseController extends DiffusionController { $request = $this->getRequest(); $viewer = $request->getUser(); - $commits = array_filter(ipull($display, 'commit')); - if ($commits) { - $commits = id(new DiffusionCommitQuery()) - ->setViewer($viewer) - ->withRepository($drequest->getRepository()) - ->withIdentifiers($commits) - ->execute(); - $commits = mpull($commits, null, 'getCommitIdentifier'); - } + $commits = mpull($blame, null, 'getCommitIdentifier'); $revision_ids = id(new DifferentialRevision()) ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); @@ -957,9 +977,9 @@ final class DiffusionBrowseController extends DiffusionController { } $phids = array(); - foreach ($commits as $commit) { - if ($commit->getAuthorPHID()) { - $phids[] = $commit->getAuthorPHID(); + foreach ($commits as $blame_commit) { + if ($blame_commit->getAuthorPHID()) { + $phids[] = $blame_commit->getAuthorPHID(); } } foreach ($revisions as $revision) { @@ -1002,7 +1022,6 @@ final class DiffusionBrowseController extends DiffusionController { $engine); foreach ($display as $line) { - $line_href = $drequest->generateURI( array( 'action' => 'browse', @@ -1023,11 +1042,11 @@ final class DiffusionBrowseController extends DiffusionController { if (idx($line, 'commit')) { $commit = $line['commit']; - if (idx($commits, $commit)) { + if ($commit) { $tooltip = $this->renderCommitTooltip( - $commits[$commit], + $commit, $handles, - $line['author']); + $commit->renderAuthorLink($handles)); } else { $tooltip = null; } @@ -1041,7 +1060,7 @@ final class DiffusionBrowseController extends DiffusionController { 'href' => $drequest->generateURI( array( 'action' => 'commit', - 'commit' => $line['commit'], + 'commit' => $commit->getCommitIdentifier(), )), 'sigil' => 'has-tooltip', 'meta' => array( @@ -1050,14 +1069,11 @@ final class DiffusionBrowseController extends DiffusionController { 'size' => 600, ), ), - id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(9) - ->setTerminator('') - ->truncateString($line['commit'])); + $commit->getShortName()); $revision_id = null; - if (idx($commits, $commit)) { - $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); + if ($commit) { + $revision_id = idx($revision_ids, $commit->getPHID()); } if ($revision_id) { @@ -1207,7 +1223,7 @@ final class DiffusionBrowseController extends DiffusionController { private function renderInlines( array $inlines, - $needs_blame, + $show_blame, $has_coverage, $engine) { @@ -1222,7 +1238,7 @@ final class DiffusionBrowseController extends DiffusionController { ->setInlineComment($inline) ->render(); - $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); + $row = array_fill(0, ($show_blame ? 3 : 1), phutil_tag('th')); $row[] = phutil_tag('td', array(), $inline_view); @@ -1722,4 +1738,44 @@ final class DiffusionBrowseController extends DiffusionController { return $view; } + private function loadBlame($path, $commit) { + $blame = $this->callConduitWithDiffusionRequest( + 'diffusion.blame', + array( + 'commit' => $commit, + 'paths' => array($path), + )); + + $identifiers = idx($blame, $path, array()); + + if ($identifiers) { + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $commits = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withRepository($repository) + ->withIdentifiers($identifiers) + // TODO: We only fetch this to improve author display behavior, but + // shouldn't really need to? + ->needCommitData(true) + ->execute(); + $commits = mpull($commits, null, 'getCommitIdentifier'); + } else { + $commits = array(); + } + + foreach ($identifiers as $key => $identifier) { + $commit = idx($commits, $identifier); + if ($commit) { + $identifiers[$key] = $commit; + } else { + $identifiers[$key] = null; + } + } + + return $identifiers; + } + } diff --git a/src/applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php b/src/applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php deleted file mode 100644 index 1e69ecd5fa..0000000000 --- a/src/applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php +++ /dev/null @@ -1,32 +0,0 @@ -call()'); - $this->assertEqual($result[0], '8220d5d54f6d5d5552a636576cbe9c35f15b65b2'); - $this->assertEqual($result[1], 'Andrew Gallagher'); - $this->assertEqual($result[2], ' $somevar = $this->call()'); - - // User name like 'Jimmy (He) Zhang' - $result = DiffusionGitFileContentQuery::match( - '8220d5d54f6d5d5552a636576cbe9c35f15b65b2 '. - '( Jimmy (He) Zhang 2013-10-11 5) '. - 'code(); "(string literal 9999-99-99 2)"; more_code();'); - $this->assertEqual($result[1], 'Jimmy (He) Zhang'); - $this->assertEqual($result[2], - ' code(); "(string literal 9999-99-99 2)"; more_code();'); - - // User name like 'Scott Shapiro (Ads Product Marketing)' - $result = DiffusionGitFileContentQuery::match( - '8220d5d54f6d5d5552a636576cbe9c35f15b65b2 '. - '( Scott Shapiro (Ads Product Marketing) 2013-10-11 5) '. - 'code(); "(string literal 9999-99-99 2)"; more_code();'); - $this->assertEqual($result[1], 'Scott Shapiro (Ads Product Marketing)'); - $this->assertEqual($result[2], - ' code(); "(string literal 9999-99-99 2)"; more_code();'); - } -} diff --git a/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php index 9664d98f07..6a9cf2e021 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php @@ -8,7 +8,6 @@ */ abstract class DiffusionFileContentQuery extends DiffusionQuery { - private $needsBlame; private $fileContent; private $viewer; private $timeout; @@ -32,6 +31,15 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery { return $this->byteLimit; } + public function setViewer(PhabricatorUser $user) { + $this->viewer = $user; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + final public static function newFromDiffusionRequest( DiffusionRequest $request) { return parent::newQueryObject(__CLASS__, $request); @@ -90,110 +98,4 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery { return $this->fileContent->getCorpus(); } - /** - * Pretty hairy function. If getNeedsBlame is false, this returns - * - * ($text_list, array(), array()) - * - * Where $text_list is the raw file content with trailing new lines stripped. - * - * If getNeedsBlame is true, this returns - * - * ($text_list, $line_rev_dict, $blame_dict) - * - * Where $text_list is just the lines of code -- the raw file content will - * contain lots of blame data, $line_rev_dict is a dictionary of line number - * => revision id, and $blame_dict is another complicated data structure. - * In detail, $blame_dict contains [revision id][author] keys, as well - * as [commit id][authorPhid] and [commit id][epoch] keys. - * - * @return ($text_list, $line_rev_dict, $blame_dict) - */ - final public function getBlameData() { - $raw_data = preg_replace('/\n$/', '', $this->getRawData()); - - $text_list = array(); - $line_rev_dict = array(); - $blame_dict = array(); - - if (!$this->getNeedsBlame()) { - $text_list = explode("\n", $raw_data); - } else if ($raw_data != '') { - $lines = array(); - foreach (explode("\n", $raw_data) as $k => $line) { - $lines[$k] = $this->tokenizeLine($line); - - list($rev_id, $author, $text) = $lines[$k]; - $text_list[$k] = $text; - $line_rev_dict[$k] = $rev_id; - } - - $line_rev_dict = $this->processRevList($line_rev_dict); - - foreach ($lines as $k => $line) { - list($rev_id, $author, $text) = $line; - $rev_id = $line_rev_dict[$k]; - - if (!isset($blame_dict[$rev_id])) { - $blame_dict[$rev_id]['author'] = $author; - } - } - - $repository = $this->getRequest()->getRepository(); - - $commits = id(new DiffusionCommitQuery()) - ->setViewer($this->getViewer()) - ->withDefaultRepository($repository) - ->withIdentifiers(array_unique($line_rev_dict)) - ->execute(); - - foreach ($commits as $commit) { - $blame_dict[$commit->getCommitIdentifier()]['epoch'] = - $commit->getEpoch(); - } - - if ($commits) { - $commits_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( - 'commitID IN (%Ls)', - mpull($commits, 'getID')); - - foreach ($commits_data as $data) { - $author_phid = $data->getCommitDetail('authorPHID'); - if (!$author_phid) { - continue; - } - $commit = $commits[$data->getCommitID()]; - $commit_identifier = $commit->getCommitIdentifier(); - $blame_dict[$commit_identifier]['authorPHID'] = $author_phid; - } - } - - } - - return array($text_list, $line_rev_dict, $blame_dict); - } - - abstract protected function tokenizeLine($line); - - public function setNeedsBlame($needs_blame) { - $this->needsBlame = $needs_blame; - return $this; - } - - public function getNeedsBlame() { - return $this->needsBlame; - } - - public function setViewer(PhabricatorUser $user) { - $this->viewer = $user; - return $this; - } - - public function getViewer() { - return $this->viewer; - } - - protected function processRevList(array $rev_list) { - return $rev_list; - } } diff --git a/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php index 67c6369940..448d2886dc 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php @@ -9,17 +9,10 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { $path = $drequest->getPath(); $commit = $drequest->getCommit(); - if ($this->getNeedsBlame()) { - return $repository->getLocalCommandFuture( - '--no-pager blame -c -l --date=short %s -- %s', - $commit, - $path); - } else { - return $repository->getLocalCommandFuture( - 'cat-file blob %s:%s', - $commit, - $path); - } + return $repository->getLocalCommandFuture( + 'cat-file blob %s:%s', + $commit, + $path); } protected function executeQueryFromFuture(Future $future) { @@ -31,28 +24,4 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { return $file_content; } - protected function tokenizeLine($line) { - return self::match($line); - } - - public static function match($line) { - $m = array(); - // sample lines: - // - // d1b4fcdd2a7c8c0f8cbdd01ca839d992135424dc - // ( hzhao 2009-05-01 202)function print(); - // - // 8220d5d54f6d5d5552a636576cbe9c35f15b65b2 - // (Andrew Gallagher 2010-12-03 324) - // // Add the lines for trailing context - preg_match( - '/^\s*?(\S+?)\s*\(\s*(.*?)\s+\d{4}-\d{2}-\d{2}\s+\d+\)(.*)?$/', - $line, - $m); - $rev_id = $m[1]; - $author = $m[2]; - $text = idx($m, 3); - return array($rev_id, $author, $text); - } - } diff --git a/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php index c97b53e75a..ec49414666 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php @@ -10,19 +10,10 @@ final class DiffusionMercurialFileContentQuery $path = $drequest->getPath(); $commit = $drequest->getCommit(); - if ($this->getNeedsBlame()) { - // NOTE: We're using "--number" instead of "--changeset" because there is - // no way to get "--changeset" to show us the full commit hashes. - return $repository->getLocalCommandFuture( - 'annotate --user --number --rev %s -- %s', - $commit, - $path); - } else { - return $repository->getLocalCommandFuture( - 'cat --rev %s -- %s', - $commit, - $path); - } + return $repository->getLocalCommandFuture( + 'cat --rev %s -- %s', + $commit, + $path); } protected function executeQueryFromFuture(Future $future) { @@ -34,45 +25,4 @@ final class DiffusionMercurialFileContentQuery return $file_content; } - protected function tokenizeLine($line) { - $matches = null; - - preg_match( - '/^(.*?)\s+([0-9]+): (.*)$/', - $line, - $matches); - - return array($matches[2], $matches[1], $matches[3]); - } - - /** - * Convert local revision IDs into full commit identifier hashes. - */ - protected function processRevList(array $rev_list) { - $drequest = $this->getRequest(); - $repository = $drequest->getRepository(); - - $revs = array_unique($rev_list); - foreach ($revs as $key => $rev) { - $revs[$key] = '--rev '.(int)$rev; - } - - list($stdout) = $repository->execxLocalCommand( - 'log --template=%s %C', - '{rev} {node}\\n', - implode(' ', $revs)); - - $map = array(); - foreach (explode("\n", trim($stdout)) as $line) { - list($rev, $node) = explode(' ', $line); - $map[$rev] = $node; - } - - foreach ($rev_list as $k => $rev) { - $rev_list[$k] = $map[$rev]; - } - - return $rev_list; - } - } diff --git a/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php index be7487969b..4976d7a27b 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php @@ -10,8 +10,7 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { $commit = $drequest->getCommit(); return $repository->getRemoteCommandFuture( - '%C %s', - $this->getNeedsBlame() ? 'blame --force' : 'cat', + 'cat %s', $repository->getSubversionPathURI($path, $commit)); } @@ -41,16 +40,4 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { return $file_content; } - protected function tokenizeLine($line) { - // sample line: - // 347498 yliu function print(); - $m = array(); - preg_match('/^\s*(\d+)\s+(\S+)(?: (.*))?$/', $line, $m); - $rev_id = $m[1]; - $author = $m[2]; - $text = idx($m, 3); - - return array($rev_id, $author, $text); - } - } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 987c42492a..d7a2e111e2 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -266,6 +266,32 @@ final class PhabricatorRepositoryCommit return $repository->formatCommitName($identifier); } + public function getShortName() { + $identifier = $this->getCommitIdentifier(); + return substr($identifier, 0, 9); + } + + public function renderAuthorLink($handles) { + $author_phid = $this->getAuthorPHID(); + if ($author_phid && isset($handles[$author_phid])) { + return $handles[$author_phid]->renderLink(); + } + + return $this->renderAuthorShortName($handles); + } + + public function renderAuthorShortName($handles) { + $author_phid = $this->getAuthorPHID(); + if ($author_phid && isset($handles[$author_phid])) { + return $handles[$author_phid]->getName(); + } + + $data = $this->getCommitData(); + $name = $data->getAuthorName(); + + $parsed = new PhutilEmailAddress($name); + return nonempty($parsed->getDisplayName(), $parsed->getAddress()); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ From 91a01e57039eaf9e3313307d6e6dc0863563dad9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 06:41:38 -0800 Subject: [PATCH 64/83] Improve Diffusion browse performance for large files Summary: When looking at a large file in Diffusion: - disable highlighting if it's huge and show a note about why; - pick up a few other optimizations. Test Plan: Locally, this improves the main render of `__phutil_library_map__.php` from 3,200ms to 600ms for me, at the cost of syntax highlighting (we can eventually add view options and let users re-enable it). Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14959 --- .../controller/DiffusionBrowseController.php | 84 ++++++++++++------- src/view/form/PHUIInfoView.php | 1 - 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 033050402b..cb14b62b75 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -585,6 +585,8 @@ final class DiffusionBrowseController extends DiffusionController { } $file_corpus = $file_content->getCorpus(); + $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; + $can_highlight = (strlen($file_corpus) <= $highlight_limit); if (!$show_color) { $lines = phutil_split_lines($file_corpus); @@ -625,12 +627,16 @@ final class DiffusionBrowseController extends DiffusionController { implode('', $rows)); } } else { - require_celerity_resource('syntax-highlighting-css'); + if ($can_highlight) { + require_celerity_resource('syntax-highlighting-css'); - $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( - $path, - $file_corpus); - $lines = phutil_split_lines($highlighted); + $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( + $path, + $file_corpus); + $lines = phutil_split_lines($highlighted); + } else { + $lines = phutil_split_lines($file_corpus); + } $rows = $this->buildDisplayRows( $lines, @@ -706,6 +712,18 @@ final class DiffusionBrowseController extends DiffusionController { ->appendChild($corpus) ->setCollapsed(true); + if (!$can_highlight) { + $message = pht( + 'This file is larger than %s, so syntax highlighting is disabled '. + 'by default.', + phutil_format_bytes($highlight_limit)); + + $corpus->setInfoView( + id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors(array($message))); + } + return $corpus; } @@ -851,6 +869,7 @@ final class DiffusionBrowseController extends DiffusionController { $show_blame) { $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); $handles = array(); if ($blame) { @@ -989,8 +1008,6 @@ final class DiffusionBrowseController extends DiffusionController { } $handles = $this->loadViewerHandles($phids); - Javelin::initBehavior('phabricator-oncopy', array()); - $engine = null; $inlines = array(); if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { @@ -1021,13 +1038,21 @@ final class DiffusionBrowseController extends DiffusionController { (bool)$this->coverage, $engine); + // NOTE: We're doing this manually because rendering is otherwise + // dominated by URI generation for very large files. + $line_base = (string)$repository->generateURI( + array( + 'action' => 'browse', + 'stable' => true, + )); + + require_celerity_resource('aphront-tooltip-css'); + Javelin::initBehavior('phabricator-oncopy'); + Javelin::initBehavior('phabricator-tooltips'); + Javelin::initBehavior('phabricator-line-linker'); + foreach ($display as $line) { - $line_href = $drequest->generateURI( - array( - 'action' => 'browse', - 'line' => $line['line'], - 'stable' => true, - )); + $line_href = $line_base.'$'.$line['line']; $blame = array(); $style = null; @@ -1051,9 +1076,6 @@ final class DiffusionBrowseController extends DiffusionController { $tooltip = null; } - Javelin::initBehavior('phabricator-tooltips', array()); - require_celerity_resource('aphront-tooltip-css'); - $commit_link = javelin_tag( 'a', array( @@ -1095,19 +1117,23 @@ final class DiffusionBrowseController extends DiffusionController { } } - $uri = $line_href->alter('before', $commit); - $before_link = javelin_tag( - 'a', - array( - 'href' => $uri->setQueryParam('view', 'blame'), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => pht('Skip Past This Commit'), - 'align' => 'E', - 'size' => 300, + if ($commit) { + $identifier = $commit->getCommitIdentifier(); + $skip_href = $line_href.'?before='.$identifier.'&view=blame'; + + $before_link = javelin_tag( + 'a', + array( + 'href' => $skip_href, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => pht('Skip Past This Commit'), + 'align' => 'E', + 'size' => 300, + ), ), - ), - "\xC2\xAB"); + "\xC2\xAB"); + } } $blame[] = phutil_tag( @@ -1149,8 +1175,6 @@ final class DiffusionBrowseController extends DiffusionController { ), $line_link); - Javelin::initBehavior('phabricator-line-linker'); - if ($line['target']) { Javelin::initBehavior( 'diffusion-jump-to', diff --git a/src/view/form/PHUIInfoView.php b/src/view/form/PHUIInfoView.php index 3a8f259f52..0da471574c 100644 --- a/src/view/form/PHUIInfoView.php +++ b/src/view/form/PHUIInfoView.php @@ -41,7 +41,6 @@ final class PHUIInfoView extends AphrontView { } public function addButton(PHUIButtonView $button) { - $this->buttons[] = $button; return $this; } From ab27af9fc6085f7f719f30346003fee83f13252b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 6 Jan 2016 21:13:52 +0000 Subject: [PATCH 65/83] Fix badges edit form Summary: Make sure to subclass the right controller on badges. Test Plan: arc liberate, make a custom badges edit form. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14961 --- src/__phutil_library_map__.php | 2 +- .../badges/controller/PhabricatorBadgesEditController.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e3175a5440..0f3ecb4d42 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5954,7 +5954,7 @@ phutil_register_library_map(array( 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability', 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', - 'PhabricatorBadgesEditController' => 'PhabricatorPasteController', + 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditEngine' => 'PhabricatorEditEngine', 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/badges/controller/PhabricatorBadgesEditController.php b/src/applications/badges/controller/PhabricatorBadgesEditController.php index aef92a983e..3ac1aebe58 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditController.php @@ -1,6 +1,7 @@ Date: Wed, 6 Jan 2016 03:57:57 -0800 Subject: [PATCH 66/83] Improve Diffusion behavior for directories with impressive numbers of files Summary: Fixes T4366. Two years ago, Facebook put 16,000 files in a directory. Today, the page has nearly loaded. Paginate large directories. Test Plan: - Viewed home and browse views in Git, Mercurial and Subversion. I put an artificially small page size (5) on home: {F1055653} I pushed 16,000 files to a directory and paged through them. Here's the last page, which rendered in about 200ms: {F1055655} Our behavior is a bit better than GitHub here, which shows only the first 1,000 files, disables pagination, and can't retrieve history for the files: {F1055656} Reviewers: chad Reviewed By: chad Maniphest Tasks: T4366 Differential Revision: https://secure.phabricator.com/D14956 --- .../DiffusionBrowseQueryConduitAPIMethod.php | 44 ++++++++++++++++--- .../controller/DiffusionBrowseController.php | 25 +++++++++-- .../controller/DiffusionController.php | 2 +- .../DiffusionRepositoryController.php | 26 +++++++++-- .../storage/PhabricatorRepository.php | 3 +- 5 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php index e2d3a941bf..f32583708c 100644 --- a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php @@ -22,6 +22,8 @@ final class DiffusionBrowseQueryConduitAPIMethod 'path' => 'optional string', 'commit' => 'optional string', 'needValidityOnly' => 'optional bool', + 'limit' => 'optional int', + 'offset' => 'optional int', ); } @@ -35,6 +37,8 @@ final class DiffusionBrowseQueryConduitAPIMethod $repository = $drequest->getRepository(); $path = $request->getValue('path'); $commit = $request->getValue('commit'); + $offset = (int)$request->getValue('offset'); + $limit = (int)$request->getValue('limit'); $result = $this->getEmptyResultSet(); if ($path == '') { @@ -99,6 +103,7 @@ final class DiffusionBrowseQueryConduitAPIMethod $prefix = ''; } + $count = 0; $results = array(); foreach (explode("\0", rtrim($stdout)) as $line) { // NOTE: Limit to 5 components so we parse filenames with spaces in them @@ -140,7 +145,15 @@ final class DiffusionBrowseQueryConduitAPIMethod $path_result->setFileType($file_type); $path_result->setFileSize($size); - $results[] = $path_result; + if ($count >= $offset) { + $results[] = $path_result; + } + + $count++; + + if ($limit && $count >= ($offset + $limit)) { + break; + } } // If we identified submodules, lookup the module info at this commit to @@ -196,6 +209,8 @@ final class DiffusionBrowseQueryConduitAPIMethod $repository = $drequest->getRepository(); $path = $request->getValue('path'); $commit = $request->getValue('commit'); + $offset = (int)$request->getValue('offset'); + $limit = (int)$request->getValue('limit'); $result = $this->getEmptyResultSet(); @@ -215,6 +230,7 @@ final class DiffusionBrowseQueryConduitAPIMethod // but ours do. $trim_len = $match_len ? $match_len + 1 : 0; + $count = 0; foreach ($entire_manifest as $path) { if (strncmp($path, $match_against, $match_len)) { continue; @@ -236,7 +252,16 @@ final class DiffusionBrowseQueryConduitAPIMethod } else { $type = DifferentialChangeType::FILE_DIRECTORY; } - $results[reset($parts)] = $type; + + if ($count >= $offset) { + $results[reset($parts)] = $type; + } + + $count++; + + if ($limit && ($count >= ($offset + $limit))) { + break; + } } foreach ($results as $key => $type) { @@ -266,6 +291,8 @@ final class DiffusionBrowseQueryConduitAPIMethod $repository = $drequest->getRepository(); $path = $request->getValue('path'); $commit = $request->getValue('commit'); + $offset = (int)$request->getValue('offset'); + $limit = (int)$request->getValue('limit'); $result = $this->getEmptyResultSet(); $subpath = $repository->getDetail('svn-subpath'); @@ -422,6 +449,7 @@ final class DiffusionBrowseQueryConduitAPIMethod $path_normal = DiffusionPathIDQuery::normalizePath($path); $results = array(); + $count = 0; foreach ($browse as $file) { $full_path = $file['pathName']; @@ -431,9 +459,7 @@ final class DiffusionBrowseQueryConduitAPIMethod $result_path = new DiffusionRepositoryPath(); $result_path->setPath($file_path); $result_path->setFullPath($full_path); -// $result_path->setHash($hash); $result_path->setFileType($file['fileType']); -// $result_path->setFileSize($size); if (!empty($file['hasCommit'])) { $commit = idx($commits, $file['svnCommit']); @@ -444,7 +470,15 @@ final class DiffusionBrowseQueryConduitAPIMethod } } - $results[] = $result_path; + if ($count >= $offset) { + $results[] = $result_path; + } + + $count++; + + if ($limit && ($count >= ($offset + $limit))) { + break; + } } if (empty($results)) { diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index cb14b62b75..fb18a18769 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -27,20 +27,30 @@ final class DiffusionBrowseController extends DiffusionController { return $this->browseSearch(); } + $pager = id(new PHUIPagerView()) + ->readFromRequest($request); + $results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( 'path' => $drequest->getPath(), 'commit' => $drequest->getStableCommit(), + 'offset' => $pager->getOffset(), + 'limit' => $pager->getPageSize() + 1, ))); + $reason = $results->getReasonForEmptyResultSet(); $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); if ($is_file) { return $this->browseFile($results); } else { - return $this->browseDirectory($results); + $paths = $results->getPaths(); + $paths = $pager->sliceResults($paths); + $results->setPaths($paths); + + return $this->browseDirectory($results, $pager); } } @@ -262,7 +272,10 @@ final class DiffusionBrowseController extends DiffusionController { ->appendChild($content); } - public function browseDirectory(DiffusionBrowseResultSet $results) { + public function browseDirectory( + DiffusionBrowseResultSet $results, + PHUIPagerView $pager) { + $request = $this->getRequest(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); @@ -339,6 +352,8 @@ final class DiffusionBrowseController extends DiffusionController { 'view' => 'browse', )); + $pager_box = $this->renderTablePagerBox($pager); + return $this->newPage() ->setTitle( array( @@ -346,7 +361,11 @@ final class DiffusionBrowseController extends DiffusionController { $repository->getDisplayName(), )) ->setCrumbs($crumbs) - ->appendChild($content); + ->appendChild( + array( + $content, + $pager_box, + )); } private function renderSearchResults() { diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index 80364fd2ef..e398a88114 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -110,7 +110,7 @@ abstract class DiffusionController extends PhabricatorController { $crumb_list = array(); // On the home page, we don't have a DiffusionRequest. - if ($this->diffusionRequest) { + if ($this->hasDiffusionRequest()) { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); } else { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 5d65270c7c..dc6969b81d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -88,6 +88,7 @@ final class DiffusionRepositoryController extends DiffusionController { private function buildNormalContent(DiffusionRequest $drequest) { + $request = $this->getRequest(); $repository = $drequest->getRepository(); $phids = array(); @@ -123,6 +124,9 @@ final class DiffusionRepositoryController extends DiffusionController { $history_exception = $ex; } + $browse_pager = id(new PHUIPagerView()) + ->readFromRequest($request); + try { $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( @@ -130,8 +134,10 @@ final class DiffusionRepositoryController extends DiffusionController { array( 'path' => $drequest->getPath(), 'commit' => $drequest->getCommit(), + 'limit' => $browse_pager->getPageSize() + 1, ))); $browse_paths = $browse_results->getPaths(); + $browse_paths = $browse_pager->sliceResults($browse_paths); foreach ($browse_paths as $item) { $data = $item->getLastCommitData(); @@ -178,7 +184,8 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_results, $browse_paths, $browse_exception, - $handles); + $handles, + $browse_pager); $content[] = $this->buildHistoryTable( $history_results, @@ -588,7 +595,8 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_results, $browse_paths, $browse_exception, - array $handles) { + array $handles, + PHUIPagerView $pager) { require_celerity_resource('diffusion-icons-css'); @@ -669,7 +677,19 @@ final class DiffusionRepositoryController extends DiffusionController { $browse_panel->setTable($browse_table); - return array($locate_panel, $browse_panel); + $pager->setURI($browse_uri, 'offset'); + + if ($pager->willShowPagingControls()) { + $pager_box = $this->renderTablePagerBox($pager); + } else { + $pager_box = null; + } + + return array( + $locate_panel, + $browse_panel, + $pager_box, + ); } private function renderCloneCommand( diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index c9455d38f8..e34c4980c0 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -627,7 +627,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO case 'refs': break; case 'branch': - $req_branch = true; + // NOTE: This does not actually require a branch, and won't have one + // in Subversion. Possibly this should be more clear. break; case 'commit': case 'rendering-ref': From 0759b84d77c079374a3d24afc3b65b66554384ee Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 15:34:05 -0800 Subject: [PATCH 67/83] Improve construction of commit queries from blame lookups Summary: Ref T2450. File blame tends to have the same commit a lot of times, and we don't do lookups like this efficiently right now. In particular, for a file like `__phutil_library_map__.php`, we would issue a query with ~9,000 clauses like this: ``` (repositoryID = 1 AND commitIdentifier LIKE "XYZ%") ``` ...but only a few hundred of those identifiers were unique. Instead, issue only one clause per unique identifier. MySQL also seems to do a little better on "commitIdentifier = X" if we have the full hash, so special case that slightly. Test Plan: - Issuing a query for only unique identifiers dropped the cost from 400ms to 100ms locally. - Swapping to `=` if we have the full hash dropped the cost from 100ms to 75ms locally. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2450 Differential Revision: https://secure.phabricator.com/D14962 --- .../diffusion/query/DiffusionCommitQuery.php | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index e8f499955a..ec76468332 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -51,6 +51,11 @@ final class DiffusionCommitQuery * they queried for. */ public function withIdentifiers(array $identifiers) { + // Some workflows (like blame lookups) can pass in large numbers of + // duplicate identifiers. We only care about unique identifiers, so + // get rid of duplicates immediately. + $identifiers = array_fuse($identifiers); + $this->identifiers = $identifiers; return $this; } @@ -185,7 +190,7 @@ final class DiffusionCommitQuery // Build the identifierMap if ($this->identifiers !== null) { - $ids = array_fuse($this->identifiers); + $ids = $this->identifiers; $prefixes = array( 'r'.$commit->getRepository()->getCallsign(), 'r'.$commit->getRepository()->getCallsign().':', @@ -395,7 +400,6 @@ final class DiffusionCommitQuery $repos->execute(); $repos = $repos->getIdentifierMap(); - foreach ($refs as $key => $ref) { $repo = idx($repos, $ref['callsign']); @@ -404,7 +408,7 @@ final class DiffusionCommitQuery } if ($repo->isSVN()) { - if (!ctype_digit($ref['identifier'])) { + if (!ctype_digit((string)$ref['identifier'])) { continue; } $sql[] = qsprintf( @@ -419,11 +423,25 @@ final class DiffusionCommitQuery if (strlen($ref['identifier']) < $min_qualified) { continue; } - $sql[] = qsprintf( - $conn, - '(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', - $repo->getID(), - $ref['identifier']); + + $identifier = $ref['identifier']; + if (strlen($identifier) == 40) { + // MySQL seems to do slightly better with this version if the + // clause, so issue it if we have a full commit hash. + $sql[] = qsprintf( + $conn, + '(commit.repositoryID = %d + AND commit.commitIdentifier = %s)', + $repo->getID(), + $identifier); + } else { + $sql[] = qsprintf( + $conn, + '(commit.repositoryID = %d + AND commit.commitIdentifier LIKE %>)', + $repo->getID(), + $identifier); + } } } } From e8d3071452f0555053dc1d9e004e345c9bcf5654 Mon Sep 17 00:00:00 2001 From: Fabian Stelzer Date: Wed, 6 Jan 2016 04:23:28 -0800 Subject: [PATCH 68/83] Implement a git blame cache Summary: Ref T2450. Ref T2453. Add a repository_blamecache table and cache git blame information Test Plan: View files in Diffusion with enabled blame Reviewers: fabe, chad, #blessed_reviewers Reviewed By: chad, #blessed_reviewers Subscribers: joshuaspence, epriestley Maniphest Tasks: T2453, T2450 Differential Revision: https://secure.phabricator.com/D10600 --- .../query/blame/DiffusionBlameQuery.php | 133 ++++++++++++++++-- .../query/PhabricatorRepositoryQuery.php | 2 + 2 files changed, 124 insertions(+), 11 deletions(-) diff --git a/src/applications/diffusion/query/blame/DiffusionBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php index 6d83020dd2..4d03b4a143 100644 --- a/src/applications/diffusion/query/blame/DiffusionBlameQuery.php +++ b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php @@ -34,9 +34,35 @@ abstract class DiffusionBlameQuery extends DiffusionQuery { final protected function executeQuery() { $paths = $this->getPaths(); + + $blame = array(); + + // Load cache keys: these are the commits at which each path was last + // touched. + $keys = $this->loadCacheKeys($paths); + + // Try to read blame data from cache. + $cache = $this->readCacheData($keys); + foreach ($paths as $key => $path) { + if (!isset($cache[$path])) { + continue; + } + + $blame[$path] = $cache[$path]; + unset($paths[$key]); + } + + // If we have no paths left, we filled everything from cache and can + // bail out early. + if (!$paths) { + return $blame; + } + $request = $this->getRequest(); $timeout = $this->getTimeout(); + // We're still missing at least some data, so we need to run VCS commands + // to pull it. $futures = array(); foreach ($paths as $path) { $future = $this->newBlameFuture($request, $path); @@ -48,22 +74,107 @@ abstract class DiffusionBlameQuery extends DiffusionQuery { $futures[$path] = $future; } + $futures = id(new FutureIterator($futures)) + ->limit(4); - $blame = array(); - - if ($futures) { - $futures = id(new FutureIterator($futures)) - ->limit(4); - - foreach ($futures as $path => $future) { - $path_blame = $this->resolveBlameFuture($future); - if ($path_blame !== null) { - $blame[$path] = $path_blame; - } + foreach ($futures as $path => $future) { + $path_blame = $this->resolveBlameFuture($future); + if ($path_blame !== null) { + $blame[$path] = $path_blame; } } + // Fill the cache with anything we generated. + $this->writeCacheData( + array_select_keys($keys, $paths), + $blame); + return $blame; } + private function loadCacheKeys(array $paths) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $repository = $request->getRepository(); + $repository_id = $repository->getID(); + + $last_modified = parent::callConduitWithDiffusionRequest( + $viewer, + $request, + 'diffusion.lastmodifiedquery', + array( + 'paths' => array_fill_keys($paths, $request->getCommit()), + )); + + $map = array(); + foreach ($paths as $path) { + $identifier = idx($last_modified, $path); + if ($identifier === null) { + continue; + } + + $map[$path] = "blame({$repository_id}, {$identifier}, {$path}, raw)"; + } + + return $map; + } + + private function readCacheData(array $keys) { + $cache = PhabricatorCaches::getImmutableCache(); + $data = $cache->getKeys($keys); + + $results = array(); + foreach ($keys as $path => $key) { + if (!isset($data[$key])) { + continue; + } + $results[$path] = $data[$key]; + } + + // Decode the cache storage format. + foreach ($results as $path => $cache) { + list($head, $body) = explode("\n", $cache, 2); + switch ($head) { + case 'raw': + $body = explode("\n", $body); + break; + default: + $body = null; + break; + } + + if ($body === null) { + unset($results[$path]); + } else { + $results[$path] = $body; + } + } + + return $results; + } + + private function writeCacheData(array $keys, array $blame) { + $writes = array(); + foreach ($keys as $path => $key) { + $value = idx($blame, $path); + if ($value === null) { + continue; + } + + // For now, just store the entire value with a "raw" header. In the + // future, we could compress this or use IDs instead. + $value = "raw\n".implode("\n", $value); + + $writes[$key] = $value; + } + + if (!$writes) { + return; + } + + $cache = PhabricatorCaches::getImmutableCache(); + $data = $cache->setKeys($writes, phutil_units('14 days in seconds')); + } + } diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 38fa5bf56c..bd9765130a 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -49,6 +49,8 @@ final class PhabricatorRepositoryQuery } public function withIdentifiers(array $identifiers) { + $identifiers = array_fuse($identifiers); + $ids = array(); $callsigns = array(); $phids = array(); From 9ab1b5a22db6f76f15c4060fd1dd6163a3e3d2aa Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 17:59:06 -0800 Subject: [PATCH 69/83] Make mundane performance improvements to Diffusion browse views Summary: Ref T2450. This reorganizes code to improve performance. Mostly, there are a lot of things which are unique per commit (author name, links, short name, etc), but we were rendering them for every line. This often meant we'd render the same author's name thousands of times. This is slower than rendering it only once. In 99% of interfaces this doesn't matter, but blame is weird and it's significant on big files. Test Plan: Locally, `__phutil_library_map__.php` now has costs of roughly: - 550ms for main content (from 650ms before the patch). - 1,500ms for blame content (frrom 1,800ms before the patch). So this isn't huge, is a decent ~20%-ish performance gain for shuffling some stuff around. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2450 Differential Revision: https://secure.phabricator.com/D14963 --- .../controller/DiffusionBrowseController.php | 589 ++++++++++-------- 1 file changed, 317 insertions(+), 272 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index fb18a18769..ae921cd664 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -592,15 +592,12 @@ final class DiffusionBrowseController extends DiffusionController { $viewer = $this->getViewer(); - $blame_handles = array(); if ($needs_blame) { $blame = $this->loadBlame($path, $drequest->getCommit()); - if ($blame) { - $author_phids = mpull($blame, 'getAuthorPHID'); - $blame_handles = $viewer->loadHandles($author_phids); - } + list($blame_list, $blame_commits) = $blame; } else { - $blame = array(); + $blame_list = array(); + $blame_commits = array(); } $file_corpus = $file_content->getCorpus(); @@ -608,43 +605,11 @@ final class DiffusionBrowseController extends DiffusionController { $can_highlight = (strlen($file_corpus) <= $highlight_limit); if (!$show_color) { - $lines = phutil_split_lines($file_corpus); - - $style = - 'border: none; width: 100%; height: 80em; font-family: monospace'; - if (!$show_blame) { - $corpus = phutil_tag( - 'textarea', - array( - 'style' => $style, - ), - $file_corpus); - } else { - $rows = array(); - foreach ($lines as $line_number => $line) { - $commit = idx($blame, $line_number); - if ($commit) { - $author = $commit->renderAuthorShortName($blame_handles); - $commit_name = $commit->getShortName(); - } else { - $author = null; - $commit_name = null; - } - - $rows[] = sprintf( - '%-10s %-20s %s', - $commit_name, - $author, - $line); - } - - $corpus = phutil_tag( - 'textarea', - array( - 'style' => $style, - ), - implode('', $rows)); - } + $corpus = $this->renderPlaintextCorpus( + $file_corpus, + $blame_list, + $blame_commits, + $show_blame); } else { if ($can_highlight) { require_celerity_resource('syntax-highlighting-css'); @@ -659,7 +624,8 @@ final class DiffusionBrowseController extends DiffusionController { $rows = $this->buildDisplayRows( $lines, - $blame, + $blame_list, + $blame_commits, $show_blame, $show_color); @@ -883,25 +849,116 @@ final class DiffusionBrowseController extends DiffusionController { private function buildDisplayRows( array $lines, - array $blame, + array $blame_list, + array $blame_commits, $show_color, $show_blame) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); - $handles = array(); - if ($blame) { - $epoch_list = mpull($blame, 'getEpoch', 'getID'); - $epoch_list = array_filter($epoch_list); + $revision_map = array(); + $revisions = array(); + if ($blame_commits) { + $commit_map = mpull($blame_commits, 'getCommitIdentifier', 'getPHID'); + + $revision_ids = id(new DifferentialRevision()) + ->loadIDsByCommitPHIDs(array_keys($commit_map)); + if ($revision_ids) { + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withIDs($revision_ids) + ->execute(); + $revisions = mpull($revisions, null, 'getID'); + } + + foreach ($revision_ids as $commit_phid => $revision_id) { + $revision_map[$commit_map[$commit_phid]] = $revision_id; + } + } + + $phids = array(); + foreach ($blame_commits as $commit) { + $author_phid = $commit->getAuthorPHID(); + if ($author_phid === null) { + continue; + } + $phids[$author_phid] = $author_phid; + } + + foreach ($revisions as $revision) { + $author_phid = $revision->getAuthorPHID(); + if ($author_phid === null) { + continue; + } + $phids[$author_phid] = $author_phid; + } + + $handles = $viewer->loadHandles($phids); + + $colors = array(); + if ($blame_commits) { + $epochs = array(); + + foreach ($blame_commits as $identifier => $commit) { + $epochs[$identifier] = $commit->getEpoch(); + } + + $epoch_list = array_filter($epochs); $epoch_list = array_unique($epoch_list); $epoch_list = array_values($epoch_list); $epoch_min = min($epoch_list); $epoch_max = max($epoch_list); $epoch_range = ($epoch_max - $epoch_min) + 1; + + foreach ($blame_commits as $identifier => $commit) { + $epoch = $epochs[$identifier]; + if (!$epoch) { + $color = '#ffffdd'; // Warning color, missing data. + } else { + $color_ratio = ($epoch - $epoch_min) / $epoch_range; + $color_value = 0xE6 * (1.0 - $color_ratio); + $color = sprintf( + '#%02x%02x%02x', + $color_value, + 0xF6, + $color_value); + } + + $colors[$identifier] = $color; + } } + $display = array(); + $last_identifier = null; + $last_color = null; + foreach ($lines as $line_index => $line) { + $color = '#f6f6f6'; + $duplicate = false; + if (isset($blame_list[$line_index])) { + $identifier = $blame_list[$line_index]; + if (isset($colors[$identifier])) { + $color = $colors[$identifier]; + } + + if ($identifier === $last_identifier) { + $duplicate = true; + } else { + $last_identifier = $identifier; + } + } + + $display[$line_index] = array( + 'data' => $line, + 'target' => false, + 'highlighted' => false, + 'color' => $color, + 'duplicate' => $duplicate, + ); + } $line_arr = array(); $line_str = $drequest->getLine(); @@ -921,112 +978,23 @@ final class DiffusionBrowseController extends DiffusionController { } } - $display = array(); + // Mark the first highlighted line as the target line. + if ($line_arr) { + $target_line = $line_arr[0]['min']; + if (isset($display[$target_line - 1])) { + $display[$target_line - 1]['target'] = true; + } + } - $line_number = 1; - $last_commit = null; - $color = null; - foreach ($lines as $line_index => $line) { - $display_line = array( - 'epoch' => null, - 'commit' => null, - 'author' => null, - 'target' => null, - 'highlighted' => null, - 'line' => $line_number, - 'data' => $line, - ); - - if ($show_blame) { - // If the line's rev is same as the line above, show empty content - // with same color; otherwise generate blame info. The newer a change - // is, the more saturated the color. - - $commit = idx($blame, $line_index, $last_commit); - - if ($commit && $last_commit && - ($last_commit->getID() == $commit->getID())) { - $display_line['color'] = $color; - } else { - if ($commit) { - $epoch = $commit->getEpoch(); - } else { - $epoch = null; - } - - if (!$epoch) { - if (!$blame) { - $color = '#f6f6f6'; - } else { - $color = '#ffd'; // Render as warning. - } - } else { - $color_ratio = ($epoch - $epoch_min) / $epoch_range; - $color_value = 0xE6 * (1.0 - $color_ratio); - $color = sprintf( - '#%02x%02x%02x', - $color_value, - 0xF6, - $color_value); - } - - $display_line['epoch'] = $epoch; - $display_line['color'] = $color; - - if ($commit) { - $display_line['commit'] = $commit; - } else { - $display_line['commit'] = null; - } - - $last_commit = $commit; + // Mark all other highlighted lines as highlighted. + foreach ($line_arr as $range) { + for ($ii = $range['min']; $ii <= $range['max']; $ii++) { + if (isset($display[$ii - 1])) { + $display[$ii - 1]['highlighted'] = true; } } - - if ($line_arr) { - if ($line_number == $line_arr[0]['min']) { - $display_line['target'] = true; - } - foreach ($line_arr as $range) { - if ($line_number >= $range['min'] && - $line_number <= $range['max']) { - $display_line['highlighted'] = true; - } - } - } - - $display[] = $display_line; - ++$line_number; } - $request = $this->getRequest(); - $viewer = $request->getUser(); - - $commits = mpull($blame, null, 'getCommitIdentifier'); - - $revision_ids = id(new DifferentialRevision()) - ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); - $revisions = array(); - if ($revision_ids) { - $revisions = id(new DifferentialRevisionQuery()) - ->setViewer($viewer) - ->withIDs($revision_ids) - ->execute(); - } - - $phids = array(); - foreach ($commits as $blame_commit) { - if ($blame_commit->getAuthorPHID()) { - $phids[] = $blame_commit->getAuthorPHID(); - } - } - foreach ($revisions as $revision) { - if ($revision->getAuthorPHID()) { - $phids[] = $revision->getAuthorPHID(); - } - } - $handles = $this->loadViewerHandles($phids); - $engine = null; $inlines = array(); if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { @@ -1059,7 +1027,7 @@ final class DiffusionBrowseController extends DiffusionController { // NOTE: We're doing this manually because rendering is otherwise // dominated by URI generation for very large files. - $line_base = (string)$repository->generateURI( + $line_base = (string)$drequest->generateURI( array( 'action' => 'browse', 'stable' => true, @@ -1070,122 +1038,87 @@ final class DiffusionBrowseController extends DiffusionController { Javelin::initBehavior('phabricator-tooltips'); Javelin::initBehavior('phabricator-line-linker'); - foreach ($display as $line) { - $line_href = $line_base.'$'.$line['line']; + // Render these once, since they tend to get repeated many times in large + // blame outputs. + $commit_links = $this->renderCommitLinks($blame_commits, $handles); + $revision_links = $this->renderRevisionLinks($revisions, $handles); - $blame = array(); - $style = null; - if (array_key_exists('color', $line)) { - if ($line['color']) { - $style = 'background: '.$line['color'].';'; - } + $skip_text = pht('Skip Past This Commit'); + foreach ($display as $line_index => $line) { + $row = array(); - $before_link = null; - $commit_link = null; - $revision_link = null; - if (idx($line, 'commit')) { - $commit = $line['commit']; + $line_number = $line_index + 1; + $line_href = $line_base.'$'.$line_number; - if ($commit) { - $tooltip = $this->renderCommitTooltip( - $commit, - $handles, - $commit->renderAuthorLink($handles)); - } else { - $tooltip = null; - } - - $commit_link = javelin_tag( - 'a', - array( - 'href' => $drequest->generateURI( - array( - 'action' => 'commit', - 'commit' => $commit->getCommitIdentifier(), - )), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - $commit->getShortName()); - - $revision_id = null; - if ($commit) { - $revision_id = idx($revision_ids, $commit->getPHID()); - } - - if ($revision_id) { - $revision = idx($revisions, $revision_id); - if ($revision) { - $tooltip = $this->renderRevisionTooltip($revision, $handles); - $revision_link = javelin_tag( - 'a', - array( - 'href' => '/D'.$revision->getID(), - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $tooltip, - 'align' => 'E', - 'size' => 600, - ), - ), - 'D'.$revision->getID()); - } - } - - if ($commit) { - $identifier = $commit->getCommitIdentifier(); - $skip_href = $line_href.'?before='.$identifier.'&view=blame'; - - $before_link = javelin_tag( - 'a', - array( - 'href' => $skip_href, - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => pht('Skip Past This Commit'), - 'align' => 'E', - 'size' => 300, - ), - ), - "\xC2\xAB"); - } - } - - $blame[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-link', - ), - $before_link); - - $object_links = array(); - $object_links[] = $commit_link; - if ($revision_link) { - $object_links[] = phutil_tag('span', array(), '/'); - $object_links[] = $revision_link; - } - - $blame[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-rev-link', - ), - $object_links); + if (isset($blame_list[$line_index])) { + $identifier = $blame_list[$line_index]; + } else { + $identifier = null; } + $revision_link = null; + $commit_link = null; + $before_link = null; + $style = null; + if ($identifier && !$line['duplicate']) { + $style = 'background: '.$line['color'].';'; + + if (isset($commit_links[$identifier])) { + $commit_link = $commit_links[$identifier]; + } + + if (isset($revision_map[$identifier])) { + $revision_id = $revision_map[$identifier]; + if (isset($revision_links[$revision_id])) { + $revision_link = $revision_links[$revision_id]; + } + } + + $skip_href = $line_href.'?before='.$identifier.'&view=blame'; + $before_link = javelin_tag( + 'a', + array( + 'href' => $skip_href, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $skip_text, + 'align' => 'E', + 'size' => 300, + ), + ), + "\xC2\xAB"); + } + + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-blame-link', + ), + $before_link); + + $object_links = array(); + $object_links[] = $commit_link; + if ($revision_link) { + $object_links[] = phutil_tag('span', array(), '/'); + $object_links[] = $revision_link; + } + + $row[] = phutil_tag( + 'th', + array( + 'class' => 'diffusion-rev-link', + ), + $object_links); + $line_link = phutil_tag( 'a', array( 'href' => $line_href, 'style' => $style, ), - $line['line']); + $line_number); - $blame[] = javelin_tag( + $row[] = javelin_tag( 'th', array( 'class' => 'diffusion-line-link', @@ -1210,7 +1143,7 @@ final class DiffusionBrowseController extends DiffusionController { $anchor_text = null; } - $blame[] = phutil_tag( + $row[] = phutil_tag( 'td', array( ), @@ -1234,7 +1167,7 @@ final class DiffusionBrowseController extends DiffusionController { $cov_class = 'N'; } - $blame[] = phutil_tag( + $row[] = phutil_tag( 'td', array( 'class' => 'cov cov-'.$cov_class, @@ -1249,10 +1182,10 @@ final class DiffusionBrowseController extends DiffusionController { 'phabricator-source-highlight' : null), ), - $blame); + $row); $cur_inlines = $this->renderInlines( - idx($inlines, $line['line'], array()), + idx($inlines, $line_number, array()), $show_blame, $this->coverage, $engine); @@ -1520,7 +1453,6 @@ final class DiffusionBrowseController extends DiffusionController { private function renderCommitTooltip( PhabricatorRepositoryCommit $commit, - array $handles, $author) { $viewer = $this->getRequest()->getUser(); @@ -1528,10 +1460,6 @@ final class DiffusionBrowseController extends DiffusionController { $date = phabricator_date($commit->getEpoch(), $viewer); $summary = trim($commit->getSummary()); - if ($commit->getAuthorPHID()) { - $author = $handles[$commit->getAuthorPHID()]->getName(); - } - return "{$summary}\n{$date} \xC2\xB7 {$author}"; } @@ -1809,16 +1737,133 @@ final class DiffusionBrowseController extends DiffusionController { $commits = array(); } - foreach ($identifiers as $key => $identifier) { - $commit = idx($commits, $identifier); - if ($commit) { - $identifiers[$key] = $commit; - } else { - $identifiers[$key] = null; - } + return array($identifiers, $commits); + } + + private function renderCommitLinks(array $commits, $handles) { + $links = array(); + foreach ($commits as $identifier => $commit) { + $tooltip = $this->renderCommitTooltip( + $commit, + $commit->renderAuthorShortName($handles)); + + $commit_link = javelin_tag( + 'a', + array( + 'href' => $commit->getURI(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + $commit->getShortName()); + + $links[$identifier] = $commit_link; } - return $identifiers; + return $links; + } + + private function renderRevisionLinks(array $revisions, $handles) { + $links = array(); + + foreach ($revisions as $revision) { + $revision_id = $revision->getID(); + + $tooltip = $this->renderRevisionTooltip($revision, $handles); + + $revision_link = javelin_tag( + 'a', + array( + 'href' => $revision->getURI(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), + ), + $revision->getMonogram()); + + $links[$revision_id] = $revision_link; + } + + return $links; + } + + private function renderPlaintextCorpus( + $file_corpus, + array $blame_list, + array $blame_commits, + $show_blame) { + + $viewer = $this->getViewer(); + + if (!$show_blame) { + $corpus = $file_corpus; + } else { + $author_phids = array(); + foreach ($blame_commits as $commit) { + $author_phid = $commit->getAuthorPHID(); + if ($author_phid === null) { + continue; + } + $author_phids[$author_phid] = $author_phid; + } + + if ($author_phids) { + $handles = $viewer->loadHandles($author_phids); + } else { + $handles = array(); + } + + $authors = array(); + $names = array(); + foreach ($blame_commits as $identifier => $commit) { + $author = $commit->renderAuthorShortName($handles); + $name = $commit->getShortName(); + + $authors[$identifier] = $author; + $names[$identifier] = $name; + } + + $lines = phutil_split_lines($file_corpus); + + $rows = array(); + foreach ($lines as $line_number => $line) { + $commit_name = null; + $author = null; + + if (isset($blame_list[$line_number])) { + $identifier = $blame_list[$line_number]; + + if (isset($names[$identifier])) { + $commit_name = $names[$identifier]; + } + + if (isset($authors[$identifier])) { + $author = $authors[$identifier]; + } + } + + $rows[] = sprintf( + '%-10s %-20s %s', + $commit_name, + $author, + $line); + } + $corpus = implode('', $rows); + } + + return phutil_tag( + 'textarea', + array( + 'style' => 'border: none; width: 100%; height: 80em; '. + 'font-family: monospace', + ), + $corpus); } } From 438100691d42d6d98ebe224705bee6f8a8657813 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 18:08:07 -0800 Subject: [PATCH 70/83] Don't let blame run for longer than 15 seconds Summary: Fixes T2450. If we spend more than 15 seconds in blame, just cut it off. Test Plan: - Changed timeout to 0.01 seconds. - Did blame on a non-highlighted file, got no blame, saw warning. - Did blame on a highlighted file, got no blame. - Note: you don't get a warning here because of Ajax stuff. It'd be kind of tricky to add and doesn't seem like a big deal so I'm planning to leave it as-is for now. Reviewers: chad Reviewed By: chad Subscribers: 20after4, chasemp Maniphest Tasks: T2450 Differential Revision: https://secure.phabricator.com/D14964 --- .../controller/DiffusionBrowseController.php | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index ae921cd664..ade61a2580 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -591,19 +591,27 @@ final class DiffusionBrowseController extends DiffusionController { $data) { $viewer = $this->getViewer(); + $blame_timeout = 15; + $blame_failed = false; - if ($needs_blame) { - $blame = $this->loadBlame($path, $drequest->getCommit()); + $file_corpus = $file_content->getCorpus(); + $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; + $blame_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; + $can_highlight = (strlen($file_corpus) <= $highlight_limit); + $can_blame = (strlen($file_corpus) <= $blame_limit); + + if ($needs_blame && $can_blame) { + $blame = $this->loadBlame($path, $drequest->getCommit(), $blame_timeout); list($blame_list, $blame_commits) = $blame; + if ($blame_list === null) { + $blame_failed = true; + $blame_list = array(); + } } else { $blame_list = array(); $blame_commits = array(); } - $file_corpus = $file_content->getCorpus(); - $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; - $can_highlight = (strlen($file_corpus) <= $highlight_limit); - if (!$show_color) { $corpus = $this->renderPlaintextCorpus( $file_corpus, @@ -697,16 +705,32 @@ final class DiffusionBrowseController extends DiffusionController { ->appendChild($corpus) ->setCollapsed(true); + $messages = array(); + if (!$can_highlight) { - $message = pht( + $messages[] = pht( 'This file is larger than %s, so syntax highlighting is disabled '. 'by default.', phutil_format_bytes($highlight_limit)); + } + if ($show_blame && !$can_blame) { + $messages[] = pht( + 'This file is larger than %s, so blame is disabled.', + phutil_format_bytes($blame_limit)); + } + + if ($blame_failed) { + $messages[] = pht( + 'Failed to load blame information for this file in %s second(s).', + new PhutilNumber($blame_timeout)); + } + + if ($messages) { $corpus->setInfoView( id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) - ->setErrors(array($message))); + ->setErrors($messages)); } return $corpus; @@ -1709,15 +1733,16 @@ final class DiffusionBrowseController extends DiffusionController { return $view; } - private function loadBlame($path, $commit) { + private function loadBlame($path, $commit, $timeout) { $blame = $this->callConduitWithDiffusionRequest( 'diffusion.blame', array( 'commit' => $commit, 'paths' => array($path), + 'timeout' => $timeout, )); - $identifiers = idx($blame, $path, array()); + $identifiers = idx($blame, $path, null); if ($identifiers) { $viewer = $this->getViewer(); From d725dedb1edafe12e00723071414716fb95da485 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 18:52:13 -0800 Subject: [PATCH 71/83] Fix two minor issues with blame that involves revisions Summary: I was looking at some random un-revisioney repository for most of my testing and missed these. Test Plan: Viewed blame of a file with some revisions. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14965 --- .../diffusion/controller/DiffusionBrowseController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index ade61a2580..e61f24173a 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1462,7 +1462,7 @@ final class DiffusionBrowseController extends DiffusionController { private function renderRevisionTooltip( DifferentialRevision $revision, - array $handles) { + $handles) { $viewer = $this->getRequest()->getUser(); $date = phabricator_date($revision->getDateModified(), $viewer); @@ -1802,7 +1802,7 @@ final class DiffusionBrowseController extends DiffusionController { $revision_link = javelin_tag( 'a', array( - 'href' => $revision->getURI(), + 'href' => '/'.$revision->getMonogram(), 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $tooltip, From 449da36c2fb080d0c72ea812aee53fce5c32adea Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jan 2016 19:12:57 -0800 Subject: [PATCH 72/83] Use a path digest when building blame cache keys Keys have a maximum length of 128, and long paths could cause key lengths to exceed this. Auditors: chad --- .../diffusion/query/blame/DiffusionBlameQuery.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/query/blame/DiffusionBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php index 4d03b4a143..1c2221eae3 100644 --- a/src/applications/diffusion/query/blame/DiffusionBlameQuery.php +++ b/src/applications/diffusion/query/blame/DiffusionBlameQuery.php @@ -114,7 +114,9 @@ abstract class DiffusionBlameQuery extends DiffusionQuery { continue; } - $map[$path] = "blame({$repository_id}, {$identifier}, {$path}, raw)"; + $path_hash = PhabricatorHash::digestForIndex($path); + + $map[$path] = "blame({$repository_id}, {$identifier}, {$path_hash}, raw)"; } return $map; From 23367265e18c58563c1ba878a1d60b9c533f8443 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Jan 2016 16:33:09 -0800 Subject: [PATCH 73/83] Strip "Transfer-Encoding" headers from proxied HTTP responses This is a likely fix for HTTP clones against proxied repositories in the cluster, although I'm not 100% sure I'm replicating it correctly. The issue appears to be that we're proxying all the headers, including the "Transfer-Encoding" header, although the request will already have stripped any encoding. This might cause us to emit a "chunked" header without a chunked body. Auditors: chad --- src/aphront/response/AphrontHTTPProxyResponse.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/aphront/response/AphrontHTTPProxyResponse.php b/src/aphront/response/AphrontHTTPProxyResponse.php index c2e5bda603..fa629181ef 100644 --- a/src/aphront/response/AphrontHTTPProxyResponse.php +++ b/src/aphront/response/AphrontHTTPProxyResponse.php @@ -57,6 +57,20 @@ final class AphrontHTTPProxyResponse extends AphrontResponse { list($status, $body, $headers) = $this->future->resolve(); $this->httpCode = $status->getStatusCode(); + + // Strip "Transfer-Encoding" headers. Particularly, the server we proxied + // may have chunked the response, but cURL will already have un-chunked it. + // If we emit the header and unchunked data, the response becomes invalid. + foreach ($headers as $key => $header) { + list($header_head, $header_body) = $header; + $header_head = phutil_utf8_strtolower($header_head); + switch ($header_head) { + case 'transfer-encoding': + unset($headers[$key]); + break; + } + } + $this->headers = $headers; return $body; From 7ba13edc2ef96d9e2bc4f969c011dd4ee7c2d6e1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 09:08:43 -0800 Subject: [PATCH 74/83] Fix an issue with the Phortune card disable route I think this got clipped in modernization at some point. Auditors: chad --- .../controller/PhortunePaymentMethodDisableController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php index 4b8fb76010..659ae2f53e 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php @@ -5,7 +5,7 @@ final class PhortunePaymentMethodDisableController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $method_id = $request->getURIData('methodID'); + $method_id = $request->getURIData('id'); $method = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) From 0dd947cceddc35ff01e307b1416991536c189309 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 04:35:17 -0800 Subject: [PATCH 75/83] Move diff extraction from commits to a separate test with a CLI command Summary: Ref T9319. When we discover a commit, we sometimes update the corresponding revision with a "this is the actual committed change" diff and send out a link to the changes between review and commit. This is currently very difficult to test, because it only happens the first time and you have to either go set up a bunch of objects or add a bunch of special casing to the parser to hit the workflow. I'm making some changes to how it pulls file content. To make those changes easier to test, first start extracting this stuff so the code can be run with `bin/differential extract ...` instead of needing to do a bunch of more complicated setup steps. Test Plan: - Ran `bin/differential extract ...` to extract diffs from commits. - Forced my way through the daemon workflow by faking out a bunch of flags, got a clean extract + attach + update. After this patch, this should rarely be necessary. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9319 Differential Revision: https://secure.phabricator.com/D14967 --- bin/differential | 1 + scripts/setup/manage_differential.php | 21 +++++ src/__phutil_library_map__.php | 6 ++ .../DifferentialDiffExtractionEngine.php | 90 +++++++++++++++++++ ...PhabricatorDifferentialExtractWorkflow.php | 63 +++++++++++++ ...bricatorDifferentialManagementWorkflow.php | 4 + .../differential/storage/DifferentialDiff.php | 6 ++ ...torRepositoryCommitMessageParserWorker.php | 64 +------------ 8 files changed, 195 insertions(+), 60 deletions(-) create mode 120000 bin/differential create mode 100755 scripts/setup/manage_differential.php create mode 100644 src/applications/differential/engine/DifferentialDiffExtractionEngine.php create mode 100644 src/applications/differential/management/PhabricatorDifferentialExtractWorkflow.php create mode 100644 src/applications/differential/management/PhabricatorDifferentialManagementWorkflow.php diff --git a/bin/differential b/bin/differential new file mode 120000 index 0000000000..5a50360da3 --- /dev/null +++ b/bin/differential @@ -0,0 +1 @@ +../scripts/setup/manage_differential.php \ No newline at end of file diff --git a/scripts/setup/manage_differential.php b/scripts/setup/manage_differential.php new file mode 100755 index 0000000000..30e11d27a9 --- /dev/null +++ b/scripts/setup/manage_differential.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline(pht('manage hunks')); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorDifferentialManagementWorkflow') + ->execute(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0f3ecb4d42..9f2d9c0c21 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -386,6 +386,7 @@ phutil_register_library_map(array( 'DifferentialDiffContentRemovedHeraldField' => 'applications/differential/herald/DifferentialDiffContentRemovedHeraldField.php', 'DifferentialDiffCreateController' => 'applications/differential/controller/DifferentialDiffCreateController.php', 'DifferentialDiffEditor' => 'applications/differential/editor/DifferentialDiffEditor.php', + 'DifferentialDiffExtractionEngine' => 'applications/differential/engine/DifferentialDiffExtractionEngine.php', 'DifferentialDiffHeraldField' => 'applications/differential/herald/DifferentialDiffHeraldField.php', 'DifferentialDiffHeraldFieldGroup' => 'applications/differential/herald/DifferentialDiffHeraldFieldGroup.php', 'DifferentialDiffInlineCommentQuery' => 'applications/differential/query/DifferentialDiffInlineCommentQuery.php', @@ -2143,6 +2144,8 @@ phutil_register_library_map(array( 'PhabricatorDifferenceEngine' => 'infrastructure/diff/PhabricatorDifferenceEngine.php', 'PhabricatorDifferentialApplication' => 'applications/differential/application/PhabricatorDifferentialApplication.php', 'PhabricatorDifferentialConfigOptions' => 'applications/differential/config/PhabricatorDifferentialConfigOptions.php', + 'PhabricatorDifferentialExtractWorkflow' => 'applications/differential/management/PhabricatorDifferentialExtractWorkflow.php', + 'PhabricatorDifferentialManagementWorkflow' => 'applications/differential/management/PhabricatorDifferentialManagementWorkflow.php', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php', 'PhabricatorDiffusionApplication' => 'applications/diffusion/application/PhabricatorDiffusionApplication.php', 'PhabricatorDiffusionConfigOptions' => 'applications/diffusion/config/PhabricatorDiffusionConfigOptions.php', @@ -4334,6 +4337,7 @@ phutil_register_library_map(array( 'DifferentialDiffContentRemovedHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffEditor' => 'PhabricatorApplicationTransactionEditor', + 'DifferentialDiffExtractionEngine' => 'Phobject', 'DifferentialDiffHeraldField' => 'HeraldField', 'DifferentialDiffHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery', @@ -6376,6 +6380,8 @@ phutil_register_library_map(array( 'PhabricatorDifferenceEngine' => 'Phobject', 'PhabricatorDifferentialApplication' => 'PhabricatorApplication', 'PhabricatorDifferentialConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorDifferentialExtractWorkflow' => 'PhabricatorDifferentialManagementWorkflow', + 'PhabricatorDifferentialManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorDiffusionApplication' => 'PhabricatorApplication', 'PhabricatorDiffusionConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php new file mode 100644 index 0000000000..a77923c417 --- /dev/null +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -0,0 +1,90 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setAuthorPHID($author_phid) { + $this->authorPHID = $author_phid; + return $this; + } + + public function getAuthorPHID() { + return $this->authorPHID; + } + + public function newDiffFromCommit(PhabricatorRepositoryCommit $commit) { + $viewer = $this->getViewer(); + + $repository = $commit->getRepository(); + $identifier = $commit->getCommitIdentifier(); + $monogram = $commit->getMonogram(); + + $drequest = DiffusionRequest::newFromDictionary( + array( + 'user' => $viewer, + 'repository' => $repository, + )); + + $raw_diff = DiffusionQuery::callConduitWithDiffusionRequest( + $viewer, + $drequest, + 'diffusion.rawdiffquery', + array( + 'commit' => $identifier, + )); + + // TODO: Support adds, deletes and moves under SVN. + if (strlen($raw_diff)) { + $changes = id(new ArcanistDiffParser())->parseDiff($raw_diff); + } else { + // This is an empty diff, maybe made with `git commit --allow-empty`. + // NOTE: These diffs have the same tree hash as their ancestors, so + // they may attach to revisions in an unexpected way. Just let this + // happen for now, although it might make sense to special case it + // eventually. + $changes = array(); + } + + $diff = DifferentialDiff::newFromRawChanges($viewer, $changes) + ->setRepositoryPHID($repository->getPHID()) + ->setCreationMethod('commit') + ->setSourceControlSystem($repository->getVersionControlSystem()) + ->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP) + ->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP) + ->setDateCreated($commit->getEpoch()) + ->setDescription($monogram); + + $author_phid = $this->getAuthorPHID(); + if ($author_phid !== null) { + $diff->setAuthorPHID($author_phid); + } + + $parents = DiffusionQuery::callConduitWithDiffusionRequest( + $viewer, + $drequest, + 'diffusion.commitparentsquery', + array( + 'commit' => $identifier, + )); + + if ($parents) { + $diff->setSourceControlBaseRevision(head($parents)); + } + + // TODO: Attach binary files. + + return $diff->save(); + } + +} diff --git a/src/applications/differential/management/PhabricatorDifferentialExtractWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialExtractWorkflow.php new file mode 100644 index 0000000000..3afe232c42 --- /dev/null +++ b/src/applications/differential/management/PhabricatorDifferentialExtractWorkflow.php @@ -0,0 +1,63 @@ +setName('extract') + ->setExamples('**extract** __commit__') + ->setSynopsis(pht('Extract a diff from a commit.')) + ->setArguments( + array( + array( + 'name' => 'extract', + 'wildcard' => true, + 'help' => pht('Commit to extract.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $extract = $args->getArg('extract'); + + if (!$extract) { + throw new PhutilArgumentUsageException( + pht('Specify a commit to extract the diff from.')); + } + + if (count($extract) > 1) { + throw new PhutilArgumentUsageException( + pht('Specify exactly one commit to extract.')); + } + + $extract = head($extract); + + $commit = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withIdentifiers(array($extract)) + ->executeOne(); + + if (!$commit) { + throw new PhutilArgumentUsageException( + pht( + 'Commit "%s" is not valid.', + $extract)); + } + + $diff = id(new DifferentialDiffExtractionEngine()) + ->setViewer($viewer) + ->newDiffFromCommit($commit); + + $uri = PhabricatorEnv::getProductionURI($diff->getURI()); + + echo tsprintf( + "%s\n\n %s\n", + pht('Extracted diff from "%s":', $extract), + $uri); + } + + +} diff --git a/src/applications/differential/management/PhabricatorDifferentialManagementWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialManagementWorkflow.php new file mode 100644 index 0000000000..1a369e57ce --- /dev/null +++ b/src/applications/differential/management/PhabricatorDifferentialManagementWorkflow.php @@ -0,0 +1,4 @@ +getID(); + return "/differential/diff/{$id}/"; + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 0bc9957aef..95ac2e47af 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -216,7 +216,10 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $low_level_query->getRevisionMatchData()); } - $diff = $this->generateFinalDiff($revision, $acting_as_phid); + $diff = id(new DifferentialDiffExtractionEngine()) + ->setViewer($actor) + ->setAuthorPHID($acting_as_phid) + ->newDiffFromCommit($commit); $vs_diff = $this->loadChangedByCommit($revision, $diff); $changed_uri = null; @@ -277,65 +280,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker PhabricatorRepositoryCommit::IMPORTED_MESSAGE); } - private function generateFinalDiff( - DifferentialRevision $revision, - $actor_phid) { - - $viewer = PhabricatorUser::getOmnipotentUser(); - - $drequest = DiffusionRequest::newFromDictionary(array( - 'user' => $viewer, - 'repository' => $this->repository, - )); - - $raw_diff = DiffusionQuery::callConduitWithDiffusionRequest( - $viewer, - $drequest, - 'diffusion.rawdiffquery', - array( - 'commit' => $this->commit->getCommitIdentifier(), - )); - - // TODO: Support adds, deletes and moves under SVN. - if (strlen($raw_diff)) { - $changes = id(new ArcanistDiffParser())->parseDiff($raw_diff); - } else { - // This is an empty diff, maybe made with `git commit --allow-empty`. - // NOTE: These diffs have the same tree hash as their ancestors, so - // they may attach to revisions in an unexpected way. Just let this - // happen for now, although it might make sense to special case it - // eventually. - $changes = array(); - } - - $diff = DifferentialDiff::newFromRawChanges($viewer, $changes) - ->setRepositoryPHID($this->repository->getPHID()) - ->setAuthorPHID($actor_phid) - ->setCreationMethod('commit') - ->setSourceControlSystem($this->repository->getVersionControlSystem()) - ->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP) - ->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP) - ->setDateCreated($this->commit->getEpoch()) - ->setDescription( - pht( - 'Commit %s', - $this->commit->getMonogram())); - - $parents = DiffusionQuery::callConduitWithDiffusionRequest( - $viewer, - $drequest, - 'diffusion.commitparentsquery', - array( - 'commit' => $this->commit->getCommitIdentifier(), - )); - if ($parents) { - $diff->setSourceControlBaseRevision(head($parents)); - } - - // TODO: Attach binary files. - - return $diff->save(); - } private function loadChangedByCommit( DifferentialRevision $revision, From 2fcf571bfd8dd367cb44dcca4cee08ccefc2ea89 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 09:18:53 -0800 Subject: [PATCH 76/83] Use more reassuring UI and copy for removing payment methods Summary: The old treatment was fairly technical. Give this UI a more human-friendly flow: - Use language "remove" instead of "disable". We keep the record that the card existed around for auditing/historical purposes, but it is no longer a valid payment method going forward and can not be undone. I think this aligns with user expectation and actual behavior better than "disable". - Only show active methods on the profile screen. Test Plan: {F1057153} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14973 --- .../controller/PhortuneAccountViewController.php | 4 ++++ .../PhortunePaymentMethodDisableController.php | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php index f4791d8bb6..b5387b935e 100644 --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -132,6 +132,10 @@ final class PhortuneAccountViewController extends PhortuneController { $methods = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) + ->withStatuses( + array( + PhortunePaymentMethod::STATUS_ACTIVE, + )) ->execute(); foreach ($methods as $method) { diff --git a/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php index 659ae2f53e..d00db2725f 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodDisableController.php @@ -38,11 +38,10 @@ final class PhortunePaymentMethodDisableController } return $this->newDialog() - ->setTitle(pht('Disable Payment Method?')) - ->setShortTitle(pht('Disable Payment Method')) + ->setTitle(pht('Remove Payment Method')) ->appendParagraph( pht( - 'Disable the payment method "%s"?', + 'Remove the payment method "%s" from your account?', phutil_tag( 'strong', array(), @@ -50,9 +49,9 @@ final class PhortunePaymentMethodDisableController ->appendParagraph( pht( 'You will no longer be able to make payments using this payment '. - 'method. Disabled payment methods can not be reactivated.')) + 'method.')) ->addCancelButton($account_uri) - ->addSubmitButton(pht('Disable Payment Method')); + ->addSubmitButton(pht('Remove Payment Method')); } } From 5592e59f928011dbd5c880a0bf3bb755f55d2afd Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 06:38:25 -0800 Subject: [PATCH 77/83] Improve error message if local Git working copy directory exists but isn't a working copy Summary: Fixes T9701. I don't want to try to autofix this because destroying the directory could destroy important files, but we can improve the error message. Test Plan: Faked a failure, ran `repository update X`, got a more tailored error message. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9701 Differential Revision: https://secure.phabricator.com/D14971 --- .../engine/PhabricatorRepositoryEngine.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php index 0bfe94a0a6..42ad650a38 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php @@ -62,8 +62,21 @@ abstract class PhabricatorRepositoryEngine extends Phobject { * @return void */ protected function verifyGitOrigin(PhabricatorRepository $repository) { - list($remotes) = $repository->execxLocalCommand( - 'remote show -n origin'); + try { + list($remotes) = $repository->execxLocalCommand( + 'remote show -n origin'); + } catch (CommandException $ex) { + throw new PhutilProxyException( + pht( + 'Expected to find a Git working copy at path "%s", but the '. + 'path exists and is not a valid working copy. If you remove '. + 'this directory, the daemons will automatically recreate it '. + 'correctly. Phabricator will not destroy the directory for you '. + 'because it can not be sure that it does not contain important '. + 'data.', + $repository->getLocalPath()), + $ex); + } $matches = null; if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) { From 413ca12fda6b97cd64a01b7b974dc8343f6d4c11 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 04:59:31 -0800 Subject: [PATCH 78/83] Move commit attachment to a separate CLI command Summary: Ref T9319. See D14967. As before, this is making a deeply-buried, complex operation easier to test by providing a CLI command. This adds `bin/differential attach-commit rXnnnn Dnnnn` to pretend that `rXnnnn` was just committed and matched `Dnnnn`. Test Plan: - Ran `bin/differential attach-commit X Y` for several different values, saw updates in the UI. - Faked the message parser to make sure stuff still worked there. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9319 Differential Revision: https://secure.phabricator.com/D14968 --- src/__phutil_library_map__.php | 2 + .../DifferentialDiffExtractionEngine.php | 170 ++++++++++++++++++ ...icatorDifferentialAttachCommitWorkflow.php | 90 ++++++++++ ...torRepositoryCommitMessageParserWorker.php | 154 ++-------------- 4 files changed, 273 insertions(+), 143 deletions(-) create mode 100644 src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9f2d9c0c21..9639eef11e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2143,6 +2143,7 @@ phutil_register_library_map(array( 'PhabricatorDiffPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDiffPreferencesSettingsPanel.php', 'PhabricatorDifferenceEngine' => 'infrastructure/diff/PhabricatorDifferenceEngine.php', 'PhabricatorDifferentialApplication' => 'applications/differential/application/PhabricatorDifferentialApplication.php', + 'PhabricatorDifferentialAttachCommitWorkflow' => 'applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php', 'PhabricatorDifferentialConfigOptions' => 'applications/differential/config/PhabricatorDifferentialConfigOptions.php', 'PhabricatorDifferentialExtractWorkflow' => 'applications/differential/management/PhabricatorDifferentialExtractWorkflow.php', 'PhabricatorDifferentialManagementWorkflow' => 'applications/differential/management/PhabricatorDifferentialManagementWorkflow.php', @@ -6379,6 +6380,7 @@ phutil_register_library_map(array( 'PhabricatorDiffPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDifferenceEngine' => 'Phobject', 'PhabricatorDifferentialApplication' => 'PhabricatorApplication', + 'PhabricatorDifferentialAttachCommitWorkflow' => 'PhabricatorDifferentialManagementWorkflow', 'PhabricatorDifferentialConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorDifferentialExtractWorkflow' => 'PhabricatorDifferentialManagementWorkflow', 'PhabricatorDifferentialManagementWorkflow' => 'PhabricatorManagementWorkflow', diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index a77923c417..b2747e8ac8 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -87,4 +87,174 @@ final class DifferentialDiffExtractionEngine extends Phobject { return $diff->save(); } + public function isDiffChangedBeforeCommit( + PhabricatorRepositoryCommit $commit, + DifferentialDiff $old, + DifferentialDiff $new) { + + $viewer = $this->getViewer(); + $repository = $commit->getRepository(); + $identifier = $commit->getCommitIdentifier(); + + $vs_changesets = array(); + foreach ($old->getChangesets() as $changeset) { + $path = $changeset->getAbsoluteRepositoryPath($repository, $old); + $path = ltrim($path, '/'); + $vs_changesets[$path] = $changeset; + } + + $changesets = array(); + foreach ($new->getChangesets() as $changeset) { + $path = $changeset->getAbsoluteRepositoryPath($repository, $new); + $path = ltrim($path, '/'); + $changesets[$path] = $changeset; + } + + if (array_fill_keys(array_keys($changesets), true) != + array_fill_keys(array_keys($vs_changesets), true)) { + return true; + } + + $file_phids = array(); + foreach ($vs_changesets as $changeset) { + $metadata = $changeset->getMetadata(); + $file_phid = idx($metadata, 'new:binary-phid'); + if ($file_phid) { + $file_phids[$file_phid] = $file_phid; + } + } + + $files = array(); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } + + foreach ($changesets as $path => $changeset) { + $vs_changeset = $vs_changesets[$path]; + + $file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid'); + if ($file_phid) { + if (!isset($files[$file_phid])) { + return true; + } + + $drequest = DiffusionRequest::newFromDictionary(array( + 'user' => $viewer, + 'repository' => $repository, + 'commit' => $identifier, + 'path' => $path, + )); + + $corpus = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->loadFileContent() + ->getCorpus(); + + if ($files[$file_phid]->loadFileData() != $corpus) { + return true; + } + } else { + $context = implode("\n", $changeset->makeChangesWithContext()); + $vs_context = implode("\n", $vs_changeset->makeChangesWithContext()); + + // We couldn't just compare $context and $vs_context because following + // diffs will be considered different: + // + // -(empty line) + // -echo 'test'; + // (empty line) + // + // (empty line) + // -echo "test"; + // -(empty line) + + $hunk = id(new DifferentialModernHunk())->setChanges($context); + $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); + if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || + $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { + return true; + } + } + } + + return false; + } + + public function updateRevisionWithCommit( + DifferentialRevision $revision, + PhabricatorRepositoryCommit $commit, + array $more_xactions, + PhabricatorContentSource $content_source) { + + $viewer = $this->getViewer(); + $result_data = array(); + + $new_diff = $this->newDiffFromCommit($commit); + + $old_diff = $revision->getActiveDiff(); + $changed_uri = null; + if ($old_diff) { + $old_diff = id(new DifferentialDiffQuery()) + ->setViewer($viewer) + ->withIDs(array($old_diff->getID())) + ->needChangesets(true) + ->executeOne(); + if ($old_diff) { + $has_changed = $this->isDiffChangedBeforeCommit( + $commit, + $old_diff, + $new_diff); + if ($has_changed) { + $result_data['vsDiff'] = $old_diff->getID(); + + $revision_monogram = $revision->getMonogram(); + $old_id = $old_diff->getID(); + $new_id = $new_diff->getID(); + + $changed_uri = "/{$revision_monogram}?vs={$old_id}&id={$new_id}#toc"; + $changed_uri = PhabricatorEnv::getProductionURI($changed_uri); + } + } + } + + $xactions = array(); + + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) + ->setIgnoreOnNoEffect(true) + ->setNewValue($new_diff->getPHID()) + ->setMetadataValue('isCommitUpdate', true); + + foreach ($more_xactions as $more_xaction) { + $xactions[] = $more_xaction; + } + + $editor = id(new DifferentialTransactionEditor()) + ->setActor($viewer) + ->setContinueOnMissingFields(true) + ->setContentSource($content_source) + ->setChangedPriorToCommitURI($changed_uri) + ->setIsCloseByCommit(true); + + $author_phid = $this->getAuthorPHID(); + if ($author_phid !== null) { + $editor->setActingAsPHID($author_phid); + } + + try { + $editor->applyTransactions($revision, $xactions); + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { + // NOTE: We've marked transactions other than the CLOSE transaction + // as ignored when they don't have an effect, so this means that we + // lost a race to close the revision. That's perfectly fine, we can + // just continue normally. + } + + return $result_data; + } + } diff --git a/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php new file mode 100644 index 0000000000..5150dba315 --- /dev/null +++ b/src/applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php @@ -0,0 +1,90 @@ +setName('attach-commit') + ->setExamples('**attach-commit** __commit__ __revision__') + ->setSynopsis(pht('Forcefully attach a commit to a revision.')) + ->setArguments( + array( + array( + 'name' => 'argv', + 'wildcard' => true, + 'help' => pht('Commit, and a revision to attach it to.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $argv = $args->getArg('argv'); + if (count($argv) !== 2) { + throw new PhutilArgumentUsageException( + pht('Specify a commit and a revision to attach it to.')); + } + + $commit_name = head($argv); + $revision_name = last($argv); + + $commit = id(new DiffusionCommitQuery()) + ->setViewer($viewer) + ->withIdentifiers(array($commit_name)) + ->executeOne(); + if (!$commit) { + throw new PhutilArgumentUsageException( + pht('Commit "%s" does not exist.', $commit_name)); + } + + $revision = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($revision_name)) + ->executeOne(); + + if (!$revision) { + throw new PhutilArgumentUsageException( + pht('Revision "%s" does not exist.', $revision_name)); + } + + if (!($revision instanceof DifferentialRevision)) { + throw new PhutilArgumentUsageException( + pht('Object "%s" must be a Differential revision.', $revision_name)); + } + + // Reload the revision to get the active diff. + $revision = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->withIDs(array($revision->getID())) + ->needActiveDiffs(true) + ->executeOne(); + + $differential_phid = id(new PhabricatorDifferentialApplication()) + ->getPHID(); + + $extraction_engine = id(new DifferentialDiffExtractionEngine()) + ->setViewer($viewer) + ->setAuthorPHID($differential_phid); + + $content_source = PhabricatorContentSource::newForSource( + PhabricatorContentSource::SOURCE_CONSOLE, + array()); + + $extraction_engine->updateRevisionWithCommit( + $revision, + $commit, + array(), + $content_source); + + echo tsprintf( + "%s\n", + pht( + 'Attached "%s" to "%s".', + $commit->getMonogram(), + $revision->getMonogram())); + } + + +} diff --git a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php index 95ac2e47af..17a2f3a3f2 100644 --- a/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php +++ b/src/applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php @@ -216,50 +216,24 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker $low_level_query->getRevisionMatchData()); } - $diff = id(new DifferentialDiffExtractionEngine()) + $extraction_engine = id(new DifferentialDiffExtractionEngine()) ->setViewer($actor) - ->setAuthorPHID($acting_as_phid) - ->newDiffFromCommit($commit); - - $vs_diff = $this->loadChangedByCommit($revision, $diff); - $changed_uri = null; - if ($vs_diff) { - $data->setCommitDetail('vsDiff', $vs_diff->getID()); - - $changed_uri = PhabricatorEnv::getProductionURI( - '/D'.$revision->getID(). - '?vs='.$vs_diff->getID(). - '&id='.$diff->getID(). - '#toc'); - } - - $xactions = array(); - $xactions[] = id(new DifferentialTransaction()) - ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) - ->setIgnoreOnNoEffect(true) - ->setNewValue($diff->getPHID()) - ->setMetadataValue('isCommitUpdate', true); - $xactions[] = $commit_close_xaction; + ->setAuthorPHID($acting_as_phid); $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_DAEMON, array()); - $editor = id(new DifferentialTransactionEditor()) - ->setActor($actor) - ->setActingAsPHID($acting_as_phid) - ->setContinueOnMissingFields(true) - ->setContentSource($content_source) - ->setChangedPriorToCommitURI($changed_uri) - ->setIsCloseByCommit(true); + $update_data = $extraction_engine->updateRevisionWithCommit( + $revision, + $commit, + array( + $commit_close_xaction, + ), + $content_source); - try { - $editor->applyTransactions($revision, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - // NOTE: We've marked transactions other than the CLOSE transaction - // as ignored when they don't have an effect, so this means that we - // lost a race to close the revision. That's perfectly fine, we can - // just continue normally. + foreach ($update_data as $key => $value) { + $data->setCommitDetail($key, $value); } } } @@ -280,112 +254,6 @@ abstract class PhabricatorRepositoryCommitMessageParserWorker PhabricatorRepositoryCommit::IMPORTED_MESSAGE); } - - private function loadChangedByCommit( - DifferentialRevision $revision, - DifferentialDiff $diff) { - - $repository = $this->repository; - - $vs_diff = id(new DifferentialDiffQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withRevisionIDs(array($revision->getID())) - ->needChangesets(true) - ->setLimit(1) - ->executeOne(); - if (!$vs_diff) { - return null; - } - - if ($vs_diff->getCreationMethod() == 'commit') { - return null; - } - - $vs_changesets = array(); - foreach ($vs_diff->getChangesets() as $changeset) { - $path = $changeset->getAbsoluteRepositoryPath($repository, $vs_diff); - $path = ltrim($path, '/'); - $vs_changesets[$path] = $changeset; - } - - $changesets = array(); - foreach ($diff->getChangesets() as $changeset) { - $path = $changeset->getAbsoluteRepositoryPath($repository, $diff); - $path = ltrim($path, '/'); - $changesets[$path] = $changeset; - } - - if (array_fill_keys(array_keys($changesets), true) != - array_fill_keys(array_keys($vs_changesets), true)) { - return $vs_diff; - } - - $file_phids = array(); - foreach ($vs_changesets as $changeset) { - $metadata = $changeset->getMetadata(); - $file_phid = idx($metadata, 'new:binary-phid'); - if ($file_phid) { - $file_phids[$file_phid] = $file_phid; - } - } - - $files = array(); - if ($file_phids) { - $files = id(new PhabricatorFileQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs($file_phids) - ->execute(); - $files = mpull($files, null, 'getPHID'); - } - - foreach ($changesets as $path => $changeset) { - $vs_changeset = $vs_changesets[$path]; - - $file_phid = idx($vs_changeset->getMetadata(), 'new:binary-phid'); - if ($file_phid) { - if (!isset($files[$file_phid])) { - return $vs_diff; - } - $drequest = DiffusionRequest::newFromDictionary(array( - 'user' => PhabricatorUser::getOmnipotentUser(), - 'repository' => $this->repository, - 'commit' => $this->commit->getCommitIdentifier(), - 'path' => $path, - )); - $corpus = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->loadFileContent() - ->getCorpus(); - if ($files[$file_phid]->loadFileData() != $corpus) { - return $vs_diff; - } - } else { - $context = implode("\n", $changeset->makeChangesWithContext()); - $vs_context = implode("\n", $vs_changeset->makeChangesWithContext()); - - // We couldn't just compare $context and $vs_context because following - // diffs will be considered different: - // - // -(empty line) - // -echo 'test'; - // (empty line) - // - // (empty line) - // -echo "test"; - // -(empty line) - - $hunk = id(new DifferentialModernHunk())->setChanges($context); - $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); - if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || - $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { - return $vs_diff; - } - } - } - - return null; - } - private function resolveUserPHID( PhabricatorRepositoryCommit $commit, $user_name) { From da3963b009ce4be170c11688fd374593bd0c4773 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 05:51:58 -0800 Subject: [PATCH 79/83] Convert a low-level VCS query in Diff extraction to a Conduit call Summary: Ref T9319. Ref T2783. This won't currently work in a future environment where daemons and repositories are not on the same host. Send it over Conduit instead. Test Plan: Used `bin/differential attach-commit rX Dy` to force attachment, saw valid content pull over Conduit. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2783, T9319 Differential Revision: https://secure.phabricator.com/D14969 --- .../DifferentialDiffExtractionEngine.php | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index b2747e8ac8..b6d9d11953 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -142,17 +142,21 @@ final class DifferentialDiffExtractionEngine extends Phobject { return true; } - $drequest = DiffusionRequest::newFromDictionary(array( - 'user' => $viewer, - 'repository' => $repository, - 'commit' => $identifier, - 'path' => $path, - )); + $drequest = DiffusionRequest::newFromDictionary( + array( + 'user' => $viewer, + 'repository' => $repository, + )); - $corpus = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->loadFileContent() - ->getCorpus(); + $response = DiffusionQuery::callConduitWithDiffusionRequest( + $viewer, + $drequest, + 'diffusion.filecontentquery', + array( + 'commit' => $identifier, + 'path' => $path, + )); + $corpus = $response['corpus']; if ($files[$file_phid]->loadFileData() != $corpus) { return true; From d1fb2f7fb96da77f56caa039f69d047355e2a7fa Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Jan 2016 03:50:16 -0800 Subject: [PATCH 80/83] Make `diffusion.filecontentquery` return file PHIDs instead of raw content Summary: Fixes T9319. Proxied requests (e.g., in the cluster) for binary files (like images) currently fail because we can not return binary data over Conduit in JSON. Although Conduit will eventually support binary-safe encodings, a cleaner approach to this is just to return a `filePHID` instead of the raw content. This is generally faster and more flexible, and gives us more opportunities to add caching later. After making the call, the client pulls the file data separately. We also no longer need to return a complex data structure because we don't do blame over this call any longer. Test Plan: - Viewed images in Diffusion. - Viewed READMEs in Diffusion. - Used `bin/differential attach-commit rX Dy` to hit attach pathway. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9319 Differential Revision: https://secure.phabricator.com/D14970 --- src/__phutil_library_map__.php | 2 - .../DifferentialDiffExtractionEngine.php | 16 ++- ...fusionFileContentQueryConduitAPIMethod.php | 45 +++++-- .../controller/DiffusionBrowseController.php | 124 +++++++----------- .../controller/DiffusionController.php | 45 +++++++ .../DiffusionRepositoryController.php | 19 +-- .../diffusion/data/DiffusionFileContent.php | 63 --------- .../filecontent/DiffusionFileContentQuery.php | 79 ++++------- .../DiffusionGitFileContentQuery.php | 10 +- .../DiffusionMercurialFileContentQuery.php | 10 +- .../DiffusionSvnFileContentQuery.php | 29 +--- 11 files changed, 186 insertions(+), 256 deletions(-) delete mode 100644 src/applications/diffusion/data/DiffusionFileContent.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9639eef11e..c73cec5965 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -598,7 +598,6 @@ phutil_register_library_map(array( 'DiffusionExternalController' => 'applications/diffusion/controller/DiffusionExternalController.php', 'DiffusionExternalSymbolQuery' => 'applications/diffusion/symbol/DiffusionExternalSymbolQuery.php', 'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php', - 'DiffusionFileContent' => 'applications/diffusion/data/DiffusionFileContent.php', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php', 'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php', @@ -4571,7 +4570,6 @@ phutil_register_library_map(array( 'DiffusionExternalController' => 'DiffusionController', 'DiffusionExternalSymbolQuery' => 'Phobject', 'DiffusionExternalSymbolsSource' => 'Phobject', - 'DiffusionFileContent' => 'Phobject', 'DiffusionFileContentQuery' => 'DiffusionQuery', 'DiffusionFileContentQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod', diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index b6d9d11953..0fc6391987 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -156,9 +156,21 @@ final class DifferentialDiffExtractionEngine extends Phobject { 'commit' => $identifier, 'path' => $path, )); - $corpus = $response['corpus']; - if ($files[$file_phid]->loadFileData() != $corpus) { + $new_file_phid = $response['filePHID']; + if (!$new_file_phid) { + return true; + } + + $new_file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new_file_phid)) + ->executeOne(); + if (!$new_file) { + return true; + } + + if ($files[$file_phid]->loadFileData() != $new_file->loadFileData()) { return true; } } else { diff --git a/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php index 73ed4161fe..8088aa66b8 100644 --- a/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php @@ -27,8 +27,7 @@ final class DiffusionFileContentQueryConduitAPIMethod protected function getResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); - $file_query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest) - ->setViewer($request->getUser()); + $file_query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest); $timeout = $request->getValue('timeout'); if ($timeout) { @@ -40,16 +39,44 @@ final class DiffusionFileContentQueryConduitAPIMethod $file_query->setByteLimit($byte_limit); } - $file_content = $file_query->loadFileContent(); + $content = $file_query->execute(); - $text_list = $rev_list = $blame_dict = array(); + $too_slow = (bool)$file_query->getExceededTimeLimit(); + $too_huge = (bool)$file_query->getExceededByteLimit(); - $file_content - ->setBlameDict($blame_dict) - ->setRevList($rev_list) - ->setTextList($text_list); + $file_phid = null; + if (!$too_slow && !$too_huge) { + $file = $this->newFile($drequest, $content); + $file_phid = $file->getPHID(); + } - return $file_content->toDictionary(); + return array( + 'tooSlow' => $too_slow, + 'tooHuge' => $too_huge, + 'filePHID' => $file_phid, + ); + } + + private function newFile(DiffusionRequest $drequest, $content) { + $path = $drequest->getPath(); + $name = basename($path); + + $repository = $drequest->getRepository(); + $repository_phid = $repository->getPHID(); + + $file = PhabricatorFile::buildFromFileDataOrHash( + $content, + array( + 'name' => $name, + 'ttl' => time() + phutil_units('48 hours in seconds'), + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file->attachToObject($repository_phid); + unset($unguarded); + + return $file; } } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index e61f24173a..eef95bb1ee 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -153,44 +153,62 @@ final class DiffusionBrowseController extends DiffusionController { ); } - $file_content = DiffusionFileContent::newFromConduit( - $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - $params)); - $data = $file_content->getCorpus(); + $response = $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + $params); - if ($view === 'raw') { - return $this->buildRawResponse($path, $data); - } + $hit_byte_limit = $response['tooHuge']; + $hit_time_limit = $response['tooSlow']; - $this->loadLintMessages(); - $this->coverage = $drequest->loadCoverage(); - - if ($byte_limit && (strlen($data) == $byte_limit)) { + $file_phid = $response['filePHID']; + if ($hit_byte_limit) { $corpus = $this->buildErrorCorpus( pht( 'This file is larger than %s byte(s), and too large to display '. 'in the web UI.', - $byte_limit)); - } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { - $file = $this->loadFileForData($path, $data); - $file_uri = $file->getBestURI(); - - if ($file->isViewableImage()) { - $corpus = $this->buildImageCorpus($file_uri); - } else { - $corpus = $this->buildBinaryCorpus($file_uri, $data); - } + phutil_format_bytes($byte_limit))); + } else if ($hit_time_limit) { + $corpus = $this->buildErrorCorpus( + pht( + 'This file took too long to load from the repository (more than '. + '%s second(s)).', + new PhutilNumber($time_limit))); } else { - // Build the content of the file. - $corpus = $this->buildCorpus( - $show_blame, - $show_color, - $file_content, - $needs_blame, - $drequest, - $path, - $data); + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + throw new Exception(pht('Failed to load content file!')); + } + + if ($view === 'raw') { + return $file->getRedirectResponse(); + } + + $data = $file->loadFileData(); + if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { + $file_uri = $file->getBestURI(); + + if ($file->isViewableImage()) { + $corpus = $this->buildImageCorpus($file_uri); + } else { + $corpus = $this->buildBinaryCorpus($file_uri, $data); + } + } else { + $this->loadLintMessages(); + $this->coverage = $drequest->loadCoverage(); + + // Build the content of the file. + $corpus = $this->buildCorpus( + $show_blame, + $show_color, + $data, + $needs_blame, + $drequest, + $path, + $data); + } } if ($request->isAjax()) { @@ -327,23 +345,7 @@ final class DiffusionBrowseController extends DiffusionController { } $content[] = $this->buildOpenRevisions(); - - - $readme_path = $results->getReadmePath(); - if ($readme_path) { - $readme_content = $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - array( - 'path' => $readme_path, - 'commit' => $drequest->getStableCommit(), - )); - if ($readme_content) { - $content[] = id(new DiffusionReadmeView()) - ->setUser($this->getViewer()) - ->setPath($readme_path) - ->setContent($readme_content['corpus']); - } - } + $content[] = $this->renderDirectoryReadme($results); $crumbs = $this->buildCrumbs( array( @@ -584,7 +586,7 @@ final class DiffusionBrowseController extends DiffusionController { private function buildCorpus( $show_blame, $show_color, - DiffusionFileContent $file_content, + $file_corpus, $needs_blame, DiffusionRequest $drequest, $path, @@ -594,7 +596,6 @@ final class DiffusionBrowseController extends DiffusionController { $blame_timeout = 15; $blame_failed = false; - $file_corpus = $file_content->getCorpus(); $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; $blame_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT; $can_highlight = (strlen($file_corpus) <= $highlight_limit); @@ -1256,28 +1257,6 @@ final class DiffusionBrowseController extends DiffusionController { return $rows; } - private function loadFileForData($path, $data) { - $file = PhabricatorFile::buildFromFileDataOrHash( - $data, - array( - 'name' => basename($path), - 'ttl' => time() + 60 * 60 * 24, - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - )); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $file->attachToObject( - $this->getDiffusionRequest()->getRepository()->getPHID()); - unset($unguarded); - - return $file; - } - - private function buildRawResponse($path, $data) { - $file = $this->loadFileForData($path, $data); - return $file->getRedirectResponse(); - } - private function buildImageCorpus($file_uri) { $properties = new PHUIPropertyListView(); @@ -1299,7 +1278,6 @@ final class DiffusionBrowseController extends DiffusionController { } private function buildBinaryCorpus($file_uri, $data) { - $size = new PhutilNumber(strlen($data)); $text = pht('This is a binary file. It is %s byte(s) in length.', $size); $text = id(new PHUIBoxView()) diff --git a/src/applications/diffusion/controller/DiffusionController.php b/src/applications/diffusion/controller/DiffusionController.php index e398a88114..a3c661a26d 100644 --- a/src/applications/diffusion/controller/DiffusionController.php +++ b/src/applications/diffusion/controller/DiffusionController.php @@ -287,4 +287,49 @@ abstract class DiffusionController extends PhabricatorController { ->appendChild($pager); } + protected function renderDirectoryReadme(DiffusionBrowseResultSet $browse) { + $readme_path = $browse->getReadmePath(); + if ($readme_path === null) { + return null; + } + + $drequest = $this->getDiffusionRequest(); + $viewer = $this->getViewer(); + + try { + $result = $this->callConduitWithDiffusionRequest( + 'diffusion.filecontentquery', + array( + 'path' => $readme_path, + 'commit' => $drequest->getStableCommit(), + )); + } catch (Exception $ex) { + return null; + } + + $file_phid = $result['filePHID']; + if (!$file_phid) { + return null; + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + return null; + } + + $corpus = $file->loadFileData(); + + if (!strlen($corpus)) { + return null; + } + + return id(new DiffusionReadmeView()) + ->setUser($this->getViewer()) + ->setPath($readme_path) + ->setContent($corpus); + } + } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index dc6969b81d..9fc948d19a 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -161,23 +161,10 @@ final class DiffusionRepositoryController extends DiffusionController { $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); - $readme = null; if ($browse_results) { - $readme_path = $browse_results->getReadmePath(); - if ($readme_path) { - $readme_content = $this->callConduitWithDiffusionRequest( - 'diffusion.filecontentquery', - array( - 'path' => $readme_path, - 'commit' => $drequest->getStableCommit(), - )); - if ($readme_content) { - $readme = id(new DiffusionReadmeView()) - ->setUser($this->getViewer()) - ->setPath($readme_path) - ->setContent($readme_content['corpus']); - } - } + $readme = $this->renderDirectoryReadme($browse_results); + } else { + $readme = null; } $content[] = $this->buildBrowseTable( diff --git a/src/applications/diffusion/data/DiffusionFileContent.php b/src/applications/diffusion/data/DiffusionFileContent.php deleted file mode 100644 index 1680d1f964..0000000000 --- a/src/applications/diffusion/data/DiffusionFileContent.php +++ /dev/null @@ -1,63 +0,0 @@ -textList = $text_list; - return $this; - } - public function getTextList() { - if (!$this->textList) { - return phutil_split_lines($this->getCorpus(), $retain_ends = false); - } - return $this->textList; - } - - public function setRevList(array $rev_list) { - $this->revList = $rev_list; - return $this; - } - public function getRevList() { - return $this->revList; - } - - public function setBlameDict(array $blame_dict) { - $this->blameDict = $blame_dict; - return $this; - } - public function getBlameDict() { - return $this->blameDict; - } - - public function setCorpus($corpus) { - $this->corpus = $corpus; - return $this; - } - - public function getCorpus() { - return $this->corpus; - } - - public function toDictionary() { - return array( - 'corpus' => $this->getCorpus(), - 'blameDict' => $this->getBlameDict(), - 'revList' => $this->getRevList(), - 'textList' => $this->getTextList(), - ); - } - - public static function newFromConduit(array $dict) { - return id(new DiffusionFileContent()) - ->setCorpus($dict['corpus']) - ->setBlameDict($dict['blameDict']) - ->setRevList($dict['revList']) - ->setTextList($dict['textList']); - } - -} diff --git a/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php index 6a9cf2e021..3ea0e12ba1 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionFileContentQuery.php @@ -1,18 +1,13 @@ timeout = $timeout; return $this; @@ -31,71 +26,51 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery { return $this->byteLimit; } - public function setViewer(PhabricatorUser $user) { - $this->viewer = $user; - return $this; - } - - public function getViewer() { - return $this->viewer; - } - final public static function newFromDiffusionRequest( DiffusionRequest $request) { return parent::newQueryObject(__CLASS__, $request); } - abstract public function getFileContentFuture(); - abstract protected function executeQueryFromFuture(Future $future); + final public function getExceededByteLimit() { + return $this->didHitByteLimit; + } - final public function loadFileContentFromFuture(Future $future) { + final public function getExceededTimeLimit() { + return $this->didHitTimeLimit; + } - if ($this->timeout) { - $future->setTimeout($this->timeout); + abstract protected function getFileContentFuture(); + abstract protected function resolveFileContentFuture(Future $future); + + final protected function executeQuery() { + $future = $this->getFileContentFuture(); + + if ($this->getTimeout()) { + $future->setTimeout($this->getTimeout()); } - if ($this->getByteLimit()) { - $future->setStdoutSizeLimit($this->getByteLimit()); + $byte_limit = $this->getByteLimit(); + if ($byte_limit) { + $future->setStdoutSizeLimit($byte_limit + 1); } try { - $file_content = $this->executeQueryFromFuture($future); + $file_content = $this->resolveFileContentFuture($future); } catch (CommandException $ex) { if (!$future->getWasKilledByTimeout()) { throw $ex; } - $message = pht( - '', - $this->timeout); - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($message); + $this->didHitTimeLimit = true; + $file_content = null; } - $this->fileContent = $file_content; - - $repository = $this->getRequest()->getRepository(); - $try_encoding = $repository->getDetail('encoding'); - if ($try_encoding) { - $this->fileContent->setCorpus( - phutil_utf8_convert( - $this->fileContent->getCorpus(), 'UTF-8', $try_encoding)); + if ($byte_limit && (strlen($file_content) > $byte_limit)) { + $this->didHitByteLimit = true; + $file_content = null; } - return $this->fileContent; - } - - final protected function executeQuery() { - return $this->loadFileContentFromFuture($this->getFileContentFuture()); - } - - final public function loadFileContent() { - return $this->executeQuery(); - } - - final public function getRawData() { - return $this->fileContent->getCorpus(); + return $file_content; } } diff --git a/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php index 448d2886dc..a383aee922 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php @@ -2,7 +2,7 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { - public function getFileContentFuture() { + protected function getFileContentFuture() { $drequest = $this->getRequest(); $repository = $drequest->getRepository(); @@ -15,13 +15,9 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { $path); } - protected function executeQueryFromFuture(Future $future) { + protected function resolveFileContentFuture(Future $future) { list($corpus) = $future->resolvex(); - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($corpus); - - return $file_content; + return $corpus; } } diff --git a/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php index ec49414666..539f5752c2 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php @@ -3,7 +3,7 @@ final class DiffusionMercurialFileContentQuery extends DiffusionFileContentQuery { - public function getFileContentFuture() { + protected function getFileContentFuture() { $drequest = $this->getRequest(); $repository = $drequest->getRepository(); @@ -16,13 +16,9 @@ final class DiffusionMercurialFileContentQuery $path); } - protected function executeQueryFromFuture(Future $future) { + protected function resolveFileContentFuture(Future $future) { list($corpus) = $future->resolvex(); - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($corpus); - - return $file_content; + return $corpus; } } diff --git a/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php b/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php index 4976d7a27b..0f6f30b355 100644 --- a/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php @@ -2,7 +2,7 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { - public function getFileContentFuture() { + protected function getFileContentFuture() { $drequest = $this->getRequest(); $repository = $drequest->getRepository(); @@ -14,30 +14,9 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { $repository->getSubversionPathURI($path, $commit)); } - protected function executeQueryFromFuture(Future $future) { - try { - list($corpus) = $future->resolvex(); - } catch (CommandException $ex) { - $stderr = $ex->getStdErr(); - if (preg_match('/path not found$/', trim($stderr))) { - // TODO: Improve user experience for this. One way to end up here - // is to have the parser behind and look at a file which was recently - // nuked; Diffusion will think it still exists and try to grab content - // at HEAD. - throw new Exception( - pht( - 'Failed to retrieve file content from Subversion. The file may '. - 'have been recently deleted, or the Diffusion cache may be out of '. - 'date.')); - } else { - throw $ex; - } - } - - $file_content = new DiffusionFileContent(); - $file_content->setCorpus($corpus); - - return $file_content; + protected function resolveFileContentFuture(Future $future) { + list($corpus) = $future->resolvex(); + return $corpus; } } From c7520cd9f26dc34ca4f6985400394f6cd29b6a11 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 11:41:49 -0800 Subject: [PATCH 81/83] Improve rendering of commit branching graph Summary: Fixes T9323. Two minor fixes: - On the first commit, don't render a downward line. - Clean up a 1px spacing issue that had cropped up a while ago when we added icons or something, I think. Test Plan: Before: {F1057248} After: {F1057249} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9323 Differential Revision: https://secure.phabricator.com/D14974 --- resources/celerity/map.php | 16 +++++++-------- .../controller/DiffusionHistoryController.php | 1 + .../view/DiffusionHistoryTableView.php | 16 +++++++++++++++ src/view/phui/PHUIPagerView.php | 4 ++++ .../diffusion/behavior-commit-graph.js | 20 ++++++++++--------- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 8343d9526c..aec7734a63 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '64e69521', 'diffusion.pkg.css' => 'f45955ed', - 'diffusion.pkg.js' => 'ca1c8b5a', + 'diffusion.pkg.js' => '3a9a8bfa', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '949a7498', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', @@ -391,7 +391,7 @@ return array( 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', - 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '9007c197', + 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '5a0b1a64', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', @@ -598,7 +598,7 @@ return array( 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', - 'javelin-behavior-diffusion-commit-graph' => '9007c197', + 'javelin-behavior-diffusion-commit-graph' => '5a0b1a64', 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', @@ -1218,6 +1218,11 @@ return array( 'javelin-vector', 'javelin-dom', ), + '5a0b1a64' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + ), '5b2e3e2b' => array( 'javelin-stratcom', 'javelin-request', @@ -1523,11 +1528,6 @@ return array( 'javelin-dom', 'javelin-workflow', ), - '9007c197' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - ), '901935ef' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/diffusion/controller/DiffusionHistoryController.php b/src/applications/diffusion/controller/DiffusionHistoryController.php index edbce636c2..59248b7acf 100644 --- a/src/applications/diffusion/controller/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/DiffusionHistoryController.php @@ -52,6 +52,7 @@ final class DiffusionHistoryController extends DiffusionController { if ($show_graph) { $history_table->setParents($history_results['parents']); $history_table->setIsHead(!$pager->getOffset()); + $history_table->setIsTail(!$pager->getHasMorePages()); } $history_panel = new PHUIObjectBoxView(); diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php index 314bfb435c..e62c4382c9 100644 --- a/src/applications/diffusion/view/DiffusionHistoryTableView.php +++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php @@ -6,6 +6,7 @@ final class DiffusionHistoryTableView extends DiffusionView { private $revisions = array(); private $handles = array(); private $isHead; + private $isTail; private $parents; public function setHistory(array $history) { @@ -60,6 +61,11 @@ final class DiffusionHistoryTableView extends DiffusionView { return $this; } + public function setIsTail($is_tail) { + $this->isTail = $is_tail; + return $this; + } + public function render() { $drequest = $this->getDiffusionRequest(); @@ -344,6 +350,16 @@ final class DiffusionHistoryTableView extends DiffusionView { ); } + // If this is the last page in history, replace the "o" with an "x" so we + // do not draw a connecting line downward, and replace "^" with an "X" for + // repositories with exactly one commit. + if ($this->isTail && $graph) { + $last = array_pop($graph); + $last['line'] = str_replace('o', 'x', $last['line']); + $last['line'] = str_replace('^', 'X', $last['line']); + $graph[] = $last; + } + // Render into tags for the behavior. foreach ($graph as $k => $meta) { diff --git a/src/view/phui/PHUIPagerView.php b/src/view/phui/PHUIPagerView.php index 16d1f96a69..1e14522ca7 100644 --- a/src/view/phui/PHUIPagerView.php +++ b/src/view/phui/PHUIPagerView.php @@ -58,6 +58,10 @@ final class PHUIPagerView extends AphrontView { return $this->hasMorePages; } + public function getHasMorePages() { + return $this->hasMorePages; + } + public function setSurroundingPages($pages) { $this->surroundingPages = max(0, $pages); return $this; diff --git a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js index 97f1de568d..a3a7c7bdf8 100644 --- a/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js +++ b/webroot/rsrc/js/application/diffusion/behavior-commit-graph.js @@ -37,6 +37,7 @@ JX.behavior('diffusion-commit-graph', function(config) { // Stroke with fill (for commit circles). function fstroke(c) { + cxt.lineWidth = 1; cxt.fillStyle = color(c); cxt.strokeStyle = '#ffffff'; cxt.fill(); @@ -52,7 +53,7 @@ JX.behavior('diffusion-commit-graph', function(config) { return (col * cell) + (cell / 2); }; - var h = 30; + var h = 32; var w = cell * config.count; var canvas = JX.$N('canvas', {width: w, height: h}); @@ -117,16 +118,17 @@ JX.behavior('diffusion-commit-graph', function(config) { case 'o': case '^': case '|': - if (c == 'o' || c == '^') { - origin = xpos(jj); + case 'x': + case 'X': + + if (c !== 'X') { + cxt.beginPath(); + cxt.moveTo(xpos(jj), (c == '^' ? h/2 : 0)); + cxt.lineTo(xpos(jj), (c == 'x' ? h/2 : h)); + lstroke(jj); } - cxt.beginPath(); - cxt.moveTo(xpos(jj), (c == '^' ? h/2 : 0)); - cxt.lineTo(xpos(jj), h); - lstroke(jj); - - if (c == 'o' || c == '^') { + if (c == 'o' || c == '^' || c == 'x' || c == 'X') { cxt.beginPath(); cxt.arc(xpos(jj), h/2, 3, 0, 2 * Math.PI, true); fstroke(jj); From 9ab22e21b305bd8767620ed25ce991e27b117388 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 30 Dec 2015 04:36:48 -0800 Subject: [PATCH 82/83] Allow installs to customize project icons Summary: Ref T10010. Ref T5819. General alignment of the stars: - There were some hacks in Conduit around stripping `fa-...` off icons when reading and writing that I wanted to get rid of. - We probably have room for a subtitle in the new heavy nav, and using the icon name is a good starting point (and maybe good enough on its own?) - The project list was real bad looking with redundant tag/names, now it is very slightly less bad looking with non-redundant types? - Some installs will want to call Milestones something else, and this gets us a big part of the way there. - This may slightly help to reinforce "tag" vs "policy" vs "group" stuff? --- I'm letting installs have enough rope to shoot themselves in the foot (e.g., define 100 icons). It isn't the end of the world if they reuse icons, and is clearly their fault. I think the cases where 100 icons will break down are: - Icon selector dialog may get very unwieldy. - Query UI will be pretty iffy/huge with 100 icons. We could improve these fairly easily if an install comes up with a reasonable use case for having 100 icons. --- The UI on the icon itself in the list views is a little iffy -- mostly, it's too saturated/bold. I'd ideally like to try either: - rendering a "shade" version (i.e. lighter, less-saturated color); or - rendering a "shade" tag with just the icon in it. However, there didn't seem to be a way to do the first one right now (`fa-example sh-blue` doesn't work) and the second one had weird margins/padding, so I left it like this for now. I figure we can clean it up once we build the thick nav, since that will probably also want an identical element. (I don't want to render a full tag with the icon + name since I think that's confusing -- it looks like a project/object tag, but is not.) Test Plan: {F1049905} {F1049906} Reviewers: chad Reviewed By: chad Subscribers: 20after4, Luke081515.2 Maniphest Tasks: T5819, T10010 Differential Revision: https://secure.phabricator.com/D14918 --- resources/celerity/map.php | 22 +- .../sql/autopatches/20151231.proj.01.icon.php | 34 ++ src/__phutil_library_map__.php | 2 + .../custom/PhabricatorConfigOptionType.php | 3 +- ...PhabricatorFileIconSetSelectController.php | 23 +- .../files/iconset/PhabricatorIconSetIcon.php | 10 + .../conduit/ProjectConduitAPIMethod.php | 2 +- .../conduit/ProjectQueryConduitAPIMethod.php | 5 - .../PhabricatorProjectConfigOptions.php | 31 ++ ...PhabricatorProjectTypeConfigOptionType.php | 10 + .../PhabricatorProjectController.php | 8 +- .../icon/PhabricatorProjectIconSet.php | 310 ++++++++++++++++-- .../PhabricatorProjectProjectPHIDType.php | 2 +- .../query/PhabricatorProjectSearchEngine.php | 4 + .../project/storage/PhabricatorProject.php | 32 +- .../view/PhabricatorProjectListView.php | 17 +- .../application/calendar/calendar-icon.css | 28 -- .../phui-icon-set-selector.css} | 3 +- .../rsrc/js/core/behavior-choose-control.js | 2 +- 19 files changed, 452 insertions(+), 96 deletions(-) create mode 100644 resources/sql/autopatches/20151231.proj.01.icon.php create mode 100644 src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php delete mode 100644 webroot/rsrc/css/application/calendar/calendar-icon.css rename webroot/rsrc/css/{application/projects/project-icon.css => phui/phui-icon-set-selector.css} (84%) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index aec7734a63..c32a98f1f2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -37,7 +37,6 @@ return array( 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '6b451f24', 'rsrc/css/application/base/standard-page-view.css' => '3c99cdf4', - 'rsrc/css/application/calendar/calendar-icon.css' => 'c69aa59f', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -94,7 +93,6 @@ return array( 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => '7b0df4da', - 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', @@ -135,6 +133,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => '0b98e572', 'rsrc/css/phui/phui-header-view.css' => '55bb32dd', + 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => 'b0a6b1b6', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', @@ -465,7 +464,7 @@ return array( 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', - 'rsrc/js/core/behavior-choose-control.js' => '8fee767e', + 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'a205cf28', @@ -524,7 +523,6 @@ return array( 'aphront-typeahead-control-css' => '0e403212', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', - 'calendar-icon-css' => 'c69aa59f', 'changeset-view-manager' => '58562350', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', @@ -571,7 +569,7 @@ return array( 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-bulk-job-reload' => 'edf8a145', - 'javelin-behavior-choose-control' => '8fee767e', + 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => 'b65559c0', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', @@ -809,6 +807,7 @@ return array( 'phui-form-css' => '0b98e572', 'phui-form-view-css' => '4a1a0f5e', 'phui-header-view-css' => '55bb32dd', + 'phui-icon-set-selector-css' => '1ab67aad', 'phui-icon-view-css' => 'b0a6b1b6', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', @@ -839,7 +838,6 @@ return array( 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => '7b0df4da', - 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', 'raphael-g-line' => '40da039e', @@ -1044,6 +1042,12 @@ return array( '2f670a96' => array( 'phui-theme-css', ), + '327a00d1' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-workflow', + ), '331b1611' => array( 'javelin-install', ), @@ -1522,12 +1526,6 @@ return array( 'javelin-install', 'javelin-dom', ), - '8fee767e' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-workflow', - ), '901935ef' => array( 'javelin-behavior', 'javelin-dom', diff --git a/resources/sql/autopatches/20151231.proj.01.icon.php b/resources/sql/autopatches/20151231.proj.01.icon.php new file mode 100644 index 0000000000..501614df3d --- /dev/null +++ b/resources/sql/autopatches/20151231.proj.01.icon.php @@ -0,0 +1,34 @@ + 'project', + 'fa-tags' => 'tag', + 'fa-lock' => 'policy', + 'fa-users' => 'group', + + 'fa-folder' => 'folder', + 'fa-calendar' => 'timeline', + 'fa-flag-checkered' => 'goal', + 'fa-truck' => 'release', + + 'fa-bug' => 'bugs', + 'fa-trash-o' => 'cleanup', + 'fa-umbrella' => 'umbrella', + 'fa-envelope' => 'communication', + + 'fa-building' => 'organization', + 'fa-cloud' => 'infrastructure', + 'fa-credit-card' => 'account', + 'fa-flask' => 'experimental', +); + +$table = new PhabricatorProject(); +$conn_w = $table->establishConnection('w'); +foreach ($icon_map as $old_icon => $new_key) { + queryfx( + $conn_w, + 'UPDATE %T SET icon = %s WHERE icon = %s', + $table->getTableName(), + $new_key, + $old_icon); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c73cec5965..2a0de867aa 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2902,6 +2902,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', + 'PhabricatorProjectTypeConfigOptionType' => 'applications/project/config/PhabricatorProjectTypeConfigOptionType.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', @@ -7268,6 +7269,7 @@ phutil_register_library_map(array( 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorProjectTypeConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/config/custom/PhabricatorConfigOptionType.php b/src/applications/config/custom/PhabricatorConfigOptionType.php index 3f588452c4..733229e652 100644 --- a/src/applications/config/custom/PhabricatorConfigOptionType.php +++ b/src/applications/config/custom/PhabricatorConfigOptionType.php @@ -24,8 +24,7 @@ abstract class PhabricatorConfigOptionType extends Phobject { $value) { if (is_array($value)) { - $json = new PhutilJSON(); - return $json->encodeFormatted($value); + return PhabricatorConfigJSON::prettyPrintJSON($value); } else { return $value; } diff --git a/src/applications/files/controller/PhabricatorFileIconSetSelectController.php b/src/applications/files/controller/PhabricatorFileIconSetSelectController.php index af46e2d66e..ed7d18a5ee 100644 --- a/src/applications/files/controller/PhabricatorFileIconSetSelectController.php +++ b/src/applications/files/controller/PhabricatorFileIconSetSelectController.php @@ -26,7 +26,7 @@ final class PhabricatorFileIconSetSelectController } } - require_celerity_resource('project-icon-css'); + require_celerity_resource('phui-icon-set-selector-css'); Javelin::initBehavior('phabricator-tooltips'); $ii = 0; @@ -37,6 +37,20 @@ final class PhabricatorFileIconSetSelectController $view = id(new PHUIIconView()) ->setIconFont($icon->getIcon()); + $classes = array(); + $classes[] = 'icon-button'; + + $is_selected = ($icon->getKey() == $v_icon); + + if ($is_selected) { + $classes[] = 'selected'; + } + + $is_disabled = $icon->getIsDisabled(); + if ($is_disabled && !$is_selected) { + continue; + } + $aural = javelin_tag( 'span', array( @@ -44,13 +58,6 @@ final class PhabricatorFileIconSetSelectController ), pht('Choose "%s" Icon', $label)); - $classes = array(); - $classes[] = 'icon-button'; - - if ($icon->getKey() == $v_icon) { - $classes[] = 'selected'; - } - $buttons[] = javelin_tag( 'button', array( diff --git a/src/applications/files/iconset/PhabricatorIconSetIcon.php b/src/applications/files/iconset/PhabricatorIconSetIcon.php index 1e1773aa16..03511cc75b 100644 --- a/src/applications/files/iconset/PhabricatorIconSetIcon.php +++ b/src/applications/files/iconset/PhabricatorIconSetIcon.php @@ -6,6 +6,7 @@ final class PhabricatorIconSetIcon private $key; private $icon; private $label; + private $isDisabled; public function setKey($key) { $this->key = $key; @@ -28,6 +29,15 @@ final class PhabricatorIconSetIcon return $this->icon; } + public function setIsDisabled($is_disabled) { + $this->isDisabled = $is_disabled; + return $this; + } + + public function getIsDisabled() { + return $this->isDisabled; + } + public function setLabel($label) { $this->label = $label; return $this; diff --git a/src/applications/project/conduit/ProjectConduitAPIMethod.php b/src/applications/project/conduit/ProjectConduitAPIMethod.php index 6acf320daf..f6e40f38a3 100644 --- a/src/applications/project/conduit/ProjectConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectConduitAPIMethod.php @@ -26,7 +26,7 @@ abstract class ProjectConduitAPIMethod extends ConduitAPIMethod { $project_slugs = $project->getSlugs(); $project_slugs = array_values(mpull($project_slugs, 'getSlug')); - $project_icon = substr($project->getIcon(), 3); + $project_icon = $project->getDisplayIconKey(); $result[$project->getPHID()] = array( 'id' => $project->getID(), diff --git a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php index 2512a9a019..3116ecb7f9 100644 --- a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php +++ b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php @@ -76,11 +76,6 @@ final class ProjectQueryConduitAPIMethod extends ProjectConduitAPIMethod { $request->getValue('icons'); if ($request->getValue('icons')) { $icons = array(); - // the internal 'fa-' prefix is a detail hidden from api clients - // but needs to pre prepended to the values in the icons array: - foreach ($request->getValue('icons') as $value) { - $icons[] = 'fa-'.$value; - } $query->withIcons($icons); } diff --git a/src/applications/project/config/PhabricatorProjectConfigOptions.php b/src/applications/project/config/PhabricatorProjectConfigOptions.php index 8ec8bc8607..1cb7654732 100644 --- a/src/applications/project/config/PhabricatorProjectConfigOptions.php +++ b/src/applications/project/config/PhabricatorProjectConfigOptions.php @@ -20,6 +20,34 @@ final class PhabricatorProjectConfigOptions } public function getOptions() { + $default_icons = PhabricatorProjectIconSet::getDefaultConfiguration(); + $icons_type = 'custom:PhabricatorProjectTypeConfigOptionType'; + + $icons_description = $this->deformat(pht(<< Icons and Images}. + +Configure a list of icon specifications. Each icon specification should be +a dictionary, which may contain these keys: + + - `key` //Required string.// Internal key identifying the icon. + - `name` //Required string.// Human-readable icon name. + - `icon` //Required string.// Specifies which actual icon image to use. + - `default` //Optional bool.// Selects a default icon. Exactly one icon must + be selected as the default. + - `disabled` //Optional bool.// If true, this icon will no longer be + available for selection when creating or editing projects. + - `special` //Optional string.// Marks an icon as a special icon: + - `milestone` This is the icon for milestones. Exactly one icon must be + selected as the milestone icon. + +You can look at the default configuration below for an example of a valid +configuration. +EOTEXT + )); + + $default_fields = array( 'std:project:internal:description' => true, ); @@ -45,6 +73,9 @@ final class PhabricatorProjectConfigOptions $this->newOption('projects.fields', $custom_field_type, $default_fields) ->setCustomData(id(new PhabricatorProject())->getCustomFieldBaseClass()) ->setDescription(pht('Select and reorder project fields.')), + $this->newOption('projects.icons', $icons_type, $default_icons) + ->setSummary(pht('Adjust project icons.')) + ->setDescription($icons_description), ); } diff --git a/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php b/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php new file mode 100644 index 0000000000..8c65dfe35a --- /dev/null +++ b/src/applications/project/config/PhabricatorProjectTypeConfigOptionType.php @@ -0,0 +1,10 @@ +supportsMilestones()) { - $milestones_icon = 'fa-map-marker'; - } else { - $milestones_icon = 'fa-map-marker grey'; + $key = PhabricatorProjectIconSet::getMilestoneIconKey(); + $milestones_icon = PhabricatorProjectIconSet::getIconIcon($key); + if (!$project->supportsMilestones()) { + $milestones_icon = "{$milestones_icon} grey"; } $nav->addIcon( diff --git a/src/applications/project/icon/PhabricatorProjectIconSet.php b/src/applications/project/icon/PhabricatorProjectIconSet.php index 470e871096..82592918b1 100644 --- a/src/applications/project/icon/PhabricatorProjectIconSet.php +++ b/src/applications/project/icon/PhabricatorProjectIconSet.php @@ -5,38 +5,121 @@ final class PhabricatorProjectIconSet const ICONSETKEY = 'projects'; + const SPECIAL_MILESTONE = 'milestone'; + public function getSelectIconTitleText() { return pht('Choose Project Icon'); } - protected function newIcons() { - $map = array( - 'fa-briefcase' => pht('Briefcase'), - 'fa-tags' => pht('Tag'), - 'fa-folder' => pht('Folder'), - 'fa-users' => pht('Team'), - - 'fa-bug' => pht('Bug'), - 'fa-trash-o' => pht('Garbage'), - 'fa-calendar' => pht('Deadline'), - 'fa-flag-checkered' => pht('Goal'), - - 'fa-envelope' => pht('Communication'), - 'fa-truck' => pht('Release'), - 'fa-lock' => pht('Policy'), - 'fa-umbrella' => pht('An Umbrella'), - - 'fa-cloud' => pht('The Cloud'), - 'fa-building' => pht('Company'), - 'fa-credit-card' => pht('Accounting'), - 'fa-flask' => pht('Experimental'), + public static function getDefaultConfiguration() { + return array( + array( + 'key' => 'project', + 'icon' => 'fa-briefcase', + 'name' => pht('Project'), + 'default' => true, + ), + array( + 'key' => 'tag', + 'icon' => 'fa-tags', + 'name' => pht('Tag'), + ), + array( + 'key' => 'policy', + 'icon' => 'fa-lock', + 'name' => pht('Policy'), + ), + array( + 'key' => 'group', + 'icon' => 'fa-users', + 'name' => pht('Group'), + ), + array( + 'key' => 'folder', + 'icon' => 'fa-folder', + 'name' => pht('Folder'), + ), + array( + 'key' => 'timeline', + 'icon' => 'fa-calendar', + 'name' => pht('Timeline'), + ), + array( + 'key' => 'goal', + 'icon' => 'fa-flag-checkered', + 'name' => pht('Goal'), + ), + array( + 'key' => 'release', + 'icon' => 'fa-truck', + 'name' => pht('Release'), + ), + array( + 'key' => 'bugs', + 'icon' => 'fa-bug', + 'name' => pht('Bugs'), + ), + array( + 'key' => 'cleanup', + 'icon' => 'fa-trash-o', + 'name' => pht('Cleanup'), + ), + array( + 'key' => 'umbrella', + 'icon' => 'fa-umbrella', + 'name' => pht('Umbrella'), + ), + array( + 'key' => 'communication', + 'icon' => 'fa-envelope', + 'name' => pht('Communication'), + ), + array( + 'key' => 'organization', + 'icon' => 'fa-building', + 'name' => pht('Organization'), + ), + array( + 'key' => 'infrastructure', + 'icon' => 'fa-cloud', + 'name' => pht('Infrastructure'), + ), + array( + 'key' => 'account', + 'icon' => 'fa-credit-card', + 'name' => pht('Account'), + ), + array( + 'key' => 'experimental', + 'icon' => 'fa-flask', + 'name' => pht('Experimental'), + ), + array( + 'key' => 'milestone', + 'icon' => 'fa-map-marker', + 'name' => pht('Milestone'), + 'special' => self::SPECIAL_MILESTONE, + ), ); + } + + + protected function newIcons() { + $map = self::getIconSpecifications(); $icons = array(); - foreach ($map as $key => $label) { + foreach ($map as $spec) { + $special = idx($spec, 'special'); + + if ($special === self::SPECIAL_MILESTONE) { + continue; + } + $icons[] = id(new PhabricatorIconSetIcon()) - ->setKey($key) - ->setLabel($label); + ->setKey($spec['key']) + ->setIsDisabled(idx($spec, 'disabled')) + ->setIcon($spec['icon']) + ->setLabel($spec['name']); } return $icons; @@ -52,4 +135,183 @@ final class PhabricatorProjectIconSet return $shades; } + private static function getIconSpecifications() { + return PhabricatorEnv::getEnvConfig('projects.icons'); + } + + public static function getDefaultIconKey() { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'default')) { + return $icon['key']; + } + } + return null; + } + + public static function getIconIcon($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'icon', null); + } + + public static function getIconName($key) { + $spec = self::getIconSpec($key); + return idx($spec, 'name', null); + } + + private static function getIconSpec($key) { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'key') === $key) { + return $icon; + } + } + + return array(); + } + + public static function getMilestoneIconKey() { + $icons = self::getIconSpecifications(); + foreach ($icons as $icon) { + if (idx($icon, 'special') === self::SPECIAL_MILESTONE) { + return idx($icon, 'key'); + } + } + return null; + } + + public static function validateConfiguration($config) { + if (!is_array($config)) { + throw new Exception( + pht('Configuration must be a list of project icon specifications.')); + } + + foreach ($config as $idx => $value) { + if (!is_array($value)) { + throw new Exception( + pht( + 'Value for index "%s" should be a dictionary.', + $idx)); + } + + PhutilTypeSpec::checkMap( + $value, + array( + 'key' => 'string', + 'name' => 'string', + 'icon' => 'string', + 'special' => 'optional string', + 'disabled' => 'optional bool', + 'default' => 'optional bool', + )); + + if (!preg_match('/^[a-z]{1,32}\z/', $value['key'])) { + throw new Exception( + pht( + 'Icon key "%s" is not a valid icon key. Icon keys must be 1-32 '. + 'characters long and contain only lowercase letters. For example, '. + '"%s" and "%s" are reasonable keys.', + 'tag', + 'group')); + } + + $special = idx($value, 'special'); + $valid = array( + self::SPECIAL_MILESTONE => true, + ); + + if ($special !== null) { + if (empty($valid[$special])) { + throw new Exception( + pht( + 'Icon special attribute "%s" is not valid. Recognized special '. + 'attributes are: %s.', + $special, + implode(', ', array_keys($valid)))); + } + } + } + + $default = null; + $milestone = null; + $keys = array(); + foreach ($config as $idx => $value) { + $key = $value['key']; + if (isset($keys[$key])) { + throw new Exception( + pht( + 'Project icons must have unique keys, but two icons share the '. + 'same key ("%s").', + $key)); + } else { + $keys[$key] = true; + } + + $is_disabled = idx($value, 'disabled'); + + if (idx($value, 'default')) { + if ($default === null) { + if ($is_disabled) { + throw new Exception( + pht( + 'The project icon marked as the default icon ("%s") must not '. + 'be disabled.', + $key)); + } + $default = $value; + } else { + $original_key = $default['key']; + throw new Exception( + pht( + 'Two different icons ("%s", "%s") are marked as the default '. + 'icon. Only one icon may be marked as the default.', + $key, + $original_key)); + } + } + + $special = idx($value, 'special'); + if ($special === self::SPECIAL_MILESTONE) { + if ($milestone === null) { + if ($is_disabled) { + throw new Exception( + pht( + 'The project icon ("%s") with special attribute "%s" must '. + 'not be disabled', + $key, + self::SPECIAL_MIILESTONE)); + } + $milestone = $value; + } else { + $original_key = $milestone['key']; + throw new Exception( + pht( + 'Two different icons ("%s", "%s") are marked with special '. + 'attribute "%s". Only one icon may be marked with this '. + 'attribute.', + $key, + $original_key, + self::SPECIAL_MILESTONE)); + } + } + } + + if ($default === null) { + throw new Exception( + pht( + 'Project icons must include one icon marked as the "%s" icon, '. + 'but no such icon exists.', + 'default')); + } + + if ($milestone === null) { + throw new Exception( + pht( + 'Project icons must include one icon marked with special attribute '. + '"%s", but no such icon exists.', + self::SPECIAL_MILESTONE)); + } + + } + } diff --git a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php index eb1a59a5f5..b9fdefc6b3 100644 --- a/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php +++ b/src/applications/project/phid/PhabricatorProjectProjectPHIDType.php @@ -51,7 +51,7 @@ final class PhabricatorProjectProjectPHIDType extends PhabricatorPHIDType { } $handle->setImageURI($project->getProfileImageURI()); - $handle->setIcon($project->getDisplayIcon()); + $handle->setIcon($project->getDisplayIconIcon()); $handle->setTagColor($project->getDisplayColor()); if ($project->isArchived()) { diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 66d64dcdcc..eca90853cc 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -131,6 +131,10 @@ protected function buildQueryFromParameters(array $map) { $set = new PhabricatorProjectIconSet(); foreach ($set->getIcons() as $icon) { + if ($icon->getIsDisabled()) { + continue; + } + $options[$icon->getKey()] = array( id(new PHUIIconView()) ->setIconFont($icon->getIcon()), diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 5d2aa3461e..92c8ac68f8 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -45,7 +45,6 @@ final class PhabricatorProject extends PhabricatorProjectDAO private $slugs = self::ATTACHABLE; private $parentProject = self::ATTACHABLE; - const DEFAULT_ICON = 'fa-briefcase'; const DEFAULT_COLOR = 'blue'; const TABLE_DATASOURCE_TOKEN = 'project_datasourcetoken'; @@ -63,9 +62,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO $join_policy = $app->getPolicy( ProjectDefaultJoinCapability::CAPABILITY); + $default_icon = PhabricatorProjectIconSet::getDefaultIconKey(); + return id(new PhabricatorProject()) ->setAuthorPHID($actor->getPHID()) - ->setIcon(self::DEFAULT_ICON) + ->setIcon($default_icon) ->setColor(self::DEFAULT_COLOR) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) @@ -484,12 +485,24 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $number; } - public function getDisplayIcon() { + public function getDisplayIconKey() { if ($this->isMilestone()) { - return 'fa-map-marker'; + $key = PhabricatorProjectIconSet::getMilestoneIconKey(); + } else { + $key = $this->getIcon(); } - return $this->getIcon(); + return $key; + } + + public function getDisplayIconIcon() { + $key = $this->getDisplayIconKey(); + return PhabricatorProjectIconSet::getIconIcon($key); + } + + public function getDisplayIconName() { + $key = $this->getDisplayIconKey(); + return PhabricatorProjectIconSet::getIconName($key); } public function getDisplayColor() { @@ -608,6 +621,10 @@ final class PhabricatorProject extends PhabricatorProjectDAO ->setKey('slug') ->setType('string') ->setDescription(pht('Primary slug/hashtag.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('icon') + ->setType('map') + ->setDescription(pht('Information about the project icon.')), ); } @@ -615,6 +632,11 @@ final class PhabricatorProject extends PhabricatorProjectDAO return array( 'name' => $this->getName(), 'slug' => $this->getPrimarySlug(), + 'icon' => array( + 'key' => $this->getDisplayIconKey(), + 'name' => $this->getDisplayIconName(), + 'icon' => $this->getDisplayIconIcon(), + ), ); } diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index f9e864ff3d..87874e3594 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -25,15 +25,24 @@ final class PhabricatorProjectListView extends AphrontView { foreach ($projects as $key => $project) { $id = $project->getID(); - $tag_list = id(new PHUIHandleTagListView()) - ->setSlim(true) - ->setHandles(array($handles[$project->getPHID()])); + $icon = $project->getDisplayIconIcon(); + $color = $project->getColor(); + + $icon_icon = id(new PHUIIconView()) + ->setIconFont("{$icon} {$color}"); + + $icon_name = $project->getDisplayIconName(); $item = id(new PHUIObjectItemView()) ->setHeader($project->getName()) ->setHref("/project/view/{$id}/") ->setImageURI($project->getProfileImageURI()) - ->addAttribute($tag_list); + ->addAttribute( + array( + $icon_icon, + ' ', + $icon_name, + )); if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ARCHIVED) { $item->addIcon('delete-grey', pht('Archived')); diff --git a/webroot/rsrc/css/application/calendar/calendar-icon.css b/webroot/rsrc/css/application/calendar/calendar-icon.css deleted file mode 100644 index 35757de9e2..0000000000 --- a/webroot/rsrc/css/application/calendar/calendar-icon.css +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @provides calendar-icon-css - */ - -button.icon-button { - background: {$lightgreybackground}; - border: 1px solid {$lightblueborder}; - position: relative; - width: 16px; - height: 16px; - padding: 12px; - margin: 4px; - text-shadow: none; - box-shadow: none; - box-sizing: content-box; -} - -.icon-grid { - text-align: center; -} - -.icon-icon + .icon-icon { - margin-left: 4px; -} - -button.icon-button.selected { - background: {$bluebackground}; -} diff --git a/webroot/rsrc/css/application/projects/project-icon.css b/webroot/rsrc/css/phui/phui-icon-set-selector.css similarity index 84% rename from webroot/rsrc/css/application/projects/project-icon.css rename to webroot/rsrc/css/phui/phui-icon-set-selector.css index 1af85b30d6..09a0362246 100644 --- a/webroot/rsrc/css/application/projects/project-icon.css +++ b/webroot/rsrc/css/phui/phui-icon-set-selector.css @@ -1,5 +1,5 @@ /** - * @provides project-icon-css + * @provides phui-icon-set-selector-css */ button.icon-button { @@ -25,4 +25,5 @@ button.icon-button { button.icon-button.selected { background: {$bluebackground}; + border: 1px solid {$blueborder}; } diff --git a/webroot/rsrc/js/core/behavior-choose-control.js b/webroot/rsrc/js/core/behavior-choose-control.js index 3ff2f166ca..68fbbbc8e0 100644 --- a/webroot/rsrc/js/core/behavior-choose-control.js +++ b/webroot/rsrc/js/core/behavior-choose-control.js @@ -22,7 +22,7 @@ JX.behavior('choose-control', function() { } var params = { - value: input.value + icon: input.value }; new JX.Workflow(data.uri, params) From 2c293bdca88d048ee413f9496ce34d75f3c45d7e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 14:36:10 -0800 Subject: [PATCH 83/83] Fix a missed use of project icons in typehaeads Summary: I missed this in the recent icon customziation thing. Test Plan: Typehaead'ed some projects, saw icons properly. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D14975 --- .../project/typeahead/PhabricatorProjectDatasource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index bb8f02cfd4..64f0604b3f 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -67,7 +67,7 @@ final class PhabricatorProjectDatasource ->setDisplayType(pht('Project')) ->setURI('/tag/'.$proj->getPrimarySlug().'/') ->setPHID($proj->getPHID()) - ->setIcon($proj->getIcon()) + ->setIcon($proj->getDisplayIconIcon()) ->setColor($proj->getColor()) ->setPriorityType('proj') ->setClosed($closed);