From 04c40e3abf108e2eae6b42528b1886596b729961 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Jul 2015 15:08:01 -0700 Subject: [PATCH 001/865] (stable) Put the HeraldActionRecord table back on the shelf Summary: Fixes T8958. I renamed this class and table (in D13644), but the migration `20150606.mlist.1.php` needs to load records from the table before the table rename applies. Put the table back and accept a tiny bit of cruft here to fix this problem. We can no-op the migration after a while. I'll cherry-pick this to `stable`. Test Plan: Applied migration. Created Herald rules. Reviewed existing Herald rules. Reviewers: btrahan Subscribers: epriestley Maniphest Tasks: T8958 Differential Revision: https://secure.phabricator.com/D13736 --- resources/sql/autopatches/20150727.heraldaction.1.sql | 2 ++ src/applications/herald/storage/HeraldActionRecord.php | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 resources/sql/autopatches/20150727.heraldaction.1.sql diff --git a/resources/sql/autopatches/20150727.heraldaction.1.sql b/resources/sql/autopatches/20150727.heraldaction.1.sql new file mode 100644 index 0000000000..f4cbf19504 --- /dev/null +++ b/resources/sql/autopatches/20150727.heraldaction.1.sql @@ -0,0 +1,2 @@ +RENAME TABLE {$NAMESPACE}_herald.herald_actionrecord + TO {$NAMESPACE}_herald.herald_action; diff --git a/src/applications/herald/storage/HeraldActionRecord.php b/src/applications/herald/storage/HeraldActionRecord.php index 1ad4721b21..dfbee3b79d 100644 --- a/src/applications/herald/storage/HeraldActionRecord.php +++ b/src/applications/herald/storage/HeraldActionRecord.php @@ -7,6 +7,14 @@ final class HeraldActionRecord extends HeraldDAO { protected $action; protected $target; + public function getTableName() { + // TODO: This class was renamed, but we have a migration which affects the + // table prior to to the rename. For now, having cruft here is cleaner than + // having it in the migration. We could rename this table again and no-op + // the migration after some time. See T8958. + return 'herald_action'; + } + protected function getConfiguration() { return array( self::CONFIG_SERIALIZATION => array( From 535c1a5f255e0da69ea5fbfb230b699eb5a152b1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 27 Jul 2015 15:12:03 -0700 Subject: [PATCH 002/865] (stable) Fix public/private profile edit note Summary: Fixes T8976. Checks state of note before applying to box. Test Plan: Set policy to login, edit profile. Reviewers: epriestley Reviewed By: epriestley Subscribers: epriestley, Korvin Maniphest Tasks: T8976 Differential Revision: https://secure.phabricator.com/D13739 --- .../controller/PhabricatorPeopleProfileEditController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php index 65bf9df0d5..f53278b536 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileEditController.php @@ -84,10 +84,13 @@ public function processRequest() { $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Edit Profile')) - ->setInfoView($note) ->setValidationException($validation_exception) ->setForm($form); + if ($note) { + $form_box->setInfoView($note); + } + $nav = $this->buildIconNavView($user); $nav->selectFilter('/'); $nav->appendChild($form_box); From de2aa64acfda85fa8dc014bdeed0fb47298d42cf Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 06:59:18 -0700 Subject: [PATCH 003/865] (stable) Fix an issue with ClassMap handling of DivinerAtomizers Fixes T9193. --- .../diviner/workflow/DivinerGenerateWorkflow.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php index 78f43a826f..d448de69c0 100644 --- a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php +++ b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php @@ -420,9 +420,10 @@ private function getDivinerAtomWorldVersion() { $atomizer_versions = array(); foreach ($atomizers as $atomizer) { - $atomizer_versions[$atomizer['name']] = call_user_func( + $name = get_class($atomizer); + $atomizer_versions[$name] = call_user_func( array( - $atomizer['name'], + $name, 'getAtomizerVersion', )); } From 9a651c18ec0bc00af11fe7ee80d7ab07ab8983fd Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 22 Sep 2015 13:03:29 -0700 Subject: [PATCH 004/865] (stable) Recover gracefully from Conduit failure when building "Tags" field in commit mail Summary: Ref T9458. This is basically the same as D13319, but the "Tags" field didn't get covered in that change. Specifically, the issue is: - We try to generate mail to a disabled user (later, we'll drop it without delivering it, but that filtering doesn't happen yet). - The disabled user doesn't have permission to use Conduit (or any other Conduit-related problem occurs). - We fail here, then retry generating the mail again later. Instead, just degrade to not building the field and showing what went wrong. Test Plan: - Pushed some commits, saw mail generate. - Added a fake exception to the field, saw the mail generate with an error message. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9458 Differential Revision: https://secure.phabricator.com/D14142 --- .../PhabricatorCommitTagsField.php | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/applications/repository/customfield/PhabricatorCommitTagsField.php b/src/applications/repository/customfield/PhabricatorCommitTagsField.php index b69d246d85..001d81e960 100644 --- a/src/applications/repository/customfield/PhabricatorCommitTagsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitTagsField.php @@ -29,18 +29,23 @@ public function updateTransactionMailBody( 'callsign' => $this->getObject()->getRepository()->getCallsign(), ); - $tags_raw = id(new ConduitCall('diffusion.tagsquery', $params)) - ->setUser($this->getViewer()) - ->execute(); - - $tags = DiffusionRepositoryTag::newFromConduit($tags_raw); - if (!$tags) { - return; + try { + $tags_raw = id(new ConduitCall('diffusion.tagsquery', $params)) + ->setUser($this->getViewer()) + ->execute(); + + $tags = DiffusionRepositoryTag::newFromConduit($tags_raw); + if (!$tags) { + return; + } + $tag_names = mpull($tags, 'getName'); + sort($tag_names); + $tag_names = implode(', ', $tag_names); + } catch (Exception $ex) { + $tag_names = pht('<%s: %s>', get_class($ex), $ex->getMessage()); } - $tag_names = mpull($tags, 'getName'); - sort($tag_names); - $body->addTextSection(pht('TAGS'), implode(', ', $tag_names)); + $body->addTextSection(pht('TAGS'), $tag_names); } } From 6d7b78f791f0030236095b6236485585d3004d61 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 28 Sep 2015 11:17:04 -0700 Subject: [PATCH 005/865] (stable) Fix issue with "Publish/Notify" handling in repositories Summary: Fixes T8728. As far as I can tell, I simply got this wrong in D11826. This is not the proper name for the preference. That change primarily focused on the "spammy junk during import" issue, and the code did get the importing flag right. It looks like my testing in D11827 focused on "during import" and just missed this case. Test Plan: Grepped for `disable-herald`. Grepped for `herald-disable`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8728 Differential Revision: https://secure.phabricator.com/D14181 --- src/applications/repository/storage/PhabricatorRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index d5b128292a..084f531a10 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -715,7 +715,7 @@ public function shouldPublish() { return false; } - if ($this->getDetail('disable-herald')) { + if ($this->getDetail('herald-disabled')) { return false; } From 5194ff3db595e2dff678eee990584dfa50a1a04e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 5 Oct 2015 15:57:41 -0700 Subject: [PATCH 006/865] (stable) Fix several error handling issues with Subversion commits in Diffusion Summary: Ref T9513. I checked this briefly but didn't do a very thorough job of it. - Don't try to query merges for Subversion, since it doesn't support them. - Fix up "existsquery" to work properly (and efficiently) for both hosted and imported repositories. - Fix up "parentsquery" to have similar behavior on invalid commits to other VCSes (throw an exception). Test Plan: - No more merges warning on SVN. - Hosted SVN gets the right exists result now. - Visiting "r23980283789287" now 404's instead of "not parsed yet". Reviewers: chad Reviewed By: chad Maniphest Tasks: T9513 Differential Revision: https://secure.phabricator.com/D14239 --- .../DiffusionExistsQueryConduitAPIMethod.php | 17 +++++++---------- .../controller/DiffusionCommitController.php | 19 +++++++++++-------- .../DiffusionLowLevelParentsQuery.php | 16 +++++++++++++++- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php index 3c6d09ebda..4280230002 100644 --- a/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php @@ -33,16 +33,13 @@ protected function getGitResult(ConduitAPIRequest $request) { protected function getSVNResult(ConduitAPIRequest $request) { $repository = $this->getDiffusionRequest()->getRepository(); $commit = $request->getValue('commit'); - list($info) = $repository->execxRemoteCommand( - 'info %s', - $repository->getRemoteURI()); - $exists = false; - $matches = null; - if (preg_match('/^Revision: (\d+)$/m', $info, $matches)) { - $base_revision = $matches[1]; - $exists = $base_revision >= $commit; - } - return $exists; + + $refs = id(new DiffusionCachedResolveRefsQuery()) + ->setRepository($repository) + ->withRefs(array($commit)) + ->execute(); + + return (bool)$refs; } protected function getMercurialResult(ConduitAPIRequest $request) { diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index fe2f539f09..a698eba5c0 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -1136,14 +1136,17 @@ private function loadCommitState() { $merge_limit = $this->getMergeDisplayLimit(); try { - $merges = $this->callConduitWithDiffusionRequest( - 'diffusion.mergedcommitsquery', - array( - 'commit' => $commit, - 'limit' => $merge_limit + 1, - )); - - $this->commitMerges = DiffusionPathChange::newFromConduit($merges); + if ($repository->isSVN()) { + $this->commitMerges = array(); + } else { + $merges = $this->callConduitWithDiffusionRequest( + 'diffusion.mergedcommitsquery', + array( + 'commit' => $commit, + 'limit' => $merge_limit + 1, + )); + $this->commitMerges = DiffusionPathChange::newFromConduit($merges); + } } catch (Exception $ex) { $this->commitMerges = false; $exceptions[] = $ex; diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php index 60ee4b5fe2..f49f828d79 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php @@ -70,7 +70,21 @@ private function loadMercurialParents() { } private function loadSubversionParents() { - $n = (int)$this->identifier; + $repository = $this->getRepository(); + $identifier = $this->identifier; + + $refs = id(new DiffusionCachedResolveRefsQuery()) + ->setRepository($repository) + ->withRefs(array($identifier)) + ->execute(); + if (!$refs) { + throw new Exception( + pht( + 'No commit "%s" in this repository.', + $identifier)); + } + + $n = (int)$identifier; if ($n > 1) { $ids = array($n - 1); } else { From c0a52201a8991aa71e84b01932b984bb233fb96c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Oct 2015 08:41:49 -0700 Subject: [PATCH 007/865] (stable) Work around an issue with custom "users" fields in Maniphest Summary: Fixes T9558. The recent changes to validate PHID fields don't work cleanly with this gross hack. This can probably be unwound now but it will definitely get fixed in T9132 so I may just wait for that. Test Plan: Edited a custom "users" field in Maniphest. This should only affect Maniphest because it has a weird hack. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9558 Differential Revision: https://secure.phabricator.com/D14264 --- .../maniphest/controller/ManiphestTaskEditController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/maniphest/controller/ManiphestTaskEditController.php b/src/applications/maniphest/controller/ManiphestTaskEditController.php index 703bb61768..5c4a3b4862 100644 --- a/src/applications/maniphest/controller/ManiphestTaskEditController.php +++ b/src/applications/maniphest/controller/ManiphestTaskEditController.php @@ -198,7 +198,8 @@ public function handleRequest(AphrontRequest $request) { // in a meaningful way. For now, build User objects. Once the Maniphest // objects exist, this will switch over automatically. This is a big // hack but shouldn't be long for this world. - $placeholder_editor = new PhabricatorUserProfileEditor(); + $placeholder_editor = id(new PhabricatorUserProfileEditor()) + ->setActor($viewer); $field_errors = $aux_field->validateApplicationTransactions( $placeholder_editor, From 111c48b21024d0e113a147ac5bd453feff09aeaa Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Nov 2015 05:21:54 -0800 Subject: [PATCH 008/865] (stable) Fix no-op transaction error on `paste.create` Conduit API method Summary: Fixes T9735. I changed how the TYPE_LANGUAGE transction works a little but that accidentally tripped an error condition in `paste.create`. - Don't bail on no-effect transactions to `paste.create` (like not setting a language). - When a transaction type has no tailored UI message, make it easier to figure out which transaction is problematic. Test Plan: Ran `arc paste ...` locally. Got an error before the patch, clean paste creation afterward. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9735 Differential Revision: https://secure.phabricator.com/D14440 --- .../paste/conduit/PasteCreateConduitAPIMethod.php | 1 + .../storage/PhabricatorApplicationTransaction.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php index d5ce021f13..2dae0132d4 100644 --- a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php +++ b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php @@ -60,6 +60,7 @@ protected function execute(ConduitAPIRequest $request) { $editor = id(new PhabricatorPasteEditor()) ->setActor($viewer) + ->setContinueOnNoEffect(true) ->setContentSourceFromConduitRequest($request); $xactions = $editor->applyTransactions($paste, $xactions); diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index a61de01978..9eb1609931 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -626,7 +626,9 @@ public function getNoEffectDescription() { return pht('Edges already exist; transaction has no effect.'); } - return pht('Transaction has no effect.'); + return pht( + 'Transaction (of type "%s") has no effect.', + $this->getTransactionType()); } public function getTitle() { From ecaa45521c0617f709b0d432fa84294811930f16 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 7 Nov 2015 18:24:06 -0800 Subject: [PATCH 009/865] (stable) Fix PropertyList background color in PHUIDocumentView Summary: Overzealous nuking. Test Plan: Test Phriction, Phame, Diviner. All show property lists correctly. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14439 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/phui/phui-document.css | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 91b57e9f5d..8f8209b309 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -127,7 +127,7 @@ 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => 'd842f867', 'rsrc/css/phui/phui-document-pro.css' => '4f2b42e3', - 'rsrc/css/phui/phui-document.css' => '9fa715d2', + 'rsrc/css/phui/phui-document.css' => 'f841ad0a', 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', 'rsrc/css/phui/phui-fontkit.css' => 'c9d63950', 'rsrc/css/phui/phui-form-view.css' => '621b21c5', @@ -780,7 +780,7 @@ 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => 'd842f867', - 'phui-document-view-css' => '9fa715d2', + 'phui-document-view-css' => 'f841ad0a', 'phui-document-view-pro-css' => '4f2b42e3', 'phui-feed-story-css' => 'b7b26d23', 'phui-font-icon-base-css' => 'ecbbb4c2', diff --git a/webroot/rsrc/css/phui/phui-document.css b/webroot/rsrc/css/phui/phui-document.css index c13d99930c..eaf90dc468 100644 --- a/webroot/rsrc/css/phui/phui-document.css +++ b/webroot/rsrc/css/phui/phui-document.css @@ -89,7 +89,8 @@ } .phui-document-content .phui-property-list-container { - border-color: {$thinblueborder}; + border-bottom: 1px solid {$thinblueborder}; + background-color: {$lightgreybackground}; } .legalpad .phui-document-content .phui-property-list-view { From cf10612438829e9f84e40602c106b0cef8b1edde Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 28 Nov 2015 12:45:17 -0800 Subject: [PATCH 010/865] (stable) Partially revert D14511 to fix "INLINE COMMENTS" in mail Summary: Ref T9845. In Differential, this is not a remarkup block -- it's a mail section. `addTextSection()` has special magic behavior when handed a prebuilt section since D9375. Swapping to `addRemarkupSection()` causes the error in T9845 and renders nothing in the comment section. Even if it were a block of text, it would not be appropriate to add it as remarkup. This would incorrectly render comments in files like `__init__.py`, which are common on Python (the filename would render as "__init__.py"). Okay that's a bad example since it works fine but, uh, a file named `T123` would be no good or whatever. I'll realign T9845 to clean this up and fix it more durably. Test Plan: Sent myself some mail with inline comments, saw them in the mail. Reviewers: joshuaspence, chad Reviewed By: chad Maniphest Tasks: T9845 Differential Revision: https://secure.phabricator.com/D14589 --- src/applications/audit/editor/PhabricatorAuditEditor.php | 2 +- .../differential/editor/DifferentialTransactionEditor.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 47fa055388..51efdf175b 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -660,7 +660,7 @@ protected function buildMailBody( } if ($inlines) { - $body->addRemarkupSection( + $body->addTextSection( pht('INLINE COMMENTS'), $this->renderInlineCommentsForMail($object, $inlines)); } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index b2e946cf7f..fabd2511ba 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1212,7 +1212,7 @@ protected function buildMailBody( } if ($inlines) { - $body->addRemarkupSection( + $body->addTextSection( pht('INLINE COMMENTS'), $this->renderInlineCommentsForMail($object, $inlines)); } From c4df4f62decdb20462dbf84a21263de320cf2139 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 15 Dec 2015 14:48:55 -0800 Subject: [PATCH 011/865] Allow login to be disabled for authentication providers Summary: Fixes T9997. This was in the database since v0, I just never hooked up the UI since it wasn't previously meaningful. However, it now makes sense to have a provider like Asana with login disabled and use it only for integrations. Test Plan: Disabled login on a provider, verified it was no longer available for login/registration but still linkable. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9997 Differential Revision: https://secure.phabricator.com/D14794 --- .../config/PhabricatorAuthEditController.php | 21 +++++++++++++++++++ .../PhabricatorAuthProviderConfigEditor.php | 7 +++++++ .../auth/provider/PhabricatorAuthProvider.php | 4 ++++ ...abricatorAuthProviderConfigTransaction.php | 12 +++++++++++ 4 files changed, 44 insertions(+) diff --git a/src/applications/auth/controller/config/PhabricatorAuthEditController.php b/src/applications/auth/controller/config/PhabricatorAuthEditController.php index 09742c3726..21ba6ef99a 100644 --- a/src/applications/auth/controller/config/PhabricatorAuthEditController.php +++ b/src/applications/auth/controller/config/PhabricatorAuthEditController.php @@ -79,6 +79,7 @@ public function handleRequest(AphrontRequest $request) { $errors = array(); + $v_login = $config->getShouldAllowLogin(); $v_registration = $config->getShouldAllowRegistration(); $v_link = $config->getShouldAllowLink(); $v_unlink = $config->getShouldAllowUnlink(); @@ -104,6 +105,11 @@ public function handleRequest(AphrontRequest $request) { } } + $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) + ->setTransactionType( + PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN) + ->setNewValue($request->getInt('allowLogin', 0)); + $xactions[] = id(new PhabricatorAuthProviderConfigTransaction()) ->setTransactionType( PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION) @@ -199,6 +205,14 @@ public function handleRequest(AphrontRequest $request) { $config_name); } + $str_login = array( + phutil_tag('strong', array(), pht('Allow Login:')), + ' ', + pht( + 'Allow users to log in using this provider. If you disable login, '. + 'users can still use account integrations for this provider.'), + ); + $str_registration = array( phutil_tag('strong', array(), pht('Allow Registration:')), ' ', @@ -268,6 +282,13 @@ public function handleRequest(AphrontRequest $request) { ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel(pht('Allow')) + ->addCheckbox( + 'allowLogin', + 1, + $str_login, + $v_login)) + ->appendChild( + id(new AphrontFormCheckboxControl()) ->addCheckbox( 'allowRegistration', 1, diff --git a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php index cdb021678a..5599ff5364 100644 --- a/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthProviderConfigEditor.php @@ -15,6 +15,7 @@ public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE; + $types[] = PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN; $types[] = PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION; $types[] = PhabricatorAuthProviderConfigTransaction::TYPE_LINK; $types[] = PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK; @@ -36,6 +37,8 @@ protected function getCustomTransactionOldValue( } else { return (int)$object->getIsEnabled(); } + case PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN: + return (int)$object->getShouldAllowLogin(); case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: return (int)$object->getShouldAllowRegistration(); case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: @@ -59,6 +62,7 @@ protected function getCustomTransactionNewValue( switch ($xaction->getTransactionType()) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: + case PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN: case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: @@ -76,6 +80,8 @@ protected function applyCustomInternalTransaction( switch ($xaction->getTransactionType()) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: return $object->setIsEnabled($v); + case PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN: + return $object->setShouldAllowLogin($v); case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: return $object->setShouldAllowRegistration($v); case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: @@ -106,6 +112,7 @@ protected function mergeTransactions( $type = $u->getTransactionType(); switch ($type) { case PhabricatorAuthProviderConfigTransaction::TYPE_ENABLE: + case PhabricatorAuthProviderConfigTransaction::TYPE_LOGIN: case PhabricatorAuthProviderConfigTransaction::TYPE_REGISTRATION: case PhabricatorAuthProviderConfigTransaction::TYPE_LINK: case PhabricatorAuthProviderConfigTransaction::TYPE_UNLINK: diff --git a/src/applications/auth/provider/PhabricatorAuthProvider.php b/src/applications/auth/provider/PhabricatorAuthProvider.php index 55e193f481..9484109943 100644 --- a/src/applications/auth/provider/PhabricatorAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorAuthProvider.php @@ -121,6 +121,10 @@ public function shouldAllowLogin() { } public function shouldAllowRegistration() { + if (!$this->shouldAllowLogin()) { + return false; + } + return $this->getProviderConfig()->getShouldAllowRegistration(); } diff --git a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php index 3853c31a66..8314b652ce 100644 --- a/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php +++ b/src/applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php @@ -4,6 +4,7 @@ final class PhabricatorAuthProviderConfigTransaction extends PhabricatorApplicationTransaction { const TYPE_ENABLE = 'config:enable'; + const TYPE_LOGIN = 'config:login'; const TYPE_REGISTRATION = 'config:registration'; const TYPE_LINK = 'config:link'; const TYPE_UNLINK = 'config:unlink'; @@ -90,6 +91,17 @@ public function getTitle() { $this->renderHandleLink($author_phid)); } break; + case self::TYPE_LOGIN: + if ($new) { + return pht( + '%s enabled login.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s disabled login.', + $this->renderHandleLink($author_phid)); + } + break; case self::TYPE_REGISTRATION: if ($new) { return pht( From 70079f47db3d47c7f03a89c5e72f00d8e5996295 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Jan 2016 16:33:09 -0800 Subject: [PATCH 012/865] (stable) 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 @@ private function readRequest() { 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 35d90c3c4ac5f4d2dd488ada4460d9e876bf5279 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 09:08:43 -0800 Subject: [PATCH 013/865] (stable) 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 9652ed74000aa4146172db577cba67c4a7549e40 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jan 2016 09:18:53 -0800 Subject: [PATCH 014/865] (stable) 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 @@ private function buildPaymentMethodsSection(PhortuneAccount $account) { $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 @@ public function handleRequest(AphrontRequest $request) { } 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 @@ public function handleRequest(AphrontRequest $request) { ->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 ba2215986639987b1e53995d3dbeaf605e78a82e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Jan 2016 09:32:54 -0800 Subject: [PATCH 015/865] (stable) Fix two issues with repository monogram regular expressions Summary: Ref T4245. Fixes T10172. These regular expressions were simply incorrect: they intend ` (form one | form two) ` but were written as `( form one) | (form two )` which allowed stuff like "R2/R13" to be interpreted as a monogram because it matches `( form one)`. Test Plan: Parsed commit `ba46ffa6169c` from RTEMS repository, see T10172. Before patch, got an identical trace; after patch, clean import. Reviewers: chad, avivey Reviewed By: avivey Maniphest Tasks: T4245, T10172 Differential Revision: https://secure.phabricator.com/D15049 --- .../repository/phid/PhabricatorRepositoryRepositoryPHIDType.php | 2 +- .../repository/query/PhabricatorRepositoryQuery.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php index 203d17c3ba..c938b31a65 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php +++ b/src/applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php @@ -48,7 +48,7 @@ public function loadHandles( } public function canLoadNamedObject($name) { - return preg_match('/^r[A-Z]+|R[1-9]\d*\z/', $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 0a5e44304f..cbb2b1b3e9 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -65,7 +65,7 @@ public function withIdentifiers(array $identifiers) { continue; } - if (preg_match('/^(r[A-Z]+)|(R[1-9]\d*)\z/', $identifier)) { + if (preg_match('/^(r[A-Z]+|R[1-9]\d*)\z/', $identifier)) { $monograms[$identifier] = $identifier; continue; } From 482da7f5dfb332a117c75cb0868a5b203b84138c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 30 Jan 2016 16:03:13 -0800 Subject: [PATCH 016/865] (stable) Apply phutil_utf8ize() to stderr output from VCS commands prior to logging Summary: Ref T10228. Commands like `git-http-backend` can emit errors with raw bytes in the output. Sanitize these if present so we can log them in JSON format. Test Plan: Edited this into production. >_> sneaky sneaky <_< Reviewers: chad Reviewed By: chad Maniphest Tasks: T10228 Differential Revision: https://secure.phabricator.com/D15144 --- .../diffusion/controller/DiffusionServeController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 1d41d62efa..13290b9f41 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -466,7 +466,10 @@ private function serveGitRequest( if ($err) { return new PhabricatorVCSResponse( 500, - pht('Error %d: %s', $err, $stderr)); + pht( + 'Error %d: %s', + $err, + phutil_utf8ize($stderr))); } return id(new DiffusionGitResponse())->setGitData($stdout); From 71bda66870d8ef832f4d048b11282f9ae0086f05 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 30 Jan 2016 16:44:33 -0800 Subject: [PATCH 017/865] (stable) Decode "Content-Encoding: gzip" content Summary: Fixes T10228. When we receive a gzipped request (rare, but `git` may send them), decode it before providing it to the application. This fixes the issue with proxying certain requests described in T10228. Test Plan: - Applied this fix in production. - Cloned a problem repository cleanly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10228 Differential Revision: https://secure.phabricator.com/D15145 --- support/PhabricatorStartup.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 198cc1b244..bc7daf4ea6 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -129,7 +129,19 @@ public static function didStartup($start_time) { self::beginOutputCapture(); - self::$rawInput = (string)file_get_contents('php://input'); + if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) { + $encoding = trim($_SERVER['HTTP_CONTENT_ENCODING']); + } else { + $encoding = null; + } + + if ($encoding === 'gzip') { + $source_stream = 'compress.zlib://php://input'; + } else { + $source_stream = 'php://input'; + } + + self::$rawInput = (string)file_get_contents($source_stream); } From 9519625092a57ccfb63b2152c4eba7a30550ab20 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Tue, 9 Feb 2016 10:13:08 -0800 Subject: [PATCH 018/865] unbreak exception handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: fix T10306 Test Plan: ¯\_(ツ)_/¯ Reviewers: chad, #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Maniphest Tasks: T10306 Differential Revision: https://secure.phabricator.com/D15229 --- .../diffusion/controller/DiffusionServeController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 8f3eb364f6..8d131eca6d 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -100,7 +100,7 @@ public function handleRequest(AphrontRequest $request) { ->setResultCode(500) ->setProperties( array( - 'exception.class' => $ex->getClass(), + 'exception.class' => get_class($ex), 'exception.message' => $ex->getMessage(), )); } From 8b56e0082bff4807ea1ea275741d5f145804e0ee Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 10:17:40 -0800 Subject: [PATCH 019/865] (stable) Fix an issue with the Herald engine field value cache Summary: To improve the performance of Herald, we attempt to generate the value for each field (e.g., a task title) only once. For most field values this is cheap, but for some (like a commit's branches) it can be quite expensive. We only want to pay this cost once, so we cache field values. However, D12957 accidentally added a check where we bypass the cache and generate the value for every field, before reading the cache. This causes us to generate each field for every rule that uses it, plus one extra time. Instead, use the cache for this check, too. Also allow the cache to cache `null`, since it can be expensive to generate `null` even though the value isn't too interesting. The value of this early hit isn't even used (we only care if it throws or not). Test Plan: - Wrote a rule like "if any condition matches: branches contain a, branches contain b, branches contain c". - Put `phlog(new Exception())` in `DiffusionCommitBranchesHeraldField`. - Before patch, saw `bin/repository reparse --herald ` compute branches three times. - After patch, saw only one computation. - Verified field values in the transcript view Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15451 --- src/applications/herald/engine/HeraldEngine.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 00c063131a..df81117b80 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -274,7 +274,7 @@ public function doesRuleMatch( } else { foreach ($conditions as $condition) { try { - $object->getHeraldField($condition->getFieldName()); + $this->getConditionObjectValue($condition, $object); } catch (Exception $ex) { $reason = pht( 'Field "%s" does not exist!', @@ -366,14 +366,11 @@ protected function getConditionObjectValue( } public function getObjectFieldValue($field) { - if (isset($this->fieldCache[$field])) { - return $this->fieldCache[$field]; + if (!array_key_exists($field, $this->fieldCache)) { + $this->fieldCache[$field] = $this->object->getHeraldField($field); } - $result = $this->object->getHeraldField($field); - - $this->fieldCache[$field] = $result; - return $result; + return $this->fieldCache[$field]; } protected function getRuleEffects( From fea2389066edf3ad0a7547ae12d8e988428a4f5c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Apr 2016 10:34:13 -0700 Subject: [PATCH 020/865] (stable) Improve error and header behaviors for Mailgun received mail webhook Summary: Ref T10709. Two issues: - If a user sends an invalid `!command`, we can throw, which means we don't return HTTP 200. This makes Mailgun re-send the mail later. - We don't parse headers of the modern API correctly, so the "Message-ID" failsafe doesn't work. Parse them correctly. I //believe// Mailgun's API changed at some point. Test Plan: This is difficult to test exhaustively in isolation. I used Mailgun's web tools to verify the format of the hook request, and faked some requests locally. I'll keep an eye on this as it goes to production and make sure the fix is correct there. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10709 Differential Revision: https://secure.phabricator.com/D15575 --- ...ricatorMetaMTAMailgunReceiveController.php | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php index af5ca34712..467995a186 100644 --- a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php +++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php @@ -28,14 +28,14 @@ public function handleRequest(AphrontRequest $request) { pht('Mail signature is not valid. Check your Mailgun API key.')); } - $user = $request->getUser(); - - $raw_headers = $request->getStr('headers'); - $raw_headers = explode("\n", rtrim($raw_headers)); + $raw_headers = $request->getStr('message-headers'); $raw_dict = array(); - foreach (array_filter($raw_headers) as $header) { - list($name, $value) = explode(':', $header, 2); - $raw_dict[$name] = ltrim($value); + if (strlen($raw_headers)) { + $raw_headers = phutil_json_decode($raw_headers); + foreach ($raw_headers as $raw_header) { + list($name, $value) = $raw_header; + $raw_dict[$name] = $value; + } } $headers = array( @@ -65,9 +65,25 @@ public function handleRequest(AphrontRequest $request) { } } $received->setAttachments($file_phids); - $received->save(); - $received->processReceivedMail(); + try { + $received->save(); + $received->processReceivedMail(); + } catch (Exception $ex) { + // We can get exceptions here in two cases. + + // First, saving the message may throw if we have already received a + // message with the same Message ID. In this case, we're declining to + // process a duplicate message, so failing silently is correct. + + // Second, processing the message may throw (for example, if it contains + // an invalid !command). This will generate an email as a side effect, + // so we don't need to explicitly handle the exception here. + + // In these cases, we want to return HTTP 200. If we do not, MailGun will + // re-transmit the message later. + phlog($ex); + } $response = new AphrontWebpageResponse(); $response->setContent(pht("Got it! Thanks, Mailgun!\n")); From 908c29cb974dcb39a0e931508e55ae017385b7cf Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 Apr 2016 03:39:17 -0700 Subject: [PATCH 021/865] (stable) When proxying cluster HTTP requests, forward only selected headers In the live cluster, some subset of the forwarded headers are creating some issues for HTTP repository operations. --- src/aphront/AphrontRequest.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index 3a95b0ebda..9305d6b48b 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -754,14 +754,26 @@ public function newClusterProxyFuture($uri) { // NOTE: apache_request_headers() might provide a nicer way to do this, // but isn't available under FCGI until PHP 5.4.0. foreach ($_SERVER as $key => $value) { - if (preg_match('/^HTTP_/', $key)) { - // Unmangle the header as best we can. - $key = substr($key, strlen('HTTP_')); - $key = str_replace('_', ' ', $key); - $key = strtolower($key); - $key = ucwords($key); - $key = str_replace(' ', '-', $key); + if (!preg_match('/^HTTP_/', $key)) { + continue; + } + + // Unmangle the header as best we can. + $key = substr($key, strlen('HTTP_')); + $key = str_replace('_', ' ', $key); + $key = strtolower($key); + $key = ucwords($key); + $key = str_replace(' ', '-', $key); + + // By default, do not forward headers. + $should_forward = false; + + // Forward "X-Hgarg-..." headers. + if (preg_match('/^X-Hgarg-/', $key)) { + $should_forward = true; + } + if ($should_forward) { $headers[] = array($key, $value); $seen[$key] = true; } From 5ddaf5289862cce3957c5630eb11df31fb0786b8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 9 Apr 2016 11:50:16 -0700 Subject: [PATCH 022/865] (stable) Set time and date on Calendar Date Control form Summary: Recurring events will fatal a Calendar with this not set. `newDateTime` requires a date and time to be called property. I think this is correct fix? Fixes T10766 Test Plan: Build a recurring event, pull up /calendar/, see recurring events as expected. Previously, fatal. Reviewers: lpriestley, epriestley Reviewed By: epriestley Subscribers: CodeMouse92, Korvin Maniphest Tasks: T10766 Differential Revision: https://secure.phabricator.com/D15666 --- src/view/form/control/AphrontFormDateControlValue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index 114340006e..db2c7235c4 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -279,7 +279,7 @@ private function getFormatSeparator() { } public function getDateTime() { - return $this->newDateTime(); + return $this->newDateTime($this->valueDate, $this->valueTime); } private function getTimezone() { From ab63b1b2ae348be19ac3f7315de43bd2570a568b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 12 Apr 2016 19:25:00 -0700 Subject: [PATCH 023/865] (stable) Fix Passphrase Credential dialog Summary: Fixes T10772, not sure why this fails, but reverting the code back to old dialog call works. Test Plan: - Try to add a new credential when importing a repository. - Also created a new credential normally, via Passphrase. - Also edited a credential. Reviewers: chad Reviewed By: chad Subscribers: Korvin Maniphest Tasks: T10772 Differential Revision: https://secure.phabricator.com/D15691 --- .../controller/PassphraseCredentialEditController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index 4aa687ddc1..a814b5dc72 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -21,6 +21,7 @@ public function handleRequest(AphrontRequest $request) { } $type = $this->getCredentialType($credential->getCredentialType()); + $type_const = $type->getCredentialType(); $is_new = false; } else { @@ -228,6 +229,7 @@ public function handleRequest(AphrontRequest $request) { $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('isInitialized', true) + ->addHiddenInput('type', $type_const) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') From f75b1cf562c0c3646324864851d693ef1069a068 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Apr 2016 05:38:09 -0700 Subject: [PATCH 024/865] (stable) Warn users about remote code execution in older Git Summary: Ref T10832. Raise a setup warning for out-of-date versions of `git`. Test Plan: {F1224632} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10832 Differential Revision: https://secure.phabricator.com/D15745 --- .../check/PhabricatorBinariesSetupCheck.php | 126 +++++++++--------- 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/src/applications/config/check/PhabricatorBinariesSetupCheck.php b/src/applications/config/check/PhabricatorBinariesSetupCheck.php index c33dcae36f..c3c0740cfa 100644 --- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php +++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php @@ -102,15 +102,24 @@ protected function executeChecks() { $version = null; switch ($vcs['versionControlSystem']) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $minimum_version = null; - $bad_versions = array(); + $bad_versions = array( + '< 2.7.4' => pht( + 'Prior to 2.7.4, Git contains two remote code execution '. + 'vulnerabilities which allow an attacker to take control of a '. + 'system by crafting a commit which affects very long paths, '. + 'then pushing it or tricking a victim into fetching it. This '. + 'is a severe security vulnerability.'), + ); list($err, $stdout, $stderr) = exec_manual('git --version'); $version = trim(substr($stdout, strlen('git version '))); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $minimum_version = '1.5'; $bad_versions = array( - '1.7.1' => pht( + // We need 1.5 for "--depth", see T7228. + '< 1.5' => pht( + 'The minimum supported version of Subversion is 1.5, which '. + 'was released in 2008.'), + '= 1.7.1' => pht( 'This version of Subversion has a bug where `%s` does not work '. 'for files added in rN (Subversion issue #2873), fixed in 1.7.2.', 'svn diff -c N'), @@ -119,12 +128,15 @@ protected function executeChecks() { $version = trim($stdout); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $minimum_version = '1.9'; $bad_versions = array( - '2.1' => pht( + // We need 1.9 for HTTP cloning, see T3046. + '< 1.9' => pht( + 'The minimum supported version of Mercurial is 1.9, which was '. + 'released in 2011.'), + '= 2.1' => pht( 'This version of Mercurial returns a bad exit code '. 'after a successful pull.'), - '2.2' => pht( + '= 2.2' => pht( 'This version of Mercurial has a significant memory leak, fixed '. 'in 2.2.1. Pushing fails with this version as well; see %s.', 'T3046#54922'), @@ -136,21 +148,26 @@ protected function executeChecks() { if ($version === null) { $this->raiseUnknownVersionWarning($binary); } else { - if ($minimum_version && - version_compare($version, $minimum_version, '<')) { - $this->raiseMinimumVersionWarning( - $binary, - $minimum_version, - $version); - } + $version_details = array(); - foreach ($bad_versions as $bad_version => $details) { - if ($bad_version === $version) { - $this->raiseBadVersionWarning( - $binary, - $bad_version); + foreach ($bad_versions as $spec => $details) { + list($operator, $bad_version) = explode(' ', $spec, 2); + $is_bad = version_compare($version, $bad_version, $operator); + if ($is_bad) { + $version_details[] = pht( + '(%s%s) %s', + $operator, + $bad_version, + $details); } } + + if ($version_details) { + $this->raiseBadVersionWarning( + $binary, + $version, + $version_details); + } } } @@ -223,57 +240,34 @@ private function raiseUnknownVersionWarning($binary) { pht('Report this Issue to the Upstream')); } - private function raiseMinimumVersionWarning( - $binary, - $minimum_version, - $version) { + private function raiseBadVersionWarning($binary, $version, array $problems) { + $summary = pht( + 'This server has a known bad version of "%s".', + $binary); - switch ($binary) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $summary = pht( - "The '%s' binary is version %s and Phabricator requires version ". - "%s or higher.", - $binary, - $version, - $minimum_version); - $message = pht( - "Please upgrade the '%s' binary to a more modern version.", - $binary); - $this->newIssue('bin.'.$binary) - ->setShortName(pht("Unsupported '%s' Version", $binary)) - ->setName(pht("Unsupported '%s' Version", $binary)) - ->setSummary($summary) - ->setMessage($summary.' '.$message); - break; - } - } + $message = array(); - private function raiseBadVersionWarning($binary, $bad_version) { - switch ($binary) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $summary = pht( - "The '%s' binary is version %s which has bugs that break ". - "Phabricator.", - $binary, - $bad_version); - $message = pht( - "Please upgrade the '%s' binary to a more modern version.", - $binary); - $this->newIssue('bin.'.$binary) - ->setShortName(pht("Unsupported '%s' Version", $binary)) - ->setName(pht("Unsupported '%s' Version", $binary)) - ->setSummary($summary) - ->setMessage($summary.' '.$message); - break; - } + $message[] = pht( + 'This server has a known bad version of "%s" installed ("%s"). This '. + 'version is not supported, or contains important bugs or security '. + 'vulnerabilities which are fixed in a newer version.', + $binary, + $version); + $message[] = pht('You should upgrade this software.'); + $message[] = pht('The known issues with this old version are:'); + + foreach ($problems as $problem) { + $message[] = $problem; + } + + $message = implode("\n\n", $message); + + $this->newIssue("bin.{$binary}.bad-version") + ->setName(pht('Unsupported/Insecure "%s" Version', $binary)) + ->setSummary($summary) + ->setMessage($message); } } From 08cc80cc5ce0fa90be81878bad6c3174f3a2b02a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 10:42:05 -0700 Subject: [PATCH 025/865] (stable) Fix one more "Reviewers" wire format issue Summary: Fixes T11010. This also needs to be inflated until we fix the whole client/server responsibility issue here. Test Plan: - Created a revision while observing error log, no error. - Disabled "allow self accept", tried to make myself a reviewer, got rejected with an error message. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11010 Differential Revision: https://secure.phabricator.com/D15966 --- .../differential/customfield/DifferentialReviewersField.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index d23dac53d9..f77f8ab749 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -242,6 +242,7 @@ public function validateCommitMessageValue($value) { $config_self_accept_key = 'differential.allow-self-accept'; $allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key); + $value = $this->inflateReviewers($value); foreach ($value as $spec) { $phid = $spec['phid']; From 70b194398e45e47a6b705eb5ef542113f5a274ad Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 17:04:27 -0700 Subject: [PATCH 026/865] (stable) Fix "Reviewers" validation issue with empty reviewers Summary: Fixes T11021. Test Plan: Created a revision without reviewers. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11021 Differential Revision: https://secure.phabricator.com/D15968 --- .../differential/customfield/DifferentialReviewersField.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php index f77f8ab749..41a4cf8f26 100644 --- a/src/applications/differential/customfield/DifferentialReviewersField.php +++ b/src/applications/differential/customfield/DifferentialReviewersField.php @@ -237,6 +237,10 @@ public function renderCommitMessageValue(array $handles) { } public function validateCommitMessageValue($value) { + if (!$value) { + return; + } + $author_phid = $this->getObject()->getAuthorPHID(); $config_self_accept_key = 'differential.allow-self-accept'; From 9f399247b1b107e88f0abdf215b7b9bdf4cf0223 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 May 2016 17:13:12 -0700 Subject: [PATCH 027/865] (stable) Fix a possible fatal on the first push to a cluster repository Summary: Fixes T11020. I think this resolves things -- `$new_version` (set above) should be used, not `$new_log` directly. Specifically, we would get into trouble if the initial push failed for some reason (working copy not initialized yet, commit hook rejected, etc). Test Plan: Made a bad push to a new repository. Saw it freeze before the patch and succeed afterwards. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11020 Differential Revision: https://secure.phabricator.com/D15969 --- .../diffusion/protocol/DiffusionRepositoryClusterEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index e3c70fecd9..b271c16741 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -391,7 +391,7 @@ public function synchronizeWorkingCopyAfterWrite() { $repository_phid, $device_phid, $this->clusterWriteVersion, - $new_log->getID(), + $new_version, $this->clusterWriteOwner); $did_release = true; break; From 1558175ec84c7b13ba3f692b897464901bc00578 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 11 Jun 2016 04:44:40 -0700 Subject: [PATCH 028/865] (stable) Promote 2016 Week 24 From b26ecb189e486998acf469db0cba87993ef6ad1b Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 13 Jun 2016 05:22:00 -0700 Subject: [PATCH 029/865] (stable) Remove all uses of PhutilGitURI in Phabricator Summary: Ref T11137. This class is removed in D16099. Depends on D16099. `PhutilURI` now attempts to "just work" with Git-style URIs, so at least in theory we can just delete all of this code and pretend it does not exist. (I've left "Display URI" and "Effective URI" as distinct, at least for now, because I think the distinction may be relevant in the future even though it isn't right now, and to keep this diff small, although I may go remove one after I think about this for a bit.) Test Plan: - Created a new Git repository with a Git URI. - Pulled/updated it, which now works correctly and should resolve the original issue in T11137. - Verified that daemons now align the origin to a Git-style URI with a relative path, which should resolve the original issue in T11004. - Grepped for `PhutilGitURI`. - Also grepped in `arcanist/`, but found no matches, so no patch for that. - Checked display/conduit URIs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11137 Differential Revision: https://secure.phabricator.com/D16100 --- ...rmasterCircleCIBuildStepImplementation.php | 5 -- .../PhabricatorRepositoryURINormalizer.php | 20 +----- .../storage/PhabricatorRepository.php | 45 +++---------- .../storage/PhabricatorRepositoryURI.php | 63 ++++++------------- 4 files changed, 29 insertions(+), 104 deletions(-) diff --git a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php index 365cf657de..e74e25a395 100644 --- a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php @@ -69,11 +69,6 @@ public static function getGitHubPath($uri) { $uri_object = new PhutilURI($uri); $domain = $uri_object->getDomain(); - if (!strlen($domain)) { - $uri_object = new PhutilGitURI($uri); - $domain = $uri_object->getDomain(); - } - $domain = phutil_utf8_strtolower($domain); switch ($domain) { case 'github.com': diff --git a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php index 42224c9553..27e93535ab 100644 --- a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php +++ b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php @@ -78,16 +78,7 @@ public function getPath() { switch ($this->type) { case self::TYPE_GIT: $uri = new PhutilURI($this->uri); - if ($uri->getProtocol()) { - return $uri->getPath(); - } - - $uri = new PhutilGitURI($this->uri); - if ($uri->getDomain()) { - return $uri->getPath(); - } - - return $this->uri; + return $uri->getPath(); case self::TYPE_SVN: case self::TYPE_MERCURIAL: $uri = new PhutilURI($this->uri); @@ -136,14 +127,7 @@ public function getNormalizedDomain() { $domain = null; $uri = new PhutilURI($this->uri); - if ($uri->getProtocol()) { - $domain = $uri->getDomain(); - } - - if (!strlen($domain)) { - $uri = new PhutilGitURI($this->uri); - $domain = $uri->getDomain(); - } + $domain = $uri->getDomain(); if (!strlen($domain)) { $domain = ''; diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 84c7cfd055..b771066ed6 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1201,27 +1201,19 @@ public function getPublicCloneURI() { */ public function getRemoteProtocol() { $uri = $this->getRemoteURIObject(); - - if ($uri instanceof PhutilGitURI) { - return 'ssh'; - } else { - return $uri->getProtocol(); - } + return $uri->getProtocol(); } /** - * Get a parsed object representation of the repository's remote URI. This - * may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git - * URI (returned as a @{class@libphutil:PhutilGitURI}). + * Get a parsed object representation of the repository's remote URI.. * - * @return wild A @{class@libphutil:PhutilURI} or - * @{class@libphutil:PhutilGitURI}. + * @return wild A @{class@libphutil:PhutilURI}. * @task uri */ public function getRemoteURIObject() { $raw_uri = $this->getDetail('remote-uri'); - if (!$raw_uri) { + if (!strlen($raw_uri)) { return new PhutilURI(''); } @@ -1229,17 +1221,7 @@ public function getRemoteURIObject() { return new PhutilURI('file://'.$raw_uri); } - $uri = new PhutilURI($raw_uri); - if ($uri->getProtocol()) { - return $uri; - } - - $uri = new PhutilGitURI($raw_uri); - if ($uri->getDomain()) { - return $uri; - } - - throw new Exception(pht("Remote URI '%s' could not be parsed!", $raw_uri)); + return new PhutilURI($raw_uri); } @@ -1666,27 +1648,14 @@ public function writeStatusMessage( return $this; } - public static function getRemoteURIProtocol($raw_uri) { - $uri = new PhutilURI($raw_uri); - if ($uri->getProtocol()) { - return strtolower($uri->getProtocol()); - } - - $git_uri = new PhutilGitURI($raw_uri); - if (strlen($git_uri->getDomain()) && strlen($git_uri->getPath())) { - return 'ssh'; - } - - return null; - } - public static function assertValidRemoteURI($uri) { if (trim($uri) != $uri) { throw new Exception( pht('The remote URI has leading or trailing whitespace.')); } - $protocol = self::getRemoteURIProtocol($uri); + $uri_object = new PhutilURI($uri); + $protocol = $uri_object->getProtocol(); // Catch confusion between Git/SCP-style URIs and normal URIs. See T3619 // for discussion. This is usually a user adding "ssh://" to an implicit diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index 853dcfa1b3..15e25205c8 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -191,11 +191,6 @@ public function getDefaultIOType() { return self::IO_NONE; } - - public function getDisplayURI() { - return $this->getURIObject(false); - } - public function getNormalizedURI() { $vcs = $this->getRepository()->getVersionControlSystem(); @@ -216,8 +211,12 @@ public function getNormalizedURI() { return $normal_uri->getNormalizedURI(); } + public function getDisplayURI() { + return $this->getURIObject(); + } + public function getEffectiveURI() { - return $this->getURIObject(true); + return $this->getURIObject(); } public function getURIEnvelope() { @@ -243,11 +242,10 @@ public function getURIEnvelope() { return new PhutilOpaqueEnvelope((string)$uri); } - private function getURIObject($normalize) { + private function getURIObject() { // Users can provide Git/SCP-style URIs in the form "user@host:path". - // These are equivalent to "ssh://user@host/path". We use the more standard - // form internally, and convert to it if we need to specify a port number, - // but try to preserve what the user typed when displaying the URI. + // In the general case, these are not equivalent to any "ssh://..." form + // because the path is relative. if ($this->isBuiltin()) { $builtin_protocol = $this->getForcedProtocol(); @@ -271,43 +269,22 @@ private function getURIObject($normalize) { // with it; this should always be provided by the associated credential. $uri->setPass(null); - if (!$uri->getProtocol()) { - $git_uri = new PhutilGitURI($raw_uri); - - // We must normalize this Git-style URI into a normal URI - $must_normalize = ($port && ($port != $default_ports['ssh'])); - if ($must_normalize || $normalize) { - $domain = $git_uri->getDomain(); - - - $uri = id(new PhutilURI("ssh://{$domain}")) - ->setUser($git_uri->getUser()) - ->setPath($git_uri->getPath()); - } else { - $uri = $git_uri; - } + $protocol = $this->getForcedProtocol(); + if ($protocol) { + $uri->setProtocol($protocol); } - $is_normal = ($uri instanceof PhutilURI); - - if ($is_normal) { - $protocol = $this->getForcedProtocol(); - if ($protocol) { - $uri->setProtocol($protocol); - } - - if ($port) { - $uri->setPort($port); - } + if ($port) { + $uri->setPort($port); + } - // Remove any explicitly set default ports. - $uri_port = $uri->getPort(); - $uri_protocol = $uri->getProtocol(); + // Remove any explicitly set default ports. + $uri_port = $uri->getPort(); + $uri_protocol = $uri->getProtocol(); - $uri_default = idx($default_ports, $uri_protocol); - if ($uri_default && ($uri_default == $uri_port)) { - $uri->setPort(null); - } + $uri_default = idx($default_ports, $uri_protocol); + if ($uri_default && ($uri_default == $uri_port)) { + $uri->setPort(null); } $user = $this->getForcedUser(); From 83121f78980be7d7089cc9cb5e8e3f2c0eb33573 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 20 Jun 2016 11:07:00 -0700 Subject: [PATCH 030/865] (stable) Handle tag tags properly in discovery Summary: Fixes T11180. In Git, it's possible to tag a tag (????). When you do, we try to log the tag-object, which automatically resolves to the commit and fails. Just skip these. If "A" points at "B" which points at "C", it's fine to ignore "A" and "B" since we'll get the same stuff when we process "C". Test Plan: - Tagged a tag. - Pushed it. - Discovered it. - Before patch: got exception similar to the one in T11180. - After patch: got tag-tag skipped. Also got slightly better error messages. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11180 Differential Revision: https://secure.phabricator.com/D16149 --- .../daemon/PhabricatorGitGraphStream.php | 18 ++++++++++++++---- .../PhabricatorRepositoryDiscoveryEngine.php | 16 +++++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/applications/repository/daemon/PhabricatorGitGraphStream.php b/src/applications/repository/daemon/PhabricatorGitGraphStream.php index 669cf9c105..b175053528 100644 --- a/src/applications/repository/daemon/PhabricatorGitGraphStream.php +++ b/src/applications/repository/daemon/PhabricatorGitGraphStream.php @@ -5,6 +5,7 @@ final class PhabricatorGitGraphStream private $repository; private $iterator; + private $startCommit; private $parents = array(); private $dates = array(); @@ -14,6 +15,7 @@ public function __construct( $start_commit = null) { $this->repository = $repository; + $this->startCommit = $start_commit; if ($start_commit !== null) { $future = $repository->getLocalCommandFuture( @@ -82,10 +84,18 @@ private function parseUntil($commit) { } } - throw new Exception( - pht( - "No such commit '%s' in repository!", - $commit)); + if ($this->startCommit !== null) { + throw new Exception( + pht( + 'Commit "%s" is not a reachable ancestor of "%s".', + $commit, + $this->startCommit)); + } else { + throw new Exception( + pht( + 'Commit "%s" is not a reachable ancestor of any ref.', + $commit)); + } } private function isParsed($commit) { diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index cdb7e11d03..fd8c57e9b4 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -157,7 +157,12 @@ private function discoverGitCommits() { $name = $ref->getShortName(); $commit = $ref->getCommitIdentifier(); - $this->log(pht('Examining ref "%s", at "%s".', $name, $commit)); + $this->log( + pht( + 'Examining "%s" (%s) at "%s".', + $name, + $ref->getRefType(), + $commit)); if (!$repository->shouldTrackRef($ref)) { $this->log(pht('Skipping, ref is untracked.')); @@ -169,6 +174,15 @@ private function discoverGitCommits() { continue; } + // In Git, it's possible to tag a tag. We just skip these, we'll discover + // them when we process the target tag. See T11180. + $fields = $ref->getRawFields(); + $tag_type = idx($fields, '*objecttype'); + if ($tag_type == 'tag') { + $this->log(pht('Skipping, this is a tag of a tag.')); + continue; + } + $this->log(pht('Looking for new commits.')); $head_refs = $this->discoverStreamAncestry( From fd33fbbfae8d980b8fd96d841a74d027752f396f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jul 2016 05:17:05 -0700 Subject: [PATCH 031/865] (stable) Fix an XSS issue where Diffusion files exceeding the highlighting byte limit were not properly escaped Fixes T11257. Auditors: chad --- .../controller/DiffusionBrowseController.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 30c2b8265d..2d0ea7770b 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -682,17 +682,21 @@ private function buildCorpus( $blame_commits, $show_blame); } else { - if ($can_highlight) { - require_celerity_resource('syntax-highlighting-css'); + require_celerity_resource('syntax-highlighting-css'); + if (!$can_highlight) { $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, $file_corpus); - $lines = phutil_split_lines($highlighted); } else { - $lines = phutil_split_lines($file_corpus); + // Highlight as plain text to escape the content properly. + $highlighted = PhabricatorSyntaxHighlighter::highlightWithLanguage( + 'txt', + $file_corpus); } + $lines = phutil_split_lines($highlighted); + $rows = $this->buildDisplayRows( $lines, $blame_list, From 58375fa9e6db4a389fd6029ee1ad14ddb0dc9e90 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jul 2016 05:22:55 -0700 Subject: [PATCH 032/865] (stable) Fix a flipped higlight vs no-highlight condition Ref T11257. Auditors: chad --- .../diffusion/controller/DiffusionBrowseController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 2d0ea7770b..3ae076a5d0 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -684,7 +684,7 @@ private function buildCorpus( } else { require_celerity_resource('syntax-highlighting-css'); - if (!$can_highlight) { + if ($can_highlight) { $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, $file_corpus); From 46ceba728fee8a775e2ddf0cdae332a0679413a4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 Jul 2016 05:55:45 -0700 Subject: [PATCH 033/865] (stable) Fix an issue with creating new Repository URIs via the Web UI Summary I broke this in D16237: that made the CLI workflow work, but we attach the repository earlier in the web workflow and won't have one when we arrive here. Test Plan: Created a new repository URI from the web UI. Auditors: chad --- .../diffusion/editor/DiffusionURIEditor.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 2ff0854a89..0b7096db3c 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -78,10 +78,12 @@ protected function applyCustomInternalTransaction( } else { $old_uri = null; - // When creating a URI, we may not have processed the repository - // transaction yet. Attach the repository here to make sure we - // have it for the calls below. - $object->attachRepository($this->repository); + // When creating a URI via the API, we may not have processed the + // repository transaction yet. Attach the repository here to make + // sure we have it for the calls below. + if ($this->repository) { + $object->attachRepository($this->repository); + } } $object->setURI($xaction->getNewValue()); From 83c857e6a6ca4af4f5ce50204d5e8ffbbcc5c035 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 10 Jul 2016 07:19:47 -0700 Subject: [PATCH 034/865] (stable) In Git, only use "--find-copies-harder" on small diffs Summary: Ref T10423. This flag can cause `git diff` to take an enormously long time (the problem case was a 5M line, 20K file commit). Instead: - Run without the flag first. - If that shows that the diff is definitely small, try again with the flag. - If that works, return the slower, better output. - If the fast diff affects too many paths or generating the slow diff takes too long, return the faster, slightly worse output. The quality of the output differs in how well Git is able to detect "M" and "C" (moves and copies of files). For example, if you copy `src/` to `srcpro/`, the fast output may not show that you copied files. The slow output will. I think this is rarely useful for large copies anyway: it's interesting if a 1-2 file diff is a copy, but usually obvious/uninteresting if a 500-file diff is a copy. Test Plan: - Ran `bin/repository reparse --change rXnnn` on Git changes. - Saw fast and slow commands execute normally. - Tried on a large diff, saw only the fast command execute. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10423 Differential Revision: https://secure.phabricator.com/D16266 --- ...nternalGitRawDiffQueryConduitAPIMethod.php | 71 ++++++++++++++++--- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php index 1e4c906322..ac241a282f 100644 --- a/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionInternalGitRawDiffQueryConduitAPIMethod.php @@ -28,6 +28,7 @@ protected function defineCustomParamTypes() { protected function getResult(ConduitAPIRequest $request) { $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); + $commit = $request->getValue('commit'); if (!$repository->isGit()) { throw new Exception( @@ -44,30 +45,78 @@ protected function getResult(ConduitAPIRequest $request) { list($parents) = $repository->execxLocalCommand( 'log -n1 --format=%s %s', '%P', - $request->getValue('commit')); - + $commit); $use_log = !strlen(trim($parents)); + + // First, get a fast raw diff without "--find-copies-harder". This flag + // produces better results for moves and copies, but is explosively slow + // for large changes to large repositories. See T10423. + $raw = $this->getRawDiff($repository, $commit, $use_log, false); + + // If we got a normal-sized diff (no more than 100 modified files), we'll + // try using "--find-copies-harder" to improve the output. This improved + // output is mostly useful for small changes anyway. + $try_harder = (substr_count($raw, "\n") <= 100); + if ($try_harder) { + try { + $raw = $this->getRawDiff($repository, $commit, $use_log, true); + } catch (Exception $ex) { + // Just ignore any exception we hit, we'll use the fast output + // instead. + } + } + + return $raw; + } + + private function getRawDiff( + PhabricatorRepository $repository, + $commit, + $use_log, + $try_harder) { + + $flags = array( + '-n1', + '-M', + '-C', + '-B', + '--raw', + '-t', + '--abbrev=40', + ); + + if ($try_harder) { + $flags[] = '--find-copies-harder'; + } + if ($use_log) { // This is the first commit so we need to use "log". We know it's not a // merge commit because it couldn't be merging anything, so this is safe. // NOTE: "--pretty=format: " is to disable diff output, we only want the // part we get from "--raw". - list($raw) = $repository->execxLocalCommand( - 'log -n1 -M -C -B --find-copies-harder --raw -t '. - '--pretty=format: --abbrev=40 %s', - $request->getValue('commit')); + $future = $repository->getLocalCommandFuture( + 'log %Ls --pretty=format: %s', + $flags, + $commit); } else { // Otherwise, we can use "diff", which will give us output for merges. // We diff against the first parent, as this is generally the expectation // and results in sensible behavior. - list($raw) = $repository->execxLocalCommand( - 'diff -n1 -M -C -B --find-copies-harder --raw -t '. - '--abbrev=40 %s^1 %s', - $request->getValue('commit'), - $request->getValue('commit')); + $future = $repository->getLocalCommandFuture( + 'diff %Ls %s^1 %s', + $flags, + $commit, + $commit); + } + + // Don't spend more than 30 seconds generating the slower output. + if ($try_harder) { + $future->setTimeout(30); } + list($raw) = $future->resolvex(); + return $raw; } From 9da15fd7ab7071e3b7361463229abd4e918d9d94 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 9 Jul 2016 06:47:32 -0700 Subject: [PATCH 035/865] (stable) By default, do not save queries when executing Conduit "*.search" calls Summary: Fixes T11304. Prior to this change, we did an unnecessary write on every "*.search" call (this write didn't always actually write a row, since we only save //unique// saved queries, but still doesn't do anything useful ever, currently). Instead, change this to not-write by default. We could add an "oh, and also I want you to do a write" option later, which would let us implement something like `arc query-stuff` which says "To see more results, view this URI in your browser: ...". (It's possible to run one of these methods with an existing SavedQuery by using the key, so we still sometimes have a queryKey to return.) Test Plan: Ran `almanac.service.search`, used DarkConsole to verify that no serachengine writes occurred. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11304 Differential Revision: https://secure.phabricator.com/D16263 --- .../search/engine/PhabricatorApplicationSearchEngine.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 96363ed947..d7268facb3 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1160,7 +1160,11 @@ public function buildConduitResponse( $saved_query->setParameter($field->getKey(), $value); } - $this->saveQuery($saved_query); + // NOTE: Currently, when running an ad-hoc query we never persist it into + // a saved query. We might want to add an option to do this in the future + // (for example, to enable a CLI-to-Web workflow where user can view more + // details about results by following a link), but have no use cases for + // it today. If we do identify a use case, we could save the query here. $query = $this->buildQueryFromSavedQuery($saved_query); $pager = $this->newPagerForSavedQuery($saved_query); @@ -1234,6 +1238,7 @@ public function buildConduitResponse( 'data' => $data, 'maps' => $method->getQueryMaps($query), 'query' => array( + // This may be `null` if we have not saved the query. 'queryKey' => $saved_query->getQueryKey(), ), 'cursor' => array( From 3f7021e0d88b0c8de0357f346899bd7c66eba164 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 29 Aug 2016 20:09:43 -0700 Subject: [PATCH 036/865] (stable) Fix search results with tables, fatals in Phortune Summary: Previously we collapsed all table search results, but the new UI doesn't need it. Remove unused methods and fix CSS. Test Plan: Legalpad Signatures, Phortune Accounts. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D16469 --- .../controller/PhortuneAccountListController.php | 4 ++-- .../controller/PhortuneProductListController.php | 2 +- .../phortune/query/PhortuneMerchantSearchEngine.php | 2 +- .../PhabricatorApplicationSearchController.php | 3 --- .../view/PhabricatorApplicationSearchResultView.php | 10 ---------- .../css/application/search/application-search-view.css | 5 +++++ 6 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/applications/phortune/controller/PhortuneAccountListController.php b/src/applications/phortune/controller/PhortuneAccountListController.php index 4b7521ceaa..d95fd709d5 100644 --- a/src/applications/phortune/controller/PhortuneAccountListController.php +++ b/src/applications/phortune/controller/PhortuneAccountListController.php @@ -39,7 +39,7 @@ public function handleRequest(AphrontRequest $request) { ->setHeader($account->getName()) ->setHref($this->getApplicationURI($account->getID().'/')) ->setObject($account) - ->setIcon('fa-credit-card'); + ->setImageIcon('fa-credit-card'); $payment_list->addItem($item); } @@ -71,7 +71,7 @@ public function handleRequest(AphrontRequest $request) { ->setHeader($merchant->getName()) ->setHref($this->getApplicationURI('/merchant/'.$merchant->getID().'/')) ->setObject($merchant) - ->setIcon('fa-bank'); + ->setImageIcon('fa-bank'); $merchant_list->addItem($item); } diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php index eeb594d650..ee84581ae1 100644 --- a/src/applications/phortune/controller/PhortuneProductListController.php +++ b/src/applications/phortune/controller/PhortuneProductListController.php @@ -41,7 +41,7 @@ public function handleRequest(AphrontRequest $request) { ->setHeader($product->getProductName()) ->setHref($view_uri) ->addAttribute($price->formatForDisplay()) - ->setIcon('fa-gift'); + ->setImageIcon('fa-gift'); $product_list->addItem($item); } diff --git a/src/applications/phortune/query/PhortuneMerchantSearchEngine.php b/src/applications/phortune/query/PhortuneMerchantSearchEngine.php index 1806af371e..d37c1455b3 100644 --- a/src/applications/phortune/query/PhortuneMerchantSearchEngine.php +++ b/src/applications/phortune/query/PhortuneMerchantSearchEngine.php @@ -74,7 +74,7 @@ protected function renderResultList( ->setHeader($merchant->getName()) ->setHref('/phortune/merchant/'.$merchant->getID().'/') ->setObject($merchant) - ->setIcon('fa-bank'); + ->setImageIcon('fa-bank'); $list->addItem($item); } diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index 164e9aabbc..a463e50471 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -270,9 +270,6 @@ private function processSearchRequest() { if ($list->getContent()) { $box->appendChild($list->getContent()); } - if ($list->getCollapsed()) { - $box->setCollapsed(true); - } $result_header = $list->getHeader(); if ($result_header) { diff --git a/src/applications/search/view/PhabricatorApplicationSearchResultView.php b/src/applications/search/view/PhabricatorApplicationSearchResultView.php index 5bcfdcf675..1c3f4aad65 100644 --- a/src/applications/search/view/PhabricatorApplicationSearchResultView.php +++ b/src/applications/search/view/PhabricatorApplicationSearchResultView.php @@ -12,7 +12,6 @@ final class PhabricatorApplicationSearchResultView extends Phobject { private $content = null; private $infoView = null; private $actions = array(); - private $collapsed = null; private $noDataString; private $crumbs = array(); private $header; @@ -70,15 +69,6 @@ public function getActions() { return $this->actions; } - public function setCollapsed($collapsed) { - $this->collapsed = $collapsed; - return $this; - } - - public function getCollapsed() { - return $this->collapsed; - } - public function setNoDataString($nodata) { $this->noDataString = $nodata; return $this; diff --git a/webroot/rsrc/css/application/search/application-search-view.css b/webroot/rsrc/css/application/search/application-search-view.css index 24ee18c847..4a573f5111 100644 --- a/webroot/rsrc/css/application/search/application-search-view.css +++ b/webroot/rsrc/css/application/search/application-search-view.css @@ -16,6 +16,11 @@ border-bottom: 1px solid {$thinblueborder}; } +.application-search-view .phui-object-box.phui-object-box-collapsed + .phui-header-shell { + padding: 20px 8px; +} + .application-search-results .phui-profile-header.phui-header-shell .phui-header-header { font-size: 20px; From 492fd29a1bd4d48dac1acdf3c89475d333af863b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 30 Aug 2016 07:37:19 -0700 Subject: [PATCH 037/865] (stable) Update the resource map on "stable". --- resources/celerity/map.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 9fd15abad3..097785c4da 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -100,7 +100,7 @@ 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', - 'rsrc/css/application/search/application-search-view.css' => 'b3e0e5ef', + 'rsrc/css/application/search/application-search-view.css' => 'be6454ec', 'rsrc/css/application/search/search-results.css' => '7dea472c', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', @@ -542,7 +542,7 @@ 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', - 'application-search-view-css' => 'b3e0e5ef', + 'application-search-view-css' => 'be6454ec', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'changeset-view-manager' => 'a2828756', From ca30df847e4e99aec46dd97c7bd9b4f7d8542cab Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Sep 2016 18:13:55 -0700 Subject: [PATCH 038/865] (stable) Rebuild Celerity map on stable. --- resources/celerity/map.php | 78 +++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 097785c4da..9cdac5d965 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'ed7ae7bb', - 'core.pkg.js' => 'b562c3db', + 'core.pkg.css' => 'ad6a3591', + 'core.pkg.js' => '2b8af4e4', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3fb7f532', 'differential.pkg.js' => '634399e9', @@ -32,25 +32,26 @@ 'rsrc/css/aphront/typeahead.css' => 'd4f16145', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => '3b0d39f7', - 'rsrc/css/application/base/notification-menu.css' => 'f31c0bde', + 'rsrc/css/application/base/main-menu-view.css' => 'e862571a', + 'rsrc/css/application/base/notification-menu.css' => 'b3ab500d', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '027ba77e', - 'rsrc/css/application/base/standard-page-view.css' => 'e709f6d0', + 'rsrc/css/application/base/standard-page-view.css' => '2b592894', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', + 'rsrc/css/application/config/config-page.css' => '8798e14f', 'rsrc/css/application/config/config-template.css' => '8e6c6fcd', 'rsrc/css/application/config/config-welcome.css' => '035aa483', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '86396117', - 'rsrc/css/application/conpherence/menu.css' => 'f99fee4c', - 'rsrc/css/application/conpherence/message-pane.css' => '5897d3ac', + 'rsrc/css/application/conpherence/menu.css' => '90bdf85c', + 'rsrc/css/application/conpherence/message-pane.css' => '5c7b7b17', 'rsrc/css/application/conpherence/notification.css' => '6cdcc253', 'rsrc/css/application/conpherence/transaction.css' => '85d0974c', 'rsrc/css/application/conpherence/update.css' => 'faf6be09', - 'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba', + 'rsrc/css/application/conpherence/widget-pane.css' => 'c5b74f9e', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '16c52f5c', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', @@ -70,7 +71,6 @@ 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 'rsrc/css/application/flag/flag.css' => '5337623f', - 'rsrc/css/application/guides/guides.css' => '1d5414e5', 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald.css' => 'dc31f6e9', @@ -108,12 +108,12 @@ 'rsrc/css/core/core.css' => 'd0801452', 'rsrc/css/core/remarkup.css' => '5ed06ed8', 'rsrc/css/core/syntax.css' => '769d3498', - 'rsrc/css/core/z-index.css' => '5b6fcf3f', + 'rsrc/css/core/z-index.css' => '2b01a823', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => '2b7ebbcc', 'rsrc/css/font/font-lato.css' => 'c7ccd872', - 'rsrc/css/font/phui-font-icon-base.css' => '4e8274c4', + 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', @@ -148,7 +148,7 @@ 'rsrc/css/phui/phui-info-view.css' => '28efab79', 'rsrc/css/phui/phui-list.css' => '9da2aa00', 'rsrc/css/phui/phui-object-box.css' => '6b487c57', - 'rsrc/css/phui/phui-object-item-list-view.css' => '40010767', + 'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-profile-menu.css' => '8a3fc181', @@ -161,7 +161,7 @@ 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', 'rsrc/css/phui/phui-two-column-view.css' => '5afdf637', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', - 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', + 'rsrc/css/phui/workboards/phui-workboard.css' => 'bda3ef58', 'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5', 'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373', 'rsrc/css/sprite-login.css' => '60e8560e', @@ -358,7 +358,7 @@ 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', - 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '031cee25', + 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '49e20786', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', @@ -548,15 +548,16 @@ 'changeset-view-manager' => 'a2828756', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', + 'config-page-css' => '8798e14f', 'config-welcome-css' => '035aa483', 'conpherence-durable-column-view' => '86396117', - 'conpherence-menu-css' => 'f99fee4c', - 'conpherence-message-pane-css' => '5897d3ac', + 'conpherence-menu-css' => '90bdf85c', + 'conpherence-message-pane-css' => '5c7b7b17', 'conpherence-notification-css' => '6cdcc253', 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '85d0974c', 'conpherence-update-css' => 'faf6be09', - 'conpherence-widget-pane-css' => '775eaaba', + 'conpherence-widget-pane-css' => 'c5b74f9e', 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '9ef7d354', 'differential-core-view-css' => '5b7b8ff4', @@ -574,7 +575,6 @@ 'font-fontawesome' => '2b7ebbcc', 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => '5c1b47c2', - 'guides-app-css' => '1d5414e5', 'harbormaster-css' => 'f491c9f4', 'herald-css' => 'dc31f6e9', 'herald-rule-editor' => 'd6a7e717', @@ -582,7 +582,7 @@ 'inline-comment-summary-css' => '51efda3a', 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', - 'javelin-behavior-aphlict-dropdown' => '031cee25', + 'javelin-behavior-aphlict-dropdown' => '49e20786', 'javelin-behavior-aphlict-listen' => 'fb20ac8d', 'javelin-behavior-aphlict-status' => 'ea681761', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', @@ -786,11 +786,11 @@ 'phabricator-flag-css' => '5337623f', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => '4a021c10', - 'phabricator-main-menu-view' => '3b0d39f7', + 'phabricator-main-menu-view' => 'e862571a', 'phabricator-nav-view-css' => 'b29426e9', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', - 'phabricator-notification-menu-css' => 'f31c0bde', + 'phabricator-notification-menu-css' => 'b3ab500d', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'cfd23f37', @@ -799,7 +799,7 @@ 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', - 'phabricator-standard-page-view' => 'e709f6d0', + 'phabricator-standard-page-view' => '2b592894', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '6323f942', @@ -814,7 +814,7 @@ 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '5b6fcf3f', + 'phabricator-zindex-css' => '2b01a823', 'phame-css' => '8efb0729', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -842,7 +842,7 @@ 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => 'dc3d46ed', 'phui-feed-story-css' => 'aa49845d', - 'phui-font-icon-base-css' => '4e8274c4', + 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '76b4a46c', @@ -858,7 +858,7 @@ 'phui-inline-comment-view-css' => '5953c28e', 'phui-list-view-css' => '9da2aa00', 'phui-object-box-css' => '6b487c57', - 'phui-object-item-list-view-css' => '40010767', + 'phui-object-item-list-view-css' => '87278fa0', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-profile-menu-css' => '8a3fc181', @@ -872,7 +872,7 @@ 'phui-timeline-view-css' => 'bc523970', 'phui-two-column-view-css' => '5afdf637', 'phui-workboard-color-css' => 'ac6fe6a7', - 'phui-workboard-view-css' => 'e6d89647', + 'phui-workboard-view-css' => 'bda3ef58', 'phui-workcard-view-css' => '0c62d7c5', 'phui-workpanel-view-css' => '92197373', 'phuix-action-list-view' => 'b5c256b8', @@ -933,16 +933,6 @@ 'javelin-dom', 'phabricator-keyboard-shortcut', ), - '031cee25' => array( - 'javelin-behavior', - 'javelin-request', - 'javelin-stratcom', - 'javelin-vector', - 'javelin-dom', - 'javelin-uri', - 'javelin-behavior-device', - 'phabricator-title', - ), '05270951' => array( 'javelin-util', 'javelin-magical-init', @@ -1166,9 +1156,6 @@ 'javelin-dom', 'javelin-magical-init', ), - '3b0d39f7' => array( - 'phui-theme-css', - ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', @@ -1244,6 +1231,16 @@ 'javelin-dom', 'javelin-stratcom', ), + '49e20786' => array( + 'javelin-behavior', + 'javelin-request', + 'javelin-stratcom', + 'javelin-vector', + 'javelin-dom', + 'javelin-uri', + 'javelin-behavior-device', + 'phabricator-title', + ), '4a021c10' => array( 'javelin-install', 'javelin-util', @@ -2099,6 +2096,9 @@ 'e6e25838' => array( 'javelin-install', ), + 'e862571a' => array( + 'phui-theme-css', + ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', From bac8562884c510bfa464f64f5553eb61028e5f68 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Sep 2016 06:58:35 -0700 Subject: [PATCH 039/865] (stable) Allow `bin/storage renamespace` to work with underscores If the namespace is something like "test_example" we currently fail to renamespace the dump. (Cowboy committing this since this is currently blocking a data export.) Test Plan: - Renamespaced a local dump, examined the output, saw 60 create / 60 use, reimported it. - Will export in production. Auditors: chad --- ...catorStorageManagementRenamespaceWorkflow.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php index ad5afac7fb..51d3dd1482 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php @@ -138,9 +138,11 @@ public function didExecute(PhutilArgumentParser $args) { $output_name)); } + $name_pattern = preg_quote($from, '@'); + $patterns = array( - 'use' => '@^(USE `)([^_]+)(_.*)$@', - 'create' => '@^(CREATE DATABASE /\*.*?\*/ `)([^_]+)(_.*)$@', + 'use' => '@^(USE `)('.$name_pattern.')(_.*)$@', + 'create' => '@^(CREATE DATABASE /\*.*?\*/ `)('.$name_pattern.')(_.*)$@', ); $found = array_fill_keys(array_keys($patterns), 0); @@ -151,16 +153,6 @@ public function didExecute(PhutilArgumentParser $args) { foreach ($patterns as $key => $pattern) { if (preg_match($pattern, $line, $matches)) { - $namespace = $matches[2]; - if ($namespace != $from) { - throw new Exception( - pht( - 'Expected namespace "%s", found "%s": %s.', - $from, - $namespace, - $line)); - } - $line = $matches[1].$to.$matches[3]; $found[$key]++; } From 6725f3719da3498670e07a1922762bbb4165764d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Sep 2016 07:29:59 -0700 Subject: [PATCH 040/865] (stable) Add a "--copy" flag to "bin/files migrate" Summary: Ref T11596. When exporting data from the Phacility cluster, we `bin/files migrate` data from S3 into a database dump on the `aux` tier. With current semantics, this //moves// the data and destroys it in S3. Add a `--copy` flag to //copy// the data instead. This leaves the old copy around, which is what we want for exports. Test Plan: - Ran `bin/files migrate` to go from `blob` to `disk` with `--copy`. Verified a copy was left in the database. - Copied it back, verified a copy was left on disk (total: 2 database copies, 1 disk copy). - Moved it back without copy, verified database was destroyed and disk was created (total: 1 database copy, 2 disk copies). - Moved it back without copy, verified local disk was destroyed and blob was created (total: 2 datbabase copies, 1 disk copy). Reviewers: chad Reviewed By: chad Maniphest Tasks: T11596 Differential Revision: https://secure.phabricator.com/D16497 --- .../PhabricatorFilesManagementMigrateWorkflow.php | 10 +++++++++- .../files/storage/PhabricatorFile.php | 15 ++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php b/src/applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php index 83978d9764..eda43e2ef3 100644 --- a/src/applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php +++ b/src/applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php @@ -36,6 +36,12 @@ protected function didConstruct() { 'name' => 'all', 'help' => pht('Migrate all files.'), ), + array( + 'name' => 'copy', + 'help' => pht( + 'Copy file data instead of moving it: after migrating, do not '. + 'remove the old data even if it is no longer referenced.'), + ), array( 'name' => 'names', 'wildcard' => true, @@ -70,6 +76,8 @@ public function execute(PhutilArgumentParser $args) { $min_size = (int)$args->getArg('min-size'); $max_size = (int)$args->getArg('max-size'); + $is_copy = $args->getArg('copy'); + $failed = array(); $engines = PhabricatorFileStorageEngine::loadAllEngines(); $total_bytes = 0; @@ -158,7 +166,7 @@ public function execute(PhutilArgumentParser $args) { if ($is_dry_run) { // Do nothing, this is a dry run. } else { - $file->migrateToEngine($target_engine); + $file->migrateToEngine($target_engine, $is_copy); } $total_files += 1; diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 8d81c3ae98..d301abceca 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -422,7 +422,10 @@ public static function newFromFileData($data, array $params = array()) { return self::buildFromFileData($data, $params); } - public function migrateToEngine(PhabricatorFileStorageEngine $engine) { + public function migrateToEngine( + PhabricatorFileStorageEngine $engine, + $make_copy) { + if (!$this->getID() || !$this->getStorageHandle()) { throw new Exception( pht("You can not migrate a file which hasn't yet been saved.")); @@ -446,10 +449,12 @@ public function migrateToEngine(PhabricatorFileStorageEngine $engine) { $this->setStorageHandle($new_handle); $this->save(); - $this->deleteFileDataIfUnused( - $old_engine, - $old_identifier, - $old_handle); + if (!$make_copy) { + $this->deleteFileDataIfUnused( + $old_engine, + $old_identifier, + $old_handle); + } return $this; } From 89c0e47a60d30dd75d746fac0f185387522e857b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Sep 2016 11:03:17 -0700 Subject: [PATCH 041/865] (stable) Split setup checks into "preflight" and "normal" checks Summary: Ref T11589. Currently, initialization order is a bit tangled: we load configuration from the database, then later test if we can connect to the database. Instead, I'm going to do: preflight checks ("PHP Version OK?", "Extensions installed?"), then configuration, then normal setup checks. To prepare for this, flag core checks as "preflight" and add a setup panel to visually confirm that I didn't miss anything. Test Plan: {F1803210} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11589 Differential Revision: https://secure.phabricator.com/D16499 --- src/__phutil_library_map__.php | 2 + .../check/PhabricatorDatabaseSetupCheck.php | 2 +- .../check/PhabricatorExtensionsSetupCheck.php | 4 +- .../check/PhabricatorPHPConfigSetupCheck.php | 4 +- .../config/check/PhabricatorSetupCheck.php | 20 +++++++- .../PhabricatorConfigSetupCheckModule.php | 49 +++++++++++++++++++ 6 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/applications/config/module/PhabricatorConfigSetupCheckModule.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f471313be8..9492d65df6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2200,6 +2200,7 @@ 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', 'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php', 'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php', + 'PhabricatorConfigSetupCheckModule' => 'applications/config/module/PhabricatorConfigSetupCheckModule.php', 'PhabricatorConfigSiteModule' => 'applications/config/module/PhabricatorConfigSiteModule.php', 'PhabricatorConfigSiteSource' => 'infrastructure/env/PhabricatorConfigSiteSource.php', 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', @@ -6954,6 +6955,7 @@ 'PhabricatorConfigSchemaQuery' => 'Phobject', 'PhabricatorConfigSchemaSpec' => 'Phobject', 'PhabricatorConfigServerSchema' => 'PhabricatorConfigStorageSchema', + 'PhabricatorConfigSetupCheckModule' => 'PhabricatorConfigModule', 'PhabricatorConfigSiteModule' => 'PhabricatorConfigModule', 'PhabricatorConfigSiteSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigSource' => 'Phobject', diff --git a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php index dab9ad3b54..b99cd37adc 100644 --- a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php +++ b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php @@ -8,7 +8,7 @@ public function getDefaultGroup() { public function getExecutionOrder() { // This must run after basic PHP checks, but before most other checks. - return 0.5; + return 500; } protected function executeChecks() { diff --git a/src/applications/config/check/PhabricatorExtensionsSetupCheck.php b/src/applications/config/check/PhabricatorExtensionsSetupCheck.php index fa723cc7da..0840e545e4 100644 --- a/src/applications/config/check/PhabricatorExtensionsSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtensionsSetupCheck.php @@ -6,8 +6,8 @@ public function getDefaultGroup() { return self::GROUP_PHP; } - public function getExecutionOrder() { - return 0; + public function isPreflightCheck() { + return true; } protected function executeChecks() { diff --git a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php index ec61b4764d..86e885614a 100644 --- a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php @@ -6,8 +6,8 @@ public function getDefaultGroup() { return self::GROUP_PHP; } - public function getExecutionOrder() { - return 0; + public function isPreflightCheck() { + return true; } protected function executeChecks() { diff --git a/src/applications/config/check/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php index a657c52799..5970b0ca81 100644 --- a/src/applications/config/check/PhabricatorSetupCheck.php +++ b/src/applications/config/check/PhabricatorSetupCheck.php @@ -12,7 +12,25 @@ abstract protected function executeChecks(); const GROUP_IMPORTANT = 'important'; public function getExecutionOrder() { - return 1; + if ($this->isPreflightCheck()) { + return 0; + } else { + return 1000; + } + } + + /** + * Should this check execute before we load configuration? + * + * The majority of checks (particularly, those checks which examine + * configuration) should run in the normal setup phase, after configuration + * loads. However, a small set of critical checks (mostly, tests for PHP + * setup and extensions) need to run before we can load configuration. + * + * @return bool True to execute before configuration is loaded. + */ + public function isPreflightCheck() { + return false; } final protected function newIssue($key) { diff --git a/src/applications/config/module/PhabricatorConfigSetupCheckModule.php b/src/applications/config/module/PhabricatorConfigSetupCheckModule.php new file mode 100644 index 0000000000..886a2e659f --- /dev/null +++ b/src/applications/config/module/PhabricatorConfigSetupCheckModule.php @@ -0,0 +1,49 @@ +getViewer(); + + $checks = PhabricatorSetupCheck::loadAllChecks(); + + $rows = array(); + foreach ($checks as $key => $check) { + if ($check->isPreflightCheck()) { + $icon = id(new PHUIIconView())->setIcon('fa-plane blue'); + } else { + $icon = id(new PHUIIconView())->setIcon('fa-times grey'); + } + + $rows[] = array( + $check->getExecutionOrder(), + $icon, + get_class($check), + ); + } + + return id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Order'), + pht('Preflight'), + pht('Class'), + )) + ->setColumnClasses( + array( + null, + null, + 'pri wide', + )); + } + +} From 445924a2a23c4e95a91b5fc20a48e60c60b1914e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Sep 2016 12:25:37 -0700 Subject: [PATCH 042/865] (stable) Split setup check phases into "preflight" and "post-config" Summary: Ref T11589. This runs: - preflight checks (critical checks: PHP version stuff, extensions); - configuration; - normal checks. The PHP checks are split into critical ("bad version") and noncritical ("sub-optimal config"). I tidied up the extension checks slightly, we realistically depend on `cURL` nowadays. Test Plan: - Faked a preflight failure. - Hit preflight check. - Got expected error screen. - Loaded normal pages. - Hit a normal setup check. - Used DarkConsole "Startup" tab to verify that preflight checks take <1ms to run (we run them on every page without caching, at least for now, but they only do trivial checks like PHP versions). Reviewers: chad Reviewed By: chad Maniphest Tasks: T11589 Differential Revision: https://secure.phabricator.com/D16500 --- src/__phutil_library_map__.php | 2 + .../AphrontApplicationConfiguration.php | 9 + .../check/PhabricatorExtensionsSetupCheck.php | 6 +- .../check/PhabricatorPHPConfigSetupCheck.php | 197 +---------------- .../PhabricatorPHPPreflightSetupCheck.php | 201 ++++++++++++++++++ .../config/check/PhabricatorSetupCheck.php | 41 +++- .../PhabricatorConfigIssueListController.php | 2 +- .../PhabricatorConfigIssuePanelController.php | 2 +- .../PhabricatorConfigIssueViewController.php | 2 +- src/infrastructure/env/PhabricatorEnv.php | 9 +- 10 files changed, 267 insertions(+), 204 deletions(-) create mode 100644 src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9492d65df6..02b4dc5b92 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3000,6 +3000,7 @@ 'PhabricatorPHPASTApplication' => 'applications/phpast/application/PhabricatorPHPASTApplication.php', 'PhabricatorPHPConfigSetupCheck' => 'applications/config/check/PhabricatorPHPConfigSetupCheck.php', 'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php', + 'PhabricatorPHPPreflightSetupCheck' => 'applications/config/check/PhabricatorPHPPreflightSetupCheck.php', 'PhabricatorPackagesApplication' => 'applications/packages/application/PhabricatorPackagesApplication.php', 'PhabricatorPackagesController' => 'applications/packages/controller/PhabricatorPackagesController.php', 'PhabricatorPackagesCreatePublisherCapability' => 'applications/packages/capability/PhabricatorPackagesCreatePublisherCapability.php', @@ -7858,6 +7859,7 @@ 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorPHPPreflightSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPackagesApplication' => 'PhabricatorApplication', 'PhabricatorPackagesController' => 'PhabricatorController', 'PhabricatorPackagesCreatePublisherCapability' => 'PhabricatorPolicyCapability', diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index dc32faedec..7c5c571b64 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -69,6 +69,15 @@ public static function runHTTPRequest(AphrontHTTPSink $sink) { // request object first. $write_guard = new AphrontWriteGuard('id'); + PhabricatorStartup::beginStartupPhase('preflight'); + + $response = PhabricatorSetupCheck::willPreflightRequest(); + if ($response) { + PhabricatorStartup::endOutputCapture(); + $sink->writeResponse($response); + return; + } + PhabricatorStartup::beginStartupPhase('env.init'); PhabricatorEnv::initializeWebEnvironment(); diff --git a/src/applications/config/check/PhabricatorExtensionsSetupCheck.php b/src/applications/config/check/PhabricatorExtensionsSetupCheck.php index 0840e545e4..973c80629b 100644 --- a/src/applications/config/check/PhabricatorExtensionsSetupCheck.php +++ b/src/applications/config/check/PhabricatorExtensionsSetupCheck.php @@ -12,7 +12,6 @@ public function isPreflightCheck() { protected function executeChecks() { // TODO: Make 'mbstring' and 'iconv' soft requirements. - // TODO: Make 'curl' a soft requirement. $required = array( 'hash', @@ -22,9 +21,8 @@ protected function executeChecks() { 'iconv', 'ctype', - // There is a chance we might not need this, but some configurations (like - // OAuth or Amazon SES) will require it. Just mark it 'required' since - // it's widely available and relatively core. + // There is a tiny chance we might not need this, but a significant + // number of applications require it and it's widely available. 'curl', ); diff --git a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php index 86e885614a..f286b46f9c 100644 --- a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php @@ -1,202 +1,17 @@ =')) { - $message = pht( - 'This version of Phabricator does not support PHP 7. You '. - 'are running PHP %s.', - phpversion()); - - $this->newIssue('php.version7') - ->setIsFatal(true) - ->setName(pht('PHP 7 Not Supported')) - ->setMessage($message) - ->addLink( - 'https://phurl.io/u/php7', - pht('Phabricator PHP 7 Compatibility Information')); - - return; - } - - $safe_mode = ini_get('safe_mode'); - if ($safe_mode) { - $message = pht( - "You have '%s' enabled in your PHP configuration, but Phabricator ". - "will not run in safe mode. Safe mode has been deprecated in PHP 5.3 ". - "and removed in PHP 5.4.\n\nDisable safe mode to continue.", - 'safe_mode'); - - $this->newIssue('php.safe_mode') - ->setIsFatal(true) - ->setName(pht('Disable PHP %s', 'safe_mode')) - ->setMessage($message) - ->addPHPConfig('safe_mode'); - return; - } - - // Check for `disable_functions` or `disable_classes`. Although it's - // possible to disable a bunch of functions (say, `array_change_key_case()`) - // and classes and still have Phabricator work fine, it's unreasonably - // difficult for us to be sure we'll even survive setup if these options - // are enabled. Phabricator needs access to the most dangerous functions, - // so there is no reasonable configuration value here which actually - // provides a benefit while guaranteeing Phabricator will run properly. - - $disable_options = array('disable_functions', 'disable_classes'); - foreach ($disable_options as $disable_option) { - $disable_value = ini_get($disable_option); - if ($disable_value) { - - // By default Debian installs the pcntl extension but disables all of - // its functions using configuration. Whitelist disabling these - // functions so that Debian PHP works out of the box (we do not need to - // call these functions from the web UI). This is pretty ridiculous but - // it's not the users' fault and they haven't done anything crazy to - // get here, so don't make them pay for Debian's unusual choices. - // See: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=605571 - $fatal = true; - if ($disable_option == 'disable_functions') { - $functions = preg_split('/[, ]+/', $disable_value); - $functions = array_filter($functions); - foreach ($functions as $k => $function) { - if (preg_match('/^pcntl_/', $function)) { - unset($functions[$k]); - } - } - if (!$functions) { - $fatal = false; - } - } - - if ($fatal) { - $message = pht( - "You have '%s' enabled in your PHP configuration.\n\n". - "This option is not compatible with Phabricator. Remove ". - "'%s' from your configuration to continue.", - $disable_option, - $disable_option); - - $this->newIssue('php.'.$disable_option) - ->setIsFatal(true) - ->setName(pht('Remove PHP %s', $disable_option)) - ->setMessage($message) - ->addPHPConfig($disable_option); - } - } - } - - $overload_option = 'mbstring.func_overload'; - $func_overload = ini_get($overload_option); - if ($func_overload) { - $message = pht( - "You have '%s' enabled in your PHP configuration.\n\n". - "This option is not compatible with Phabricator. Disable ". - "'%s' in your PHP configuration to continue.", - $overload_option, - $overload_option); - - $this->newIssue('php'.$overload_option) - ->setIsFatal(true) - ->setName(pht('Disable PHP %s', $overload_option)) - ->setMessage($message) - ->addPHPConfig($overload_option); - } - - $open_basedir = ini_get('open_basedir'); - if ($open_basedir) { - - // 'open_basedir' restricts which files we're allowed to access with - // file operations. This might be okay -- we don't need to write to - // arbitrary places in the filesystem -- but we need to access certain - // resources. This setting is unlikely to be providing any real measure - // of security so warn even if things look OK. - - $failures = array(); - - try { - $open_libphutil = class_exists('Future'); - } catch (Exception $ex) { - $failures[] = $ex->getMessage(); - } - - try { - $open_arcanist = class_exists('ArcanistDiffParser'); - } catch (Exception $ex) { - $failures[] = $ex->getMessage(); - } - - $open_urandom = false; - try { - Filesystem::readRandomBytes(1); - $open_urandom = true; - } catch (FilesystemException $ex) { - $failures[] = $ex->getMessage(); - } - - try { - $tmp = new TempFile(); - file_put_contents($tmp, '.'); - $open_tmp = @fopen((string)$tmp, 'r'); - if (!$open_tmp) { - $failures[] = pht( - "Unable to read temporary file '%s'.", - (string)$tmp); - } - } catch (Exception $ex) { - $message = $ex->getMessage(); - $dir = sys_get_temp_dir(); - $failures[] = pht( - "Unable to open temp files from '%s': %s", - $dir, - $message); - } - - $issue = $this->newIssue('php.open_basedir') - ->setName(pht('Disable PHP %s', 'open_basedir')) - ->addPHPConfig('open_basedir'); - - if ($failures) { - $message = pht( - "Your server is configured with '%s', which prevents Phabricator ". - "from opening files it requires access to.\n\n". - "Disable this setting to continue.\n\nFailures:\n\n%s", - 'open_basedir', - implode("\n\n", $failures)); - - $issue - ->setIsFatal(true) - ->setMessage($message); - - return; - } else { - $summary = pht( - "You have '%s' configured in your PHP settings, which ". - "may cause some features to fail.", - 'open_basedir'); - - $message = pht( - "You have '%s' configured in your PHP settings. Although this ". - "setting appears permissive enough that Phabricator will work ". - "properly, you may still run into problems because of it.\n\n". - "Consider disabling '%s'.", - 'open_basedir', - 'open_basedir'); - - $issue - ->setSummary($summary) - ->setMessage($message); - } - } if (empty($_SERVER['REMOTE_ADDR'])) { $doc_href = PhabricatorEnv::getDocLink('Configuring a Preamble Script'); @@ -245,5 +60,7 @@ protected function executeChecks() { ->setMessage($message) ->addPHPConfig('always_populate_raw_post_data'); } + } + } diff --git a/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php b/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php new file mode 100644 index 0000000000..65a582b29c --- /dev/null +++ b/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php @@ -0,0 +1,201 @@ +=')) { + $message = pht( + 'This version of Phabricator does not support PHP 7. You '. + 'are running PHP %s.', + phpversion()); + + $this->newIssue('php.version7') + ->setIsFatal(true) + ->setName(pht('PHP 7 Not Supported')) + ->setMessage($message) + ->addLink( + 'https://phurl.io/u/php7', + pht('Phabricator PHP 7 Compatibility Information')); + + return; + } + + $safe_mode = ini_get('safe_mode'); + if ($safe_mode) { + $message = pht( + "You have '%s' enabled in your PHP configuration, but Phabricator ". + "will not run in safe mode. Safe mode has been deprecated in PHP 5.3 ". + "and removed in PHP 5.4.\n\nDisable safe mode to continue.", + 'safe_mode'); + + $this->newIssue('php.safe_mode') + ->setIsFatal(true) + ->setName(pht('Disable PHP %s', 'safe_mode')) + ->setMessage($message) + ->addPHPConfig('safe_mode'); + return; + } + + // Check for `disable_functions` or `disable_classes`. Although it's + // possible to disable a bunch of functions (say, `array_change_key_case()`) + // and classes and still have Phabricator work fine, it's unreasonably + // difficult for us to be sure we'll even survive setup if these options + // are enabled. Phabricator needs access to the most dangerous functions, + // so there is no reasonable configuration value here which actually + // provides a benefit while guaranteeing Phabricator will run properly. + + $disable_options = array('disable_functions', 'disable_classes'); + foreach ($disable_options as $disable_option) { + $disable_value = ini_get($disable_option); + if ($disable_value) { + + // By default Debian installs the pcntl extension but disables all of + // its functions using configuration. Whitelist disabling these + // functions so that Debian PHP works out of the box (we do not need to + // call these functions from the web UI). This is pretty ridiculous but + // it's not the users' fault and they haven't done anything crazy to + // get here, so don't make them pay for Debian's unusual choices. + // See: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=605571 + $fatal = true; + if ($disable_option == 'disable_functions') { + $functions = preg_split('/[, ]+/', $disable_value); + $functions = array_filter($functions); + foreach ($functions as $k => $function) { + if (preg_match('/^pcntl_/', $function)) { + unset($functions[$k]); + } + } + if (!$functions) { + $fatal = false; + } + } + + if ($fatal) { + $message = pht( + "You have '%s' enabled in your PHP configuration.\n\n". + "This option is not compatible with Phabricator. Remove ". + "'%s' from your configuration to continue.", + $disable_option, + $disable_option); + + $this->newIssue('php.'.$disable_option) + ->setIsFatal(true) + ->setName(pht('Remove PHP %s', $disable_option)) + ->setMessage($message) + ->addPHPConfig($disable_option); + } + } + } + + $overload_option = 'mbstring.func_overload'; + $func_overload = ini_get($overload_option); + if ($func_overload) { + $message = pht( + "You have '%s' enabled in your PHP configuration.\n\n". + "This option is not compatible with Phabricator. Disable ". + "'%s' in your PHP configuration to continue.", + $overload_option, + $overload_option); + + $this->newIssue('php'.$overload_option) + ->setIsFatal(true) + ->setName(pht('Disable PHP %s', $overload_option)) + ->setMessage($message) + ->addPHPConfig($overload_option); + } + + $open_basedir = ini_get('open_basedir'); + if ($open_basedir) { + + // 'open_basedir' restricts which files we're allowed to access with + // file operations. This might be okay -- we don't need to write to + // arbitrary places in the filesystem -- but we need to access certain + // resources. This setting is unlikely to be providing any real measure + // of security so warn even if things look OK. + + $failures = array(); + + try { + $open_libphutil = class_exists('Future'); + } catch (Exception $ex) { + $failures[] = $ex->getMessage(); + } + + try { + $open_arcanist = class_exists('ArcanistDiffParser'); + } catch (Exception $ex) { + $failures[] = $ex->getMessage(); + } + + $open_urandom = false; + try { + Filesystem::readRandomBytes(1); + $open_urandom = true; + } catch (FilesystemException $ex) { + $failures[] = $ex->getMessage(); + } + + try { + $tmp = new TempFile(); + file_put_contents($tmp, '.'); + $open_tmp = @fopen((string)$tmp, 'r'); + if (!$open_tmp) { + $failures[] = pht( + "Unable to read temporary file '%s'.", + (string)$tmp); + } + } catch (Exception $ex) { + $message = $ex->getMessage(); + $dir = sys_get_temp_dir(); + $failures[] = pht( + "Unable to open temp files from '%s': %s", + $dir, + $message); + } + + $issue = $this->newIssue('php.open_basedir') + ->setName(pht('Disable PHP %s', 'open_basedir')) + ->addPHPConfig('open_basedir'); + + if ($failures) { + $message = pht( + "Your server is configured with '%s', which prevents Phabricator ". + "from opening files it requires access to.\n\n". + "Disable this setting to continue.\n\nFailures:\n\n%s", + 'open_basedir', + implode("\n\n", $failures)); + + $issue + ->setIsFatal(true) + ->setMessage($message); + + return; + } else { + $summary = pht( + "You have '%s' configured in your PHP settings, which ". + "may cause some features to fail.", + 'open_basedir'); + + $message = pht( + "You have '%s' configured in your PHP settings. Although this ". + "setting appears permissive enough that Phabricator will work ". + "properly, you may still run into problems because of it.\n\n". + "Consider disabling '%s'.", + 'open_basedir', + 'open_basedir'); + + $issue + ->setSummary($summary) + ->setMessage($message); + } + } + } +} diff --git a/src/applications/config/check/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php index 5970b0ca81..b1f79bdd47 100644 --- a/src/applications/config/check/PhabricatorSetupCheck.php +++ b/src/applications/config/check/PhabricatorSetupCheck.php @@ -129,16 +129,39 @@ final public static function deleteSetupCheckCache() { )); } + final public static function willPreflightRequest() { + $checks = self::loadAllChecks(); + + foreach ($checks as $check) { + if (!$check->isPreflightCheck()) { + continue; + } + + $check->runSetupChecks(); + + foreach ($check->getIssues() as $key => $issue) { + return self::newIssueResponse($issue); + } + } + + return null; + } + + private static function newIssueResponse(PhabricatorSetupIssue $issue) { + $view = id(new PhabricatorSetupIssueView()) + ->setIssue($issue); + + return id(new PhabricatorConfigResponse()) + ->setView($view); + } + final public static function willProcessRequest() { $issue_keys = self::getOpenSetupIssueKeys(); if ($issue_keys === null) { - $issues = self::runAllChecks(); + $issues = self::runNormalChecks(); foreach ($issues as $issue) { if ($issue->getIsFatal()) { - $view = id(new PhabricatorSetupIssueView()) - ->setIssue($issue); - return id(new PhabricatorConfigResponse()) - ->setView($view); + return self::newIssueResponse($issue); } } $issue_keys = self::getUnignoredIssueKeys($issues); @@ -176,9 +199,15 @@ final public static function loadAllChecks() { ->execute(); } - final public static function runAllChecks() { + final public static function runNormalChecks() { $checks = self::loadAllChecks(); + foreach ($checks as $key => $check) { + if ($check->isPreflightCheck()) { + unset($checks[$key]); + } + } + $issues = array(); foreach ($checks as $check) { $check->runSetupChecks(); diff --git a/src/applications/config/controller/PhabricatorConfigIssueListController.php b/src/applications/config/controller/PhabricatorConfigIssueListController.php index 7f0b19af69..a5553a34bb 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueListController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueListController.php @@ -9,7 +9,7 @@ public function handleRequest(AphrontRequest $request) { $nav = $this->buildSideNavView(); $nav->selectFilter('issue/'); - $issues = PhabricatorSetupCheck::runAllChecks(); + $issues = PhabricatorSetupCheck::runNormalChecks(); PhabricatorSetupCheck::setOpenSetupIssueKeys( PhabricatorSetupCheck::getUnignoredIssueKeys($issues), $update_database = true); diff --git a/src/applications/config/controller/PhabricatorConfigIssuePanelController.php b/src/applications/config/controller/PhabricatorConfigIssuePanelController.php index 16541d184b..3dd910f693 100644 --- a/src/applications/config/controller/PhabricatorConfigIssuePanelController.php +++ b/src/applications/config/controller/PhabricatorConfigIssuePanelController.php @@ -6,7 +6,7 @@ final class PhabricatorConfigIssuePanelController public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $open_items = PhabricatorSetupCheck::getOpenSetupIssueKeys(); - $issues = PhabricatorSetupCheck::runAllChecks(); + $issues = PhabricatorSetupCheck::runNormalChecks(); PhabricatorSetupCheck::setOpenSetupIssueKeys( PhabricatorSetupCheck::getUnignoredIssueKeys($issues), $update_database = true); diff --git a/src/applications/config/controller/PhabricatorConfigIssueViewController.php b/src/applications/config/controller/PhabricatorConfigIssueViewController.php index 4700e6315e..57d7b75137 100644 --- a/src/applications/config/controller/PhabricatorConfigIssueViewController.php +++ b/src/applications/config/controller/PhabricatorConfigIssueViewController.php @@ -7,7 +7,7 @@ public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $issue_key = $request->getURIData('key'); - $issues = PhabricatorSetupCheck::runAllChecks(); + $issues = PhabricatorSetupCheck::runNormalChecks(); PhabricatorSetupCheck::setOpenSetupIssueKeys( PhabricatorSetupCheck::getUnignoredIssueKeys($issues), $update_database = true); diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index d66e022465..221d553d2b 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -315,6 +315,14 @@ public static function getEnvConfig($key) { return self::$cache[$key]; } + if (!self::$sourceStack) { + throw new Exception( + pht( + 'Trying to read configuration "%s" before configuration has been '. + 'initialized.', + $key)); + } + $result = self::$sourceStack->getKeys(array($key)); if (array_key_exists($key, $result)) { self::$cache[$key] = $result[$key]; @@ -327,7 +335,6 @@ public static function getEnvConfig($key) { } } - /** * Get the current configuration setting for a given key. If the key * does not exist, return a default value instead of throwing. This is From a1338bd2d8897d4f763cd4e94287ebb39b92d201 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Sep 2016 12:52:03 -0700 Subject: [PATCH 043/865] (stable) Raise setup warnings immediately when failing to load configuration from the database Summary: Ref T11589. Previously, when we failed to load database configuration we just continued anyway, in order to get to setup checks so we could raise a better error. There was a small chance that this could lead to pages running in a broken state, where ONLY that connection failed and everything else worked. This was accidentally fixed by narrowing the exceptions we continue on in D16489. However, this "fix" meant that users no longer got helpful setup instructions. Instead: - Keep throwing these exceptions: it's bad to continue if we've failed to connect to the database. - However, catch them and turn them into setup errors. - Share all the setup code so these errors and setup check errors work the same way. Test Plan: - Intentionally broke `mysql.host` and `mysql.pass`. - Loaded pages. - Got good setup errors. - Hit normal setup errors too. - Put everything back. - Swapped into cluster mode. - Intentionally broke cluster mode, saw failover to readonly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11589 Differential Revision: https://secure.phabricator.com/D16501 --- .../AphrontApplicationConfiguration.php | 82 +++++++++++-------- .../check/PhabricatorDatabaseSetupCheck.php | 22 ++--- .../config/check/PhabricatorSetupCheck.php | 2 +- .../config/issue/PhabricatorSetupIssue.php | 20 +++++ 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 7c5c571b64..5eeba52221 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -73,13 +73,26 @@ public static function runHTTPRequest(AphrontHTTPSink $sink) { $response = PhabricatorSetupCheck::willPreflightRequest(); if ($response) { - PhabricatorStartup::endOutputCapture(); - $sink->writeResponse($response); - return; + return self::writeResponse($sink, $response); } PhabricatorStartup::beginStartupPhase('env.init'); - PhabricatorEnv::initializeWebEnvironment(); + + try { + PhabricatorEnv::initializeWebEnvironment(); + $database_exception = null; + } catch (AphrontInvalidCredentialsQueryException $ex) { + $database_exception = $ex; + } catch (AphrontConnectionQueryException $ex) { + $database_exception = $ex; + } + + if ($database_exception) { + $issue = PhabricatorSetupIssue::newDatabaseConnectionIssue( + $database_exception); + $response = PhabricatorSetupCheck::newIssueResponse($issue); + return self::writeResponse($sink, $response); + } $multimeter->setSampleRate( PhabricatorEnv::getEnvConfig('debug.sample-rate')); @@ -111,9 +124,7 @@ public static function runHTTPRequest(AphrontHTTPSink $sink) { $response = PhabricatorSetupCheck::willProcessRequest(); if ($response) { - PhabricatorStartup::endOutputCapture(); - $sink->writeResponse($response); - return; + return self::writeResponse($sink, $response); } $host = AphrontRequest::getHTTPHeader('Host'); @@ -256,31 +267,7 @@ public function processRequest( $response = $controller->willSendResponse($response); $response->setRequest($request); - $unexpected_output = PhabricatorStartup::endOutputCapture(); - if ($unexpected_output) { - $unexpected_output = pht( - "Unexpected output:\n\n%s", - $unexpected_output); - - phlog($unexpected_output); - - if ($response instanceof AphrontWebpageResponse) { - echo phutil_tag( - 'div', - array( - 'style' => - 'background: #eeddff;'. - 'white-space: pre-wrap;'. - 'z-index: 200000;'. - 'position: relative;'. - 'padding: 8px;'. - 'font-family: monospace', - ), - $unexpected_output); - } - } - - $sink->writeResponse($response); + self::writeResponse($sink, $response); } catch (Exception $ex) { if ($original_exception) { throw $original_exception; @@ -291,6 +278,37 @@ public function processRequest( return $response; } + private static function writeResponse( + AphrontHTTPSink $sink, + AphrontResponse $response) { + + $unexpected_output = PhabricatorStartup::endOutputCapture(); + if ($unexpected_output) { + $unexpected_output = pht( + "Unexpected output:\n\n%s", + $unexpected_output); + + phlog($unexpected_output); + + if ($response instanceof AphrontWebpageResponse) { + echo phutil_tag( + 'div', + array( + 'style' => + 'background: #eeddff;'. + 'white-space: pre-wrap;'. + 'z-index: 200000;'. + 'position: relative;'. + 'padding: 8px;'. + 'font-family: monospace', + ), + $unexpected_output); + } + } + + $sink->writeResponse($response); + } + /* -( URI Routing )-------------------------------------------------------- */ diff --git a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php index b99cd37adc..fd318a5fa1 100644 --- a/src/applications/config/check/PhabricatorDatabaseSetupCheck.php +++ b/src/applications/config/check/PhabricatorDatabaseSetupCheck.php @@ -23,21 +23,17 @@ protected function executeChecks() { try { queryfx($conn_raw, 'SELECT 1'); + $database_exception = null; + } catch (AphrontInvalidCredentialsQueryException $ex) { + $database_exception = $ex; } catch (AphrontConnectionQueryException $ex) { - $message = pht( - "Unable to connect to MySQL!\n\n". - "%s\n\n". - "Make sure Phabricator and MySQL are correctly configured.", - $ex->getMessage()); + $database_exception = $ex; + } - $this->newIssue('mysql.connect') - ->setName(pht('Can Not Connect to MySQL')) - ->setMessage($message) - ->setIsFatal(true) - ->addRelatedPhabricatorConfig('mysql.host') - ->addRelatedPhabricatorConfig('mysql.port') - ->addRelatedPhabricatorConfig('mysql.user') - ->addRelatedPhabricatorConfig('mysql.pass'); + if ($database_exception) { + $issue = PhabricatorSetupIssue::newDatabaseConnectionIssue( + $database_exception); + $this->addIssue($issue); return; } diff --git a/src/applications/config/check/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php index b1f79bdd47..ad779ed63f 100644 --- a/src/applications/config/check/PhabricatorSetupCheck.php +++ b/src/applications/config/check/PhabricatorSetupCheck.php @@ -147,7 +147,7 @@ final public static function willPreflightRequest() { return null; } - private static function newIssueResponse(PhabricatorSetupIssue $issue) { + public static function newIssueResponse(PhabricatorSetupIssue $issue) { $view = id(new PhabricatorSetupIssueView()) ->setIssue($issue); diff --git a/src/applications/config/issue/PhabricatorSetupIssue.php b/src/applications/config/issue/PhabricatorSetupIssue.php index 1c2ff0f99b..53bab14a85 100644 --- a/src/applications/config/issue/PhabricatorSetupIssue.php +++ b/src/applications/config/issue/PhabricatorSetupIssue.php @@ -20,6 +20,26 @@ final class PhabricatorSetupIssue extends Phobject { private $originalPHPConfigValues = array(); private $links; + public static function newDatabaseConnectionIssue( + AphrontQueryException $ex) { + + $message = pht( + "Unable to connect to MySQL!\n\n". + "%s\n\n". + "Make sure Phabricator and MySQL are correctly configured.", + $ex->getMessage()); + + return id(new self()) + ->setIssueKey('mysql.connect') + ->setName(pht('Can Not Connect to MySQL')) + ->setMessage($message) + ->setIsFatal(true) + ->addRelatedPhabricatorConfig('mysql.host') + ->addRelatedPhabricatorConfig('mysql.port') + ->addRelatedPhabricatorConfig('mysql.user') + ->addRelatedPhabricatorConfig('mysql.pass'); + } + public function addCommand($command) { $this->commands[] = $command; return $this; From 32a580c720e31b0e4d7c5e8846870e8ce9fe3978 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Sep 2016 13:20:31 -0700 Subject: [PATCH 044/865] (stable) Continue on bad database configuration from select scripts Summary: Ref T11589. Provide a way for scripts to say "just continue if database config fails", and use it in `bin/config` and `bin/storage`. Test Plan: - Broke database config. - Ran `bin/config`, worked fine. - Ran `bin/storage`, got helpful "set up the database" message. - Ran `bin/repository`, got fatal. - Ran normal site with valid/invalid config, got proper feedback. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11589 Differential Revision: https://secure.phabricator.com/D16502 --- scripts/__init_script__.php | 23 +---------------------- scripts/init/init-script.php | 10 ++++++++++ scripts/init/init-setup.php | 12 ++++++++++++ scripts/init/lib.php | 23 +++++++++++++++++++++++ scripts/setup/manage_config.php | 2 +- scripts/sql/manage_storage.php | 2 +- src/infrastructure/env/PhabricatorEnv.php | 20 ++++++++++++++------ 7 files changed, 62 insertions(+), 30 deletions(-) create mode 100644 scripts/init/init-script.php create mode 100644 scripts/init/init-setup.php create mode 100644 scripts/init/lib.php diff --git a/scripts/__init_script__.php b/scripts/__init_script__.php index 57ada96e64..b807ae68a4 100644 --- a/scripts/__init_script__.php +++ b/scripts/__init_script__.php @@ -1,24 +1,3 @@ false, + )); diff --git a/scripts/init/init-setup.php b/scripts/init/init-setup.php new file mode 100644 index 0000000000..fea975830d --- /dev/null +++ b/scripts/init/init-setup.php @@ -0,0 +1,12 @@ + true, + )); diff --git a/scripts/init/lib.php b/scripts/init/lib.php new file mode 100644 index 0000000000..431d2c1373 --- /dev/null +++ b/scripts/init/lib.php @@ -0,0 +1,23 @@ +setTagline(pht('manage configuration')); diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php index 7ad94277f7..2ba1e9920c 100755 --- a/scripts/sql/manage_storage.php +++ b/scripts/sql/manage_storage.php @@ -2,7 +2,7 @@ setTagline(pht('manage Phabricator storage and schemata')); diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index 221d553d2b..7ea00957ff 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -68,11 +68,11 @@ final class PhabricatorEnv extends Phobject { * @phutil-external-symbol class PhabricatorStartup */ public static function initializeWebEnvironment() { - self::initializeCommonEnvironment(); + self::initializeCommonEnvironment(false); } - public static function initializeScriptEnvironment() { - self::initializeCommonEnvironment(); + public static function initializeScriptEnvironment($config_optional) { + self::initializeCommonEnvironment($config_optional); // NOTE: This is dangerous in general, but we know we're in a script context // and are not vulnerable to CSRF. @@ -88,11 +88,11 @@ public static function initializeScriptEnvironment() { } - private static function initializeCommonEnvironment() { + private static function initializeCommonEnvironment($config_optional) { PhutilErrorHandler::initialize(); self::resetUmask(); - self::buildConfigurationSourceStack(); + self::buildConfigurationSourceStack($config_optional); // Force a valid timezone. If both PHP and Phabricator configuration are // invalid, use UTC. @@ -174,7 +174,7 @@ public static function setLocaleCode($locale_code) { } } - private static function buildConfigurationSourceStack() { + private static function buildConfigurationSourceStack($config_optional) { self::dropConfigCache(); $stack = new PhabricatorConfigStackSource(); @@ -235,6 +235,14 @@ private static function buildConfigurationSourceStack() { // If the database is not available, just skip this configuration // source. This happens during `bin/storage upgrade`, `bin/conf` before // schema setup, etc. + } catch (AphrontConnectionQueryException $ex) { + if (!$config_optional) { + throw $ex; + } + } catch (AphrontInvalidCredentialsQueryException $ex) { + if (!$config_optional) { + throw $ex; + } } } From 699ba2d4abedae09f31217f468ea9bda6ece9172 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 12 Sep 2016 14:43:00 -0700 Subject: [PATCH 045/865] (stable) Just fatal for any setting of open_basedir Summary: Fixes T11627. Beyond being complex, I have no real reason to believe these checks even work (and they don't test repositories, file storage, logfiles, etc). Test Plan: Faked the error: {F1813433} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11627 Differential Revision: https://secure.phabricator.com/D16544 --- .../PhabricatorPHPPreflightSetupCheck.php | 96 ++++--------------- 1 file changed, 17 insertions(+), 79 deletions(-) diff --git a/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php b/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php index 65a582b29c..82a13438d8 100644 --- a/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php +++ b/src/applications/config/check/PhabricatorPHPPreflightSetupCheck.php @@ -113,89 +113,27 @@ protected function executeChecks() { } $open_basedir = ini_get('open_basedir'); - if ($open_basedir) { + if (strlen($open_basedir)) { + // If `open_basedir` is set, just fatal. It's technically possible for + // us to run with certain values of `open_basedir`, but: we can only + // raise fatal errors from preflight steps, so we'd have to do this check + // in two parts to support fatal and advisory versions; it's much simpler + // to just fatal instead of trying to test all the different things we + // may need to access in the filesystem; and use of this option seems + // rare (particularly in supported environments). - // 'open_basedir' restricts which files we're allowed to access with - // file operations. This might be okay -- we don't need to write to - // arbitrary places in the filesystem -- but we need to access certain - // resources. This setting is unlikely to be providing any real measure - // of security so warn even if things look OK. - - $failures = array(); - - try { - $open_libphutil = class_exists('Future'); - } catch (Exception $ex) { - $failures[] = $ex->getMessage(); - } - - try { - $open_arcanist = class_exists('ArcanistDiffParser'); - } catch (Exception $ex) { - $failures[] = $ex->getMessage(); - } - - $open_urandom = false; - try { - Filesystem::readRandomBytes(1); - $open_urandom = true; - } catch (FilesystemException $ex) { - $failures[] = $ex->getMessage(); - } - - try { - $tmp = new TempFile(); - file_put_contents($tmp, '.'); - $open_tmp = @fopen((string)$tmp, 'r'); - if (!$open_tmp) { - $failures[] = pht( - "Unable to read temporary file '%s'.", - (string)$tmp); - } - } catch (Exception $ex) { - $message = $ex->getMessage(); - $dir = sys_get_temp_dir(); - $failures[] = pht( - "Unable to open temp files from '%s': %s", - $dir, - $message); - } + $message = pht( + "Your server is configured with '%s', which prevents Phabricator ". + "from opening files it requires access to.\n\n". + "Disable this setting to continue.", + 'open_basedir'); $issue = $this->newIssue('php.open_basedir') ->setName(pht('Disable PHP %s', 'open_basedir')) - ->addPHPConfig('open_basedir'); - - if ($failures) { - $message = pht( - "Your server is configured with '%s', which prevents Phabricator ". - "from opening files it requires access to.\n\n". - "Disable this setting to continue.\n\nFailures:\n\n%s", - 'open_basedir', - implode("\n\n", $failures)); - - $issue - ->setIsFatal(true) - ->setMessage($message); - - return; - } else { - $summary = pht( - "You have '%s' configured in your PHP settings, which ". - "may cause some features to fail.", - 'open_basedir'); - - $message = pht( - "You have '%s' configured in your PHP settings. Although this ". - "setting appears permissive enough that Phabricator will work ". - "properly, you may still run into problems because of it.\n\n". - "Consider disabling '%s'.", - 'open_basedir', - 'open_basedir'); - - $issue - ->setSummary($summary) - ->setMessage($message); - } + ->addPHPConfig('open_basedir') + ->setIsFatal(true) + ->setMessage($message); } + } } From 554940b33f4fbe1a56a74a50c2159cd8fbb2c485 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Sep 2016 16:20:33 -0700 Subject: [PATCH 046/865] (stable) Retain repository update cooldowns across daemon restarts Summary: Ref T11665. Fixes T7865. When we restart the daemons, the repository pull daemon currently resets the cooldowns on all of its pulls. This can generate a burst of initial load when restarting a lot of instance daemons (as in the Phacility cluster), described in T7865. This smooths things out so that recent pulls are considered, and any repositories which were waiting keep waiting. Somewhat counterintuitively, hosted repositories write `TYPE_FETCH` status messages, so this should work equally well for hosted and observed repositories. This also paves the way for better backoff behavior on repository errors, described in T11665. The error backoff now uses the same logic that the standard backoff does. The next change will make backoff computation consider recent errors. (This is technically too large for repositories which have encountered one error and have a low commit rate, but I'll fix that in the following change; this is just a checkpoint on the way there.) Test Plan: Ran `bin/phd debug pull`, saw the daemon compute reasonable windows based on previous pull activity. Reviewers: chad Maniphest Tasks: T7865, T11665 Differential Revision: https://secure.phabricator.com/D16574 --- .../PhabricatorRepositoryPullLocalDaemon.php | 99 ++++++++++++++----- .../storage/PhabricatorRepository.php | 2 +- 2 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php index bbd73d2d90..9cbb8db8e9 100644 --- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php +++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php @@ -102,44 +102,59 @@ protected function run() { $retry_after, array_keys($pullable)); - // Figure out which repositories we need to queue for an update. foreach ($pullable as $id => $repository) { - $monogram = $repository->getMonogram(); + $now = PhabricatorTime::getNow(); + $display_name = $repository->getDisplayName(); if (isset($futures[$id])) { - $this->log(pht('Repository "%s" is currently updating.', $monogram)); + $this->log( + pht( + 'Repository "%s" is currently updating.', + $display_name)); continue; } if (isset($queue[$id])) { - $this->log(pht('Repository "%s" is already queued.', $monogram)); - continue; - } - - $after = idx($retry_after, $id, 0); - if ($after > time()) { $this->log( pht( - 'Repository "%s" is not due for an update for %s second(s).', - $monogram, - new PhutilNumber($after - time()))); + 'Repository "%s" is already queued.', + $display_name)); continue; } + $after = idx($retry_after, $id); if (!$after) { + $smart_wait = $repository->loadUpdateInterval($min_sleep); + $last_update = $this->loadLastUpdate($repository); + + $after = $last_update + $smart_wait; + $retry_after[$id] = $after; + $this->log( pht( - 'Scheduling repository "%s" for an initial update.', - $monogram)); - } else { + 'Scheduling repository "%s" with an update window of %s '. + 'second(s). Last update was %s second(s) ago.', + $display_name, + new PhutilNumber($smart_wait), + new PhutilNumber($now - $last_update))); + } + + if ($after > time()) { $this->log( pht( - 'Scheduling repository "%s" for an update (%s seconds overdue).', - $monogram, - new PhutilNumber(time() - $after))); + 'Repository "%s" is not due for an update for %s second(s).', + $display_name, + new PhutilNumber($after - $now))); + continue; } + $this->log( + pht( + 'Scheduling repository "%s" for an update (%s seconds overdue).', + $display_name, + new PhutilNumber($now - $after))); + $queue[$id] = $after; } @@ -157,8 +172,11 @@ protected function run() { continue; } - $monogram = $repository->getMonogram(); - $this->log(pht('Starting update for repository "%s".', $monogram)); + $display_name = $repository->getDisplayName(); + $this->log( + pht( + 'Starting update for repository "%s".', + $display_name)); unset($queue[$id]); $futures[$id] = $this->buildUpdateFuture( @@ -296,6 +314,32 @@ private function loadRepositoryUpdateMessages($consume = false) { } + /** + * @task pull + */ + private function loadLastUpdate(PhabricatorRepository $repository) { + $table = new PhabricatorRepositoryStatusMessage(); + $conn = $table->establishConnection('r'); + + $epoch = queryfx_one( + $conn, + 'SELECT MAX(epoch) last_update FROM %T + WHERE repositoryID = %d + AND statusType IN (%Ls)', + $table->getTableName(), + $repository->getID(), + array( + PhabricatorRepositoryStatusMessage::TYPE_INIT, + PhabricatorRepositoryStatusMessage::TYPE_FETCH, + )); + + if ($epoch) { + return (int)$epoch['last_update']; + } + + return PhabricatorTime::getNow(); + } + /** * @task pull */ @@ -385,9 +429,9 @@ private function resolveUpdateFuture( ExecFuture $future, $min_sleep) { - $monogram = $repository->getMonogram(); + $display_name = $repository->getDisplayName(); - $this->log(pht('Resolving update for "%s".', $monogram)); + $this->log(pht('Resolving update for "%s".', $display_name)); try { list($stdout, $stderr) = $future->resolvex(); @@ -395,17 +439,18 @@ private function resolveUpdateFuture( $proxy = new PhutilProxyException( pht( 'Error while updating the "%s" repository.', - $repository->getMonogram()), + $display_name), $ex); phlog($proxy); - return time() + $min_sleep; + $smart_wait = $repository->loadUpdateInterval($min_sleep); + return PhabricatorTime::getNow() + $smart_wait; } if (strlen($stderr)) { $stderr_msg = pht( 'Unexpected output while updating repository "%s": %s', - $monogram, + $display_name, $stderr); phlog($stderr_msg); } @@ -416,10 +461,10 @@ private function resolveUpdateFuture( pht( 'Based on activity in repository "%s", considering a wait of %s '. 'seconds before update.', - $repository->getMonogram(), + $display_name, new PhutilNumber($smart_wait))); - return time() + $smart_wait; + return PhabricatorTime::getNow() + $smart_wait; } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 3e59c7c53d..3ab3912f2c 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1783,7 +1783,7 @@ public function loadUpdateInterval($minimum = 15) { $smart_wait = $minimum; } - return $smart_wait; + return (int)$smart_wait; } From 894d0dc51bc3dcb3998d2c86eb62e83d3e4c741e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Sep 2016 17:01:59 -0700 Subject: [PATCH 047/865] (stable) When repositories hit pull errors, stop updating them as frequently Summary: Ref T11665. Currently, when a repository hits an error, we retry it after 15s. This is correct if the error was temporary/transient/config-related (e.g., bad network or administrator setting up credentials) but not so great if the error is long-lasting (completely bad authentication, invalid URI, etc), as it can pile up to a meaningful amount of unnecessary load over time. Instead, record how many times in a row we've hit an error and adjust backoff behavior: first error is 15s, then 30s, 45s, etc. Additionally, when computing the backoff for an empty repository, use the repository creation time as though it was the most recent commit. This is a good proxy which gives us reasonable backoff behavior. This required removing the `CODE_WORKING` messages, since they would have reset the error count. We could restore them (as a different type of message), but I think they aren't particularly useful since cloning usually doesn't take too long and there's more status information avilable now than there was when this stuff was written. Test Plan: - Ran `bin/phd debug pull`. - Saw sensible, increasing backoffs selected for repositories with errors. - Saw sensible backoffs selected for empty repositories. Reviewers: chad Maniphest Tasks: T11665 Differential Revision: https://secure.phabricator.com/D16575 --- .../20160919.repo.messagecount.sql | 2 + ...ffusionRepositoryStatusManagementPanel.php | 9 +- .../PhabricatorRepositoryPullEngine.php | 2 - .../storage/PhabricatorRepository.php | 90 ++++++++++++++----- .../PhabricatorRepositoryStatusMessage.php | 3 +- 5 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 resources/sql/autopatches/20160919.repo.messagecount.sql diff --git a/resources/sql/autopatches/20160919.repo.messagecount.sql b/resources/sql/autopatches/20160919.repo.messagecount.sql new file mode 100644 index 0000000000..a28bbb1e7e --- /dev/null +++ b/resources/sql/autopatches/20160919.repo.messagecount.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_statusmessage + ADD messageCount INT UNSIGNED NOT NULL; diff --git a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php index 573162b927..0fb220c385 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryStatusManagementPanel.php @@ -359,20 +359,13 @@ private function buildRepositoryStatus( return $view; } break; - case PhabricatorRepositoryStatusMessage::CODE_WORKING: + default: $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') ->setTarget(pht('Initializing Working Copy')) ->setNote(pht('Daemons are initializing the working copy.'))); return $view; - default: - $view->addItem( - id(new PHUIStatusItemView()) - ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') - ->setTarget(pht('Unknown Init Status')) - ->setNote($message->getStatusCode())); - return $view; } } else { $view->addItem( diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 08fe8ea224..d7a403acf8 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -172,8 +172,6 @@ private function abortPull($message, Exception $ex = null) { } private function logPull($message) { - $code_working = PhabricatorRepositoryStatusMessage::CODE_WORKING; - $this->updateRepositoryInitStatus($code_working, $message); $this->log('%s', $message); } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 3ab3912f2c..ee8e0ad54d 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1649,21 +1649,33 @@ public function writeStatusMessage( $this->getID(), $status_type); } else { + // If the existing message has the same code (e.g., we just hit an + // error and also previously hit an error) we increment the message + // count by 1. This allows us to determine how many times in a row + // we've run into an error. + queryfx( $conn_w, 'INSERT INTO %T - (repositoryID, statusType, statusCode, parameters, epoch) - VALUES (%d, %s, %s, %s, %d) + (repositoryID, statusType, statusCode, parameters, epoch, + messageCount) + VALUES (%d, %s, %s, %s, %d, %d) ON DUPLICATE KEY UPDATE statusCode = VALUES(statusCode), parameters = VALUES(parameters), - epoch = VALUES(epoch)', + epoch = VALUES(epoch), + messageCount = + IF( + statusCode = VALUES(statusCode), + messageCount + 1, + VALUES(messageCount))', $table_name, $this->getID(), $status_type, $status_code, json_encode($parameters), - time()); + time(), + 1); } return $this; @@ -1738,6 +1750,33 @@ public static function assertValidRemoteURI($uri) { * @return int Repository update interval, in seconds. */ public function loadUpdateInterval($minimum = 15) { + // First, check if we've hit errors recently. If we have, wait one period + // for each consecutive error. Normally, this corresponds to a backoff of + // 15s, 30s, 45s, etc. + + $message_table = new PhabricatorRepositoryStatusMessage(); + $conn = $message_table->establishConnection('r'); + $error_count = queryfx_one( + $conn, + 'SELECT MAX(messageCount) error_count FROM %T + WHERE repositoryID = %d + AND statusType IN (%Ls) + AND statusCode IN (%Ls)', + $message_table->getTableName(), + $this->getID(), + array( + PhabricatorRepositoryStatusMessage::TYPE_INIT, + PhabricatorRepositoryStatusMessage::TYPE_FETCH, + ), + array( + PhabricatorRepositoryStatusMessage::CODE_ERROR, + )); + + $error_count = (int)$error_count['error_count']; + if ($error_count > 0) { + return (int)($minimum * $error_count); + } + // If a repository is still importing, always pull it as frequently as // possible. This prevents us from hanging for a long time at 99.9% when // importing an inactive repository. @@ -1758,31 +1797,34 @@ public function loadUpdateInterval($minimum = 15) { $window_start); if ($last_commit) { $time_since_commit = ($window_start - $last_commit['epoch']); + } else { + // If the repository has no commits, treat the creation date as + // though it were the date of the last commit. This makes empty + // repositories update quickly at first but slow down over time + // if they don't see any activity. + $time_since_commit = ($window_start - $this->getDateCreated()); + } - $last_few_days = phutil_units('3 days in seconds'); - - if ($time_since_commit <= $last_few_days) { - // For repositories with activity in the recent past, we wait one - // extra second for every 10 minutes since the last commit. This - // shorter backoff is intended to handle weekends and other short - // breaks from development. - $smart_wait = ($time_since_commit / 600); - } else { - // For repositories without recent activity, we wait one extra second - // for every 4 minutes since the last commit. This longer backoff - // handles rarely used repositories, up to the maximum. - $smart_wait = ($time_since_commit / 240); - } - - // We'll never wait more than 6 hours to pull a repository. - $longest_wait = phutil_units('6 hours in seconds'); - $smart_wait = min($smart_wait, $longest_wait); + $last_few_days = phutil_units('3 days in seconds'); - $smart_wait = max($minimum, $smart_wait); + if ($time_since_commit <= $last_few_days) { + // For repositories with activity in the recent past, we wait one + // extra second for every 10 minutes since the last commit. This + // shorter backoff is intended to handle weekends and other short + // breaks from development. + $smart_wait = ($time_since_commit / 600); } else { - $smart_wait = $minimum; + // For repositories without recent activity, we wait one extra second + // for every 4 minutes since the last commit. This longer backoff + // handles rarely used repositories, up to the maximum. + $smart_wait = ($time_since_commit / 240); } + // We'll never wait more than 6 hours to pull a repository. + $longest_wait = phutil_units('6 hours in seconds'); + $smart_wait = min($smart_wait, $longest_wait); + $smart_wait = max($minimum, $smart_wait); + return (int)$smart_wait; } diff --git a/src/applications/repository/storage/PhabricatorRepositoryStatusMessage.php b/src/applications/repository/storage/PhabricatorRepositoryStatusMessage.php index 79d75cc50c..a9a1b1ae8c 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryStatusMessage.php +++ b/src/applications/repository/storage/PhabricatorRepositoryStatusMessage.php @@ -8,7 +8,6 @@ final class PhabricatorRepositoryStatusMessage const TYPE_NEEDS_UPDATE = 'needs-update'; const CODE_ERROR = 'error'; - const CODE_WORKING = 'working'; const CODE_OKAY = 'okay'; protected $repositoryID; @@ -16,6 +15,7 @@ final class PhabricatorRepositoryStatusMessage protected $statusCode; protected $parameters = array(); protected $epoch; + protected $messageCount; protected function getConfiguration() { return array( @@ -26,6 +26,7 @@ protected function getConfiguration() { self::CONFIG_COLUMN_SCHEMA => array( 'statusType' => 'text32', 'statusCode' => 'text32', + 'messageCount' => 'uint32', ), self::CONFIG_KEY_SCHEMA => array( 'repositoryID' => array( From a768a252af3b3b278f2fc055ade3880d7fcb9c41 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 19 Sep 2016 19:43:24 -0700 Subject: [PATCH 048/865] (stable) Add a default value for messageCount so writes from old tiers survive the update query Auditors: chad --- resources/sql/autopatches/20160919.repo.messagedefault.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 resources/sql/autopatches/20160919.repo.messagedefault.sql diff --git a/resources/sql/autopatches/20160919.repo.messagedefault.sql b/resources/sql/autopatches/20160919.repo.messagedefault.sql new file mode 100644 index 0000000000..0c8b84d44a --- /dev/null +++ b/resources/sql/autopatches/20160919.repo.messagedefault.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_statusmessage + CHANGE messageCount messageCount INT UNSIGNED NOT NULL DEFAULT 0; From 25eea83bc05f17fb5f928f5bfbb4c746eecd1998 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 20 Sep 2016 05:57:52 -0700 Subject: [PATCH 049/865] (stable) Stop doing an excessive amount of work in `diffusion.rawdiffquery` Ref T11665. Without `-n 1`, this logs the ENTIRE history of the repository. We actually get the right result, but this is egregiously slow. Add `-n 1` to return only one result. It appears that I wrote this wrong way back in 2011, in D953. This query is rarely used (until recently) which is likely why it has escaped notice for so long. Test Plan: Used Conduit console to execute `diffusion.rawdiffquery`. Got the same results but spent 8ms instead of 200ms executing this command, in a very small repository. --- .../diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php b/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php index 18e5a9ec9a..41d91c00ca 100644 --- a/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php +++ b/src/applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php @@ -23,7 +23,7 @@ protected function newQueryFuture() { // Check if this is the root commit by seeing if it has parents, since // `git diff X^ X` does not work if "X" is the initial commit. list($parents) = $repository->execxLocalCommand( - 'log --format=%s %s --', + 'log -n 1 --format=%s %s --', '%P', $commit); From 921aab604ec759f2af997faf9b3822ce0e4ee340 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 28 Sep 2016 14:49:30 -0700 Subject: [PATCH 050/865] (stable) Fix an issue where repository message counts would never reset Summary: Fixes T11705. I did not realize that `ON DUPLICATE KEY UPDATE` was order-dependent, so the "reset" clause of this `IF(...)` never actually worked. Reorder it so we check if we're changing the message type //first//, then actually change the message type. This makes the count reset properly when a failing repository succeeds, or a working repository fails. Test Plan: - On `master`, forced a working repository to fail a `bin/repository update`, saw the message change types (expected) but keep the old count (wrong!). - With this patch, repeated the process and saw the count reset properly. - Ran the patch, verified counts reset to 0. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11705 Differential Revision: https://secure.phabricator.com/D16623 --- .../autopatches/20160928.repo.messagecount.sql | 4 ++++ .../storage/PhabricatorRepository.php | 18 +++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 resources/sql/autopatches/20160928.repo.messagecount.sql diff --git a/resources/sql/autopatches/20160928.repo.messagecount.sql b/resources/sql/autopatches/20160928.repo.messagecount.sql new file mode 100644 index 0000000000..e02b257c72 --- /dev/null +++ b/resources/sql/autopatches/20160928.repo.messagecount.sql @@ -0,0 +1,4 @@ +/* Reset message counts to fix the bug in T11705 which caused some of them to + become very large. */ +UPDATE {$NAMESPACE}_repository.repository_statusmessage + SET messageCount = 0; diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index ee8e0ad54d..c27f562864 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1651,8 +1651,12 @@ public function writeStatusMessage( } else { // If the existing message has the same code (e.g., we just hit an // error and also previously hit an error) we increment the message - // count by 1. This allows us to determine how many times in a row - // we've run into an error. + // count. This allows us to determine how many times in a row we've + // run into an error. + + // NOTE: The assignments in "ON DUPLICATE KEY UPDATE" are evaluated + // in order, so the "messageCount" assignment must occur before the + // "statusCode" assignment. See T11705. queryfx( $conn_w, @@ -1661,14 +1665,14 @@ public function writeStatusMessage( messageCount) VALUES (%d, %s, %s, %s, %d, %d) ON DUPLICATE KEY UPDATE - statusCode = VALUES(statusCode), - parameters = VALUES(parameters), - epoch = VALUES(epoch), messageCount = IF( statusCode = VALUES(statusCode), - messageCount + 1, - VALUES(messageCount))', + messageCount + VALUES(messageCount), + VALUES(messageCount)), + statusCode = VALUES(statusCode), + parameters = VALUES(parameters), + epoch = VALUES(epoch)', $table_name, $this->getID(), $status_type, From 8a3d6071a026774cd5de4f6a8d9c51bd22de1b22 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 13 Nov 2016 16:29:33 -0800 Subject: [PATCH 051/865] (stable) Allow bin/aphlict to start without a valid database connection Summary: Ref T11818. See that task for a description. This is like a tiny pinch of hackiness but not really unreasonable. Basically, `aphlict` is already an "uber-server" and one copy can handle a ton of instances. Test Plan: Started `bin/aphlict` without a valid database connection. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11818 Differential Revision: https://secure.phabricator.com/D16854 --- scripts/init/init-aphlict.php | 10 ++++++++++ support/aphlict/server/aphlict_launcher.php | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 scripts/init/init-aphlict.php diff --git a/scripts/init/init-aphlict.php b/scripts/init/init-aphlict.php new file mode 100644 index 0000000000..e2029a0d2a --- /dev/null +++ b/scripts/init/init-aphlict.php @@ -0,0 +1,10 @@ + true, + )); diff --git a/support/aphlict/server/aphlict_launcher.php b/support/aphlict/server/aphlict_launcher.php index f6173fbd6d..af644c58a1 100755 --- a/support/aphlict/server/aphlict_launcher.php +++ b/support/aphlict/server/aphlict_launcher.php @@ -2,7 +2,7 @@ Date: Sun, 13 Nov 2016 16:38:54 -0800 Subject: [PATCH 052/865] (stable) Discard stdout/stderr from the aphlict subprocess when running in daemon (normal) mode Summary: Fixes T11818. We don't discard output, so once we read more than 2GB of output we'll exceed the maximum size of a string in an internal buffer. Instead, configure the future so output is discarded. Test Plan: Added logging to `libphutil/`, saw internal buffer grow steadily before this change and stay constant at 0 after this change. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11818 Differential Revision: https://secure.phabricator.com/D16855 --- .../management/PhabricatorAphlictManagementWorkflow.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php b/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php index b799d0ef90..8522f00421 100644 --- a/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php +++ b/src/applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php @@ -416,6 +416,14 @@ final protected function launch() { while (true) { global $g_future; $g_future = new ExecFuture('exec %C', $command); + + // Discard all output the subprocess produces: it writes to the log on + // disk, so we don't need to send the output anywhere and can just + // throw it away. + $g_future + ->setStdoutSizeLimit(0) + ->setStderrSizeLimit(0); + $g_future->resolve(); // If the server exited, wait a couple of seconds and restart it. From e053534c7e84b09e5f01ac3acb41352bb6a37e05 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 17 Nov 2016 09:34:54 -0800 Subject: [PATCH 053/865] (stable) Prevent typeahead sources from querying against empty tokens Summary: Certain unusual queries, like `[-]`, could tokenize into a list which included the empty string. This would then convert into a query for `... LIKE "%"` which just joins the entire table. Instead: tokenize smarter; never return the empty token; add some test cases. Test Plan: Ran unit tests. Queried for `[[blah blah]]`, saw a reasonable query come out the other end. Reviewers: chad Reviewed By: chad Subscribers: 20after4 Differential Revision: https://secure.phabricator.com/D16888 --- PhabricatorTypeaheadDatasourceTestCase.php | 39 +++++++++++++++++++ .../PhabricatorTypeaheadDatasource.php | 14 ++++++- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 PhabricatorTypeaheadDatasourceTestCase.php diff --git a/PhabricatorTypeaheadDatasourceTestCase.php b/PhabricatorTypeaheadDatasourceTestCase.php new file mode 100644 index 0000000000..92544db4f7 --- /dev/null +++ b/PhabricatorTypeaheadDatasourceTestCase.php @@ -0,0 +1,39 @@ +assertTokenization( + 'The quick brown fox', + array('the', 'quick', 'brown', 'fox')); + + $this->assertTokenization( + 'Quack quack QUACK', + array('quack')); + + $this->assertTokenization( + '', + array()); + + $this->assertTokenization( + ' [ - ] ', + array()); + + $this->assertTokenization( + 'jury-rigged', + array('jury', 'rigged')); + + $this->assertTokenization( + '[[ brackets ]] [-] ]-[ tie-fighters', + array('brackets', 'tie', 'fighters')); + } + + private function assertTokenization($input, $expect) { + $this->assertEqual( + $expect, + PhabricatorTypeaheadDatasource::tokenizeString($input), + pht('Tokenization of "%s"', $input)); + } + +} diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index 163b6c40b3..cfea3bd30c 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -141,8 +141,18 @@ public static function tokenizeString($string) { return array(); } - $tokens = preg_split('/\s+|[-\[\]]/u', $string); - return array_unique($tokens); + $tokens = preg_split('/[\s\[\]-]+/u', $string); + $tokens = array_unique($tokens); + + // Make sure we don't return the empty token, as this will boil down to a + // JOIN against every token. + foreach ($tokens as $key => $value) { + if (!strlen($value)) { + unset($tokens[$key]); + } + } + + return array_values($tokens); } public function getTokens() { From 7ebc47d906fecc5b7952baeb8f25c4d69f5604ba Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 17 Nov 2016 10:23:39 -0800 Subject: [PATCH 054/865] (stable) Fix an EditEngine issue with unlocking fields which can't be locked Summary: This code should go inside the field-locking loop. Otherwise, it only applies to the last field, and fatals if there are no fields. Test Plan: Carefuller inspection. Reviewers: chad Reviewed By: chad Subscribers: 20after4 Differential Revision: https://secure.phabricator.com/D16889 --- .../storage/PhabricatorEditEngineConfiguration.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php index fc778c65b7..c1b1308801 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php @@ -158,11 +158,11 @@ public function applyConfigurationToFields( // If we don't have an explicit value, don't make any adjustments. break; } - } - // If the field isn't lockable, remove any lock we applied. - if (!$field->getIsLockable()) { - $field->setIsLocked(false); + // If the field isn't lockable, remove any lock we applied. + if (!$field->getIsLockable()) { + $field->setIsLocked(false); + } } $fields = $this->reorderFields($fields); From bb6bd8c8d034ecad3e95af27261b15cac0d78f1e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 17 Nov 2016 14:50:50 -0800 Subject: [PATCH 055/865] (stable) When a field isn't lockable, just freeze the lock status instead of removing any lock Summary: See downstream issue here: In at least one case (project milestones) we have a locked, non-lockable field. This means "this is locked, and you can't change the fact that it is locked". At least for now, preserve this behavior. Test Plan: Created a new milestone of an existing project. This worked correctly with the patch. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D16895 --- .../PhabricatorEditEngineConfiguration.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php index c1b1308801..65359bb0dd 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php @@ -144,25 +144,26 @@ public function applyConfigurationToFields( switch (idx($locks, $key)) { case self::LOCK_LOCKED: $field->setIsHidden(false); - $field->setIsLocked(true); + if ($field->getIsLockable()) { + $field->setIsLocked(true); + } break; case self::LOCK_HIDDEN: $field->setIsHidden(true); - $field->setIsLocked(false); + if ($field->getIsLockable()) { + $field->setIsLocked(false); + } break; case self::LOCK_VISIBLE: $field->setIsHidden(false); - $field->setIsLocked(false); + if ($field->getIsLockable()) { + $field->setIsLocked(false); + } break; default: // If we don't have an explicit value, don't make any adjustments. break; } - - // If the field isn't lockable, remove any lock we applied. - if (!$field->getIsLockable()) { - $field->setIsLocked(false); - } } $fields = $this->reorderFields($fields); From 74a0e5cd7930ac06013ed6fafc4fd96e7730b40c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 19 Nov 2016 08:35:29 -0800 Subject: [PATCH 056/865] (stable) Respect user/pass flags to bin/storage for direct DatabaseRef-based queries Summary: Ref T11893. Previously, we excuted all `bin/storage` queries through `StorageManagementAPI` objects. After D16848, we execute some queries through `PhabricatorDatabaseRef`. However, the refs we use weren't getting passed the `-u` / `-p` flags correctly, for specifying alternate administrative credentials. Test Plan: - Created a second account (`trunk`). - Ran `bin/storage -u trunk`. - Made libphutil throw when not connecting as that user. - Before patch: some connections incorrectly used the default user, "root". - After patch: all connections correctly used the configured user, "trunk". Reviewers: chad Reviewed By: chad Maniphest Tasks: T11893 Differential Revision: https://secure.phabricator.com/D16901 --- scripts/sql/manage_storage.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php index db21fc23f5..ec3f492e8c 100755 --- a/scripts/sql/manage_storage.php +++ b/scripts/sql/manage_storage.php @@ -206,6 +206,9 @@ ->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); PhabricatorEnv::overrideConfig('mysql.user', $api->getUser()); + $ref->setUser($selected_user); + $ref->setPass($password); + try { queryfx( $api->getConn(null), From 3cd0001d8b95897d5efafa0c391a9e870fd35471 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 11 Dec 2016 07:15:32 -0800 Subject: [PATCH 057/865] (stable) Fix two cache issues (global settings; initial setup) Summary: - Fixes T11995. This got moved but I missed renaming this callsite. - Fixes T11993. If you have valid credentials, but haven't run `storage upgrade` yet, we can hit this exception during setup. Just ignore it instead. Test Plan: - Saved global settings, no more fatal. - Changed `storage-namespace` to junk, loaded web UI with valid database credentials. {F2106358} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11993, T11995 Differential Revision: https://secure.phabricator.com/D17024 --- src/applications/config/check/PhabricatorSetupCheck.php | 8 ++------ .../settings/editor/PhabricatorUserPreferencesEditor.php | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/applications/config/check/PhabricatorSetupCheck.php b/src/applications/config/check/PhabricatorSetupCheck.php index 0d9101efe3..e0574b7636 100644 --- a/src/applications/config/check/PhabricatorSetupCheck.php +++ b/src/applications/config/check/PhabricatorSetupCheck.php @@ -82,12 +82,12 @@ final public static function resetSetupState() { AphrontWriteGuard::allowDangerousUnguardedWrites(true); } - $caught = null; try { $db_cache = new PhabricatorKeyValueDatabaseCache(); $db_cache->deleteKey('phabricator.setup.issue-keys'); } catch (Exception $ex) { - $caught = $ex; + // If we hit an exception here, just ignore it. In particular, this can + // happen on initial startup before the databases are initialized. } if ($use_scope) { @@ -95,10 +95,6 @@ final public static function resetSetupState() { } else { AphrontWriteGuard::allowDangerousUnguardedWrites(false); } - - if ($caught) { - throw $caught; - } } final public static function setOpenSetupIssueKeys( diff --git a/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php b/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php index c2ec0a7094..a927cc7475 100644 --- a/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php +++ b/src/applications/settings/editor/PhabricatorUserPreferencesEditor.php @@ -159,7 +159,7 @@ protected function applyFinalEffects( $user_phid); } else { $cache = PhabricatorCaches::getMutableStructureCache(); - $cache->deleteKey(PhabricatorUserPreferences::getGlobalCacheKey()); + $cache->deleteKey(PhabricatorUser::getGlobalSettingsCacheKey()); PhabricatorUserCache::clearCacheForAllUsers( PhabricatorUserPreferencesCacheType::KEY_PREFERENCES); From 1cd64f9975d66a5fff516cd064cd076b0eb7b07f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 13 Dec 2016 14:02:04 -0800 Subject: [PATCH 058/865] (stable) Don't combine automatic output compression with "Content-Length" Summary: Fixes T12013. Send either "Content-Length" or enable output compression, but not both. Prefer compression for static resources (CSS, JS, etc). Test Plan: Ran `curl -v ...`, no longer saw responses with both compression and `Content-Length`. Reviewers: chad, avivey Reviewed By: avivey Subscribers: avivey Maniphest Tasks: T12013 Differential Revision: https://secure.phabricator.com/D17045 --- src/aphront/response/AphrontFileResponse.php | 18 +++++++++++++++++- src/aphront/response/AphrontResponse.php | 15 +++++++++++++++ src/aphront/sink/AphrontHTTPSink.php | 2 ++ .../controller/CelerityResourceController.php | 7 ++++--- support/PhabricatorStartup.php | 5 ----- 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/aphront/response/AphrontFileResponse.php b/src/aphront/response/AphrontFileResponse.php index 4688e3bc8f..a8f673d218 100644 --- a/src/aphront/response/AphrontFileResponse.php +++ b/src/aphront/response/AphrontFileResponse.php @@ -5,6 +5,7 @@ final class AphrontFileResponse extends AphrontResponse { private $content; private $contentIterator; private $contentLength; + private $compressResponse; private $mimeType; private $download; @@ -69,6 +70,15 @@ public function getContentLength() { return $this->contentLength; } + public function setCompressResponse($compress_response) { + $this->compressResponse = $compress_response; + return $this; + } + + public function getCompressResponse() { + return $this->compressResponse; + } + public function setRange($min, $max) { $this->rangeMin = $min; $this->rangeMax = $max; @@ -94,7 +104,9 @@ public function getHeaders() { $content_len = $this->getContentLength(); } - $headers[] = array('Content-Length', $this->getContentLength()); + if (!$this->shouldCompressResponse()) { + $headers[] = array('Content-Length', $this->getContentLength()); + } if (strlen($this->getDownload())) { $headers[] = array('X-Download-Options', 'noopen'); @@ -118,4 +130,8 @@ public function getHeaders() { return $headers; } + protected function shouldCompressResponse() { + return $this->getCompressResponse(); + } + } diff --git a/src/aphront/response/AphrontResponse.php b/src/aphront/response/AphrontResponse.php index b213244f1c..6c52e417e3 100644 --- a/src/aphront/response/AphrontResponse.php +++ b/src/aphront/response/AphrontResponse.php @@ -242,6 +242,21 @@ private function formatEpochTimestampForHTTPHeader($epoch_timestamp) { return gmdate('D, d M Y H:i:s', $epoch_timestamp).' GMT'; } + protected function shouldCompressResponse() { + return true; + } + + public function willBeginWrite() { + if ($this->shouldCompressResponse()) { + // Enable automatic compression here. Webservers sometimes do this for + // us, but we now detect the absence of compression and warn users about + // it so try to cover our bases more thoroughly. + ini_set('zlib.output_compression', 1); + } else { + ini_set('zlib.output_compression', 0); + } + } + public function didCompleteWrite($aborted) { return; } diff --git a/src/aphront/sink/AphrontHTTPSink.php b/src/aphront/sink/AphrontHTTPSink.php index 4a729da16e..11c6466cf1 100644 --- a/src/aphront/sink/AphrontHTTPSink.php +++ b/src/aphront/sink/AphrontHTTPSink.php @@ -96,6 +96,8 @@ final public function writeData($data) { * @return void */ final public function writeResponse(AphrontResponse $response) { + $response->willBeginWrite(); + // Build the content iterator first, in case it throws. Ideally, we'd // prefer to handle exceptions before we emit the response status or any // HTTP headers. diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php index 95d674c641..0f1478ec5c 100644 --- a/src/applications/celerity/controller/CelerityResourceController.php +++ b/src/applications/celerity/controller/CelerityResourceController.php @@ -103,9 +103,10 @@ protected function serveResource(array $spec) { } } - $response = new AphrontFileResponse(); - $response->setContent($data); - $response->setMimeType($type_map[$type]); + $response = id(new AphrontFileResponse()) + ->setContent($data) + ->setMimeType($type_map[$type]) + ->setCompressResponse(true); // NOTE: This is a piece of magic required to make WOFF fonts work in // Firefox and IE. Possibly we should generalize this more. diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 26c6b26a9c..0a27a616e3 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -395,11 +395,6 @@ private static function setupPHP() { if (function_exists('libxml_disable_entity_loader')) { libxml_disable_entity_loader(true); } - - // Enable automatic compression here. Webservers sometimes do this for - // us, but we now detect the absence of compression and warn users about - // it so try to cover our bases more thoroughly. - ini_set('zlib.output_compression', 1); } From 40be2d53743ca3da83b579985e0acad7d852b8d1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 8 Jan 2017 10:53:35 -0800 Subject: [PATCH 059/865] (stable) Fix excessively strict "Can Use Application" policy filtering Summary: Ref T9058. The stricter filtering is over-filtering Handles. For example, in the Phacility cluster, users can not see Almanac services. So this filtering happens: - The AlmanacServiceQuery filters the service beacuse they can't see the application. - The HandleQuery generates a "you can't see this" handle. - But then the HandleQuery filters that handle! It has a "service" PHID and the user can't see Almanac. This violates the assumption that all application code makes about handles: it's OK to query handles for objects you can't see, and you'll get something back. Instead, don't do application filtering on handles. Test Plan: - Added a failing test and made it pass. - As a user who can not see Almanac, viewed an Instances timeline. - Before patch: fatal on trying to load a handle for a Service. - After patch: smooth sailing. Reviewers: chad Maniphest Tasks: T9058 Differential Revision: https://secure.phabricator.com/D17152 --- .../policy/filter/PhabricatorPolicyFilter.php | 13 ++++++++++++ .../PhabricatorProjectCoreTestCase.php | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php index 855149f6b3..e0c223cd2c 100644 --- a/src/applications/policy/filter/PhabricatorPolicyFilter.php +++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php @@ -874,6 +874,19 @@ private function applyApplicationChecks(array $objects) { $viewer = $this->viewer; foreach ($objects as $key => $object) { + // Don't filter handles: users are allowed to see handles from an + // application they can't see even if they can not see objects from + // that application. Note that the application policies still apply to + // the underlying object, so these will be "Restricted Object" handles. + + // If we don't let these through, PhabricatorHandleQuery will completely + // fail to load results for PHIDs that are part of applications which + // the viewer can not see, but a fundamental property of handles is that + // we always load something and they can safely be assumed to load. + if ($object instanceof PhabricatorObjectHandle) { + continue; + } + $phid = $object->getPHID(); if (!$phid) { continue; diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index cb977dd681..71161b031e 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -51,6 +51,13 @@ public function testApplicationPolicy() { $proj, PhabricatorPolicyCapability::CAN_VIEW)); + // This object is visible so its handle should load normally. + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($user) + ->withPHIDs(array($proj->getPHID())) + ->executeOne(); + $this->assertEqual($proj->getPHID(), $handle->getPHID()); + // Change the "Can Use Application" policy for Projecs to "No One". This // should cause filtering checks to fail even when they are executed // directly rather than via a Query. @@ -76,6 +83,19 @@ public function testApplicationPolicy() { $proj, PhabricatorPolicyCapability::CAN_VIEW)); + // We should still be able to load a handle for the project, even if we + // can not see the application. + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($user) + ->withPHIDs(array($proj->getPHID())) + ->executeOne(); + + // The handle should load... + $this->assertEqual($proj->getPHID(), $handle->getPHID()); + + // ...but be policy filtered. + $this->assertTrue($handle->getPolicyFiltered()); + unset($env); } From ab4355cde0f8913b9e12be345f813f5ba7e0e468 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 21 Jan 2017 08:11:19 -0800 Subject: [PATCH 060/865] (stable) Move Favorites and User menus to MenuBarExtensions Summary: Ref T12140. The major effect of this change is that uninstalling "Home" (as we do on admin.phacility.com) no longer uninstalls the user menu (which is required to access settings or log out). This also simplifies the code a bit, by consolidating how menus are built into MenuBarExtensions instead of some in Applications and some in Extensions. Test Plan: - While logged in and logged out, saw main menus in the correct order. - Uninstalled Favorites, saw the menu vanish. - Uninstalled Home, still had a user menu. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12140 Differential Revision: https://secure.phabricator.com/D17239 --- src/__phutil_library_map__.php | 4 + .../PhabricatorAuthMainMenuBarExtension.php | 4 + .../base/PhabricatorApplication.php | 17 --- .../PhabricatorFavoritesApplication.php | 78 ------------ ...abricatorFavoritesMainMenuBarExtension.php | 96 +++++++++++++++ .../PhabricatorHomeApplication.php | 110 ----------------- .../PeopleMainMenuBarExtension.php | 116 ++++++++++++++++++ .../menu/PhabricatorMainMenuBarExtension.php | 5 + .../page/menu/PhabricatorMainMenuView.php | 21 +--- 9 files changed, 231 insertions(+), 220 deletions(-) create mode 100644 src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php create mode 100644 src/applications/people/engineextension/PeopleMainMenuBarExtension.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c828c6b04e..7ce9b1c72d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1785,6 +1785,7 @@ 'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php', 'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php', 'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php', + 'PeopleMainMenuBarExtension' => 'applications/people/engineextension/PeopleMainMenuBarExtension.php', 'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php', 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', @@ -2671,6 +2672,7 @@ 'PhabricatorFavoritesApplication' => 'applications/favorites/application/PhabricatorFavoritesApplication.php', 'PhabricatorFavoritesController' => 'applications/favorites/controller/PhabricatorFavoritesController.php', 'PhabricatorFavoritesMainController' => 'applications/favorites/controller/PhabricatorFavoritesMainController.php', + 'PhabricatorFavoritesMainMenuBarExtension' => 'applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php', 'PhabricatorFavoritesMenuItemController' => 'applications/favorites/controller/PhabricatorFavoritesMenuItemController.php', 'PhabricatorFavoritesProfileMenuEngine' => 'applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php', 'PhabricatorFaxContentSource' => 'infrastructure/contentsource/PhabricatorFaxContentSource.php', @@ -6662,6 +6664,7 @@ 'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability', 'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability', 'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', + 'PeopleMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -7685,6 +7688,7 @@ 'PhabricatorFavoritesApplication' => 'PhabricatorApplication', 'PhabricatorFavoritesController' => 'PhabricatorController', 'PhabricatorFavoritesMainController' => 'PhabricatorFavoritesController', + 'PhabricatorFavoritesMainMenuBarExtension' => 'PhabricatorMainMenuBarExtension', 'PhabricatorFavoritesMenuItemController' => 'PhabricatorFavoritesController', 'PhabricatorFavoritesProfileMenuEngine' => 'PhabricatorProfileMenuEngine', 'PhabricatorFaxContentSource' => 'PhabricatorContentSource', diff --git a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php index 2757bec91c..d9d251ab07 100644 --- a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php +++ b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php @@ -9,6 +9,10 @@ public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { return true; } + public function getExtensionOrder() { + return 900; + } + public function buildMainMenus() { $viewer = $this->getViewer(); diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index e50045b0e9..63ab9d2315 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -315,23 +315,6 @@ public function buildMainMenuItems( } - /** - * Build extra items for the main menu. Generally, this is used to render - * static dropdowns. - * - * @param PhabricatorUser The viewing user. - * @param AphrontController The current controller. May be null for special - * pages like 404, exception handlers, etc. - * @return view List of menu items. - * @task ui - */ - public function buildMainMenuExtraNodes( - PhabricatorUser $viewer, - PhabricatorController $controller = null) { - return array(); - } - - /* -( Application Management )--------------------------------------------- */ diff --git a/src/applications/favorites/application/PhabricatorFavoritesApplication.php b/src/applications/favorites/application/PhabricatorFavoritesApplication.php index 958355a2b5..1d5d3ae2aa 100644 --- a/src/applications/favorites/application/PhabricatorFavoritesApplication.php +++ b/src/applications/favorites/application/PhabricatorFavoritesApplication.php @@ -32,82 +32,4 @@ public function isLaunchable() { return false; } - public function buildMainMenuExtraNodes( - PhabricatorUser $viewer, - PhabricatorController $controller = null) { - - $dropdown = $this->renderFavoritesDropdown($viewer); - if (!$dropdown) { - return null; - } - - return id(new PHUIButtonView()) - ->setTag('a') - ->setHref('#') - ->setIcon('fa-star') - ->addClass('phabricator-core-user-menu') - ->setNoCSS(true) - ->setDropdown(true) - ->setDropdownMenu($dropdown); - } - - private function renderFavoritesDropdown(PhabricatorUser $viewer) { - $application = __CLASS__; - - $applications = id(new PhabricatorApplicationQuery()) - ->setViewer($viewer) - ->withClasses(array($application)) - ->withInstalled(true) - ->execute(); - $favorites = head($applications); - if (!$favorites) { - return null; - } - - $menu_engine = id(new PhabricatorFavoritesProfileMenuEngine()) - ->setViewer($viewer) - ->setProfileObject($favorites); - - if ($viewer->getPHID()) { - $menu_engine - ->setCustomPHID($viewer->getPHID()) - ->setMenuType(PhabricatorProfileMenuEngine::MENU_COMBINED); - } else { - $menu_engine - ->setMenuType(PhabricatorProfileMenuEngine::MENU_GLOBAL); - } - - $filter_view = $menu_engine->buildNavigation(); - - $menu_view = $filter_view->getMenu(); - $item_views = $menu_view->getItems(); - - $view = id(new PhabricatorActionListView()) - ->setViewer($viewer); - foreach ($item_views as $item) { - $type = null; - if (!strlen($item->getName())) { - $type = PhabricatorActionView::TYPE_DIVIDER; - } - $action = id(new PhabricatorActionView()) - ->setName($item->getName()) - ->setHref($item->getHref()) - ->setType($type); - $view->addAction($action); - } - - // Build out edit interface - if ($viewer->isLoggedIn()) { - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Favorites')) - ->setHref('/favorites/')); - } - - return $view; - } - } diff --git a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php new file mode 100644 index 0000000000..990ecad993 --- /dev/null +++ b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php @@ -0,0 +1,96 @@ +getViewer(); + + $dropdown = $this->newDropdown($viewer); + if (!$dropdown) { + return null; + } + + $favorites_menu = id(new PHUIButtonView()) + ->setTag('a') + ->setHref('#') + ->setIcon('fa-star') + ->addClass('phabricator-core-user-menu') + ->setNoCSS(true) + ->setDropdown(true) + ->setDropdownMenu($dropdown); + + return array( + $favorites_menu, + ); + } + + private function newDropdown(PhabricatorUser $viewer) { + $applications = id(new PhabricatorApplicationQuery()) + ->setViewer($viewer) + ->withClasses(array('PhabricatorFavoritesApplication')) + ->withInstalled(true) + ->execute(); + $favorites = head($applications); + if (!$favorites) { + return null; + } + + $menu_engine = id(new PhabricatorFavoritesProfileMenuEngine()) + ->setViewer($viewer) + ->setProfileObject($favorites); + + if ($viewer->getPHID()) { + $menu_engine + ->setCustomPHID($viewer->getPHID()) + ->setMenuType(PhabricatorProfileMenuEngine::MENU_COMBINED); + } else { + $menu_engine + ->setMenuType(PhabricatorProfileMenuEngine::MENU_GLOBAL); + } + + $filter_view = $menu_engine->buildNavigation(); + + $menu_view = $filter_view->getMenu(); + $item_views = $menu_view->getItems(); + + $view = id(new PhabricatorActionListView()) + ->setViewer($viewer); + foreach ($item_views as $item) { + $type = null; + if (!strlen($item->getName())) { + $type = PhabricatorActionView::TYPE_DIVIDER; + } + $action = id(new PhabricatorActionView()) + ->setName($item->getName()) + ->setHref($item->getHref()) + ->setType($type); + $view->addAction($action); + } + + if ($viewer->isLoggedIn()) { + $view->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Favorites')) + ->setHref('/favorites/')); + } + + return $view; + } + +} diff --git a/src/applications/home/application/PhabricatorHomeApplication.php b/src/applications/home/application/PhabricatorHomeApplication.php index 20858e4931..a860bc8fb3 100644 --- a/src/applications/home/application/PhabricatorHomeApplication.php +++ b/src/applications/home/application/PhabricatorHomeApplication.php @@ -42,114 +42,4 @@ public function getApplicationOrder() { return 9; } - public function buildMainMenuExtraNodes( - PhabricatorUser $viewer, - PhabricatorController $controller = null) { - - if (!$viewer->isLoggedIn()) { - return; - } - - $image = $viewer->getProfileImageURI(); - - $profile_image = id(new PHUIIconView()) - ->setImage($image) - ->setHeadSize(PHUIIconView::HEAD_SMALL); - - if ($controller) { - $application = $controller->getCurrentApplication(); - } else { - $application = null; - } - $dropdown_menu = $this->renderUserDropdown($viewer, $application); - - $menu_id = celerity_generate_unique_node_id(); - - Javelin::initBehavior( - 'user-menu', - array( - 'menuID' => $menu_id, - 'menu' => $dropdown_menu->getDropdownMenuMetadata(), - )); - - return id(new PHUIButtonView()) - ->setID($menu_id) - ->setTag('a') - ->setHref('/p/'.$viewer->getUsername().'/') - ->setIcon($profile_image) - ->addClass('phabricator-core-user-menu') - ->setHasCaret(true) - ->setNoCSS(true); - } - - private function renderUserDropdown( - PhabricatorUser $viewer, - $application) { - - $person_to_show = id(new PHUIObjectItemView()) - ->setObjectName($viewer->getRealName()) - ->setSubHead($viewer->getUsername()) - ->setImageURI($viewer->getProfileImageURI()); - - $user_view = id(new PHUIObjectItemListView()) - ->setViewer($viewer) - ->setFlush(true) - ->setSimple(true) - ->addItem($person_to_show) - ->addClass('phabricator-core-user-profile-object'); - - $view = id(new PhabricatorActionListView()) - ->setViewer($viewer); - - // User Menu - $view->addAction( - id(new PhabricatorActionView()) - ->appendChild($user_view)); - - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Profile')) - ->setHref('/p/'.$viewer->getUsername().'/')); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Settings')) - ->setHref('/settings/user/'.$viewer->getUsername().'/')); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Manage')) - ->setHref('/people/manage/'.$viewer->getID().'/')); - - // Help Menus - if ($application) { - $help_links = $application->getHelpMenuItems($viewer); - if ($help_links) { - foreach ($help_links as $link) { - $view->addAction($link); - } - } - } - - // Logout Menu - $view->addAction( - id(new PhabricatorActionView()) - ->addSigil('logout-item') - ->setType(PhabricatorActionView::TYPE_DIVIDER)); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Log Out %s', $viewer->getUsername())) - ->addSigil('logout-item') - ->setHref('/logout/') - ->setColor(PhabricatorActionView::RED) - ->setWorkflow(true)); - - return $view; - } - } diff --git a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php new file mode 100644 index 0000000000..cd1cffa7a2 --- /dev/null +++ b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php @@ -0,0 +1,116 @@ +isLoggedIn(); + } + + public function getExtensionOrder() { + return 1200; + } + + public function buildMainMenus() { + $viewer = $this->getViewer(); + $application = $this->getApplication(); + $dropdown_menu = $this->newDropdown($viewer, $application); + + $menu_id = celerity_generate_unique_node_id(); + + Javelin::initBehavior( + 'user-menu', + array( + 'menuID' => $menu_id, + 'menu' => $dropdown_menu->getDropdownMenuMetadata(), + )); + + $image = $viewer->getProfileImageURI(); + $profile_image = id(new PHUIIconView()) + ->setImage($image) + ->setHeadSize(PHUIIconView::HEAD_SMALL); + + $user_menu = id(new PHUIButtonView()) + ->setID($menu_id) + ->setTag('a') + ->setHref('/p/'.$viewer->getUsername().'/') + ->setIcon($profile_image) + ->addClass('phabricator-core-user-menu') + ->setHasCaret(true) + ->setNoCSS(true); + + return array( + $user_menu, + ); + } + + private function newDropdown( + PhabricatorUser $viewer, + $application) { + + $person_to_show = id(new PHUIObjectItemView()) + ->setObjectName($viewer->getRealName()) + ->setSubHead($viewer->getUsername()) + ->setImageURI($viewer->getProfileImageURI()); + + $user_view = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true) + ->setSimple(true) + ->addItem($person_to_show) + ->addClass('phabricator-core-user-profile-object'); + + $view = id(new PhabricatorActionListView()) + ->setViewer($viewer); + + $view->addAction( + id(new PhabricatorActionView()) + ->appendChild($user_view)); + + $view->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Profile')) + ->setHref('/p/'.$viewer->getUsername().'/')); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Settings')) + ->setHref('/settings/user/'.$viewer->getUsername().'/')); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage')) + ->setHref('/people/manage/'.$viewer->getID().'/')); + + if ($application) { + $help_links = $application->getHelpMenuItems($viewer); + if ($help_links) { + foreach ($help_links as $link) { + $view->addAction($link); + } + } + } + + $view->addAction( + id(new PhabricatorActionView()) + ->addSigil('logout-item') + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Log Out %s', $viewer->getUsername())) + ->addSigil('logout-item') + ->setHref('/logout/') + ->setColor(PhabricatorActionView::RED) + ->setWorkflow(true)); + + return $view; + } + +} diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php index 8b863c835c..fede2fdb85 100644 --- a/src/view/page/menu/PhabricatorMainMenuBarExtension.php +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -64,12 +64,17 @@ public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { return true; } + public function getExtensionOrder() { + return 1000; + } + abstract public function buildMainMenus(); final public static function getAllExtensions() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setUniqueMethod('getExtensionKey') + ->setSortMethod('getExtensionOrder') ->execute(); } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 75071a9ee5..e923a5e348 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -82,20 +82,6 @@ public function render() { phutil_implode_html(' ', $aural)); } - // Build out Header Menus - $applications = PhabricatorApplication::getAllInstalledApplications(); - - $menus = array(); - $controller = $this->getController(); - foreach ($applications as $application) { - $app_extra = $application->buildMainMenuExtraNodes( - $viewer, - $controller); - if ($app_extra !== null) { - $menus[] = $app_extra; - } - } - $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { $extension->setViewer($viewer); @@ -116,13 +102,18 @@ public function render() { } } - // Builds out "login" button + $menus = array(); foreach ($extensions as $extension) { foreach ($extension->buildMainMenus() as $menu) { $menus[] = $menu; } } + // Because we display these with "float: right", reverse their order before + // rendering them into the document so that the extension order and display + // order are the same. + $menus = array_reverse($menus); + foreach ($menus as $menu) { $menu_bar[] = $menu; } From 23c54262caf84abdad82974212170ff24d138f4d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 22 Jan 2017 08:36:16 -0800 Subject: [PATCH 061/865] Fix fatal saving menu items without custom validation Summary: Fixes T12142. Correct spelling of method. Test Plan: Edit the name of a Details menu item in projects, or add a divider. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12142 Differential Revision: https://secure.phabricator.com/D17240 --- src/applications/search/menuitem/PhabricatorProfileMenuItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php index 05fa6543d3..6f1342d4c0 100644 --- a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php @@ -70,7 +70,7 @@ protected function newItem() { return new PHUIListItemView(); } - public function valdateTransactions( + public function validateTransactions( PhabricatorProfileMenuItemConfiguration $config, $field_key, $value, From 2dc4692021c31cb4198541c6f15a59483022d3ee Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Jan 2017 12:14:02 -0800 Subject: [PATCH 062/865] (stable) Allow menu items to render their own content; make Dashboard items render on-page Summary: Ref T11957. When you click a dashboard item, it now sends you to `//item/view/123/`, which renders the proper crumbs, navigation, etc., with the dashboard as page content. This works as you'd expect in Projects: {F2508568} It's sliiiightly odd in Favorites since we nuke the nav menu, but seems basically fine? {F2508571} Test Plan: - Created a dashboard panel on a project. - Clicked it, saw it render. - Made it the default panel, viewed project default screen, saw dashboard. - Disabled every panel I could, still saw reasonable behavior (this is silly anyway). Reviewers: chad Reviewed By: chad Maniphest Tasks: T11957 Differential Revision: https://secure.phabricator.com/D17255 --- .../PhabricatorFavoritesProfileMenuEngine.php | 2 +- .../PhabricatorHomeProfileMenuEngine.php | 2 +- .../PhabricatorPeopleProfileMenuEngine.php | 2 +- .../PhabricatorProjectViewController.php | 3 +- .../PhabricatorProjectProfileMenuEngine.php | 2 +- ...habricatorProjectManageProfileMenuItem.php | 5 -- .../engine/PhabricatorProfileMenuEngine.php | 55 +++++++++++++------ .../PhabricatorDashboardProfileMenuItem.php | 32 ++++++++++- .../menuitem/PhabricatorProfileMenuItem.php | 24 ++++++++ ...habricatorProfileMenuItemConfiguration.php | 13 +++++ 10 files changed, 113 insertions(+), 27 deletions(-) diff --git a/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php b/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php index 3c86d19a58..87b59e3e0f 100644 --- a/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php +++ b/src/applications/favorites/engine/PhabricatorFavoritesProfileMenuEngine.php @@ -7,7 +7,7 @@ protected function isMenuEngineConfigurable() { return true; } - protected function getItemURI($path) { + public function getItemURI($path) { $object = $this->getProfileObject(); $custom = $this->getCustomPHID(); diff --git a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php index ebcc92b189..528edba697 100644 --- a/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php +++ b/src/applications/home/engine/PhabricatorHomeProfileMenuEngine.php @@ -7,7 +7,7 @@ protected function isMenuEngineConfigurable() { return true; } - protected function getItemURI($path) { + public function getItemURI($path) { $object = $this->getProfileObject(); $custom = $this->getCustomPHID(); diff --git a/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php b/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php index 9965147a12..3d67872772 100644 --- a/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php +++ b/src/applications/people/engine/PhabricatorPeopleProfileMenuEngine.php @@ -10,7 +10,7 @@ protected function isMenuEngineConfigurable() { return false; } - protected function getItemURI($path) { + public function getItemURI($path) { $user = $this->getProfileObject(); $username = $user->getUsername(); $username = phutil_escape_uri($username); diff --git a/src/applications/project/controller/PhabricatorProjectViewController.php b/src/applications/project/controller/PhabricatorProjectViewController.php index d2f8d4f58d..7d5dc37e0b 100644 --- a/src/applications/project/controller/PhabricatorProjectViewController.php +++ b/src/applications/project/controller/PhabricatorProjectViewController.php @@ -25,9 +25,10 @@ public function handleRequest(AphrontRequest $request) { $controller_object = new PhabricatorProjectBoardViewController(); break; case PhabricatorProject::ITEM_PROFILE: - default: $controller_object = new PhabricatorProjectProfileController(); break; + default: + return $engine->buildResponse(); } return $this->delegateToController($controller_object); diff --git a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php index d3ea39e1e2..a39e7a8236 100644 --- a/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php +++ b/src/applications/project/engine/PhabricatorProjectProfileMenuEngine.php @@ -7,7 +7,7 @@ protected function isMenuEngineConfigurable() { return true; } - protected function getItemURI($path) { + public function getItemURI($path) { $project = $this->getProfileObject(); $id = $project->getID(); return "/project/{$id}/item/{$path}"; diff --git a/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php index 1bd7e796dc..ddb59ec095 100644 --- a/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php @@ -18,11 +18,6 @@ public function canHideMenuItem( return false; } - public function canMakeDefault( - PhabricatorProfileMenuItemConfiguration $config) { - return true; - } - public function getDisplayName( PhabricatorProfileMenuItemConfiguration $config) { $name = $config->getMenuItemProperty('name'); diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php index 3e11436ced..a6db1b2d2b 100644 --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -82,7 +82,7 @@ public function getShowNavigation() { return $this->showNavigation; } - abstract protected function getItemURI($path); + abstract public function getItemURI($path); abstract protected function isMenuEngineConfigurable(); abstract protected function getBuiltinProfileItems($object); @@ -102,6 +102,9 @@ public function buildResponse() { $request = $controller->getRequest(); $item_action = $request->getURIData('itemAction'); + if (!$item_action) { + $item_action = 'view'; + } // If the engine is not configurable, don't respond to any of the editing // or configuration routes. @@ -136,6 +139,12 @@ public function buildResponse() { } } + if (!$selected_item) { + if ($item_action == 'view') { + $selected_item = $this->getDefaultItem(); + } + } + switch ($item_action) { case 'view': case 'info': @@ -159,21 +168,33 @@ public function buildResponse() { } $navigation = $this->buildNavigation(); - $navigation->selectFilter('item.configure'); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); - switch ($this->getMenuType()) { - case 'personal': - $crumbs->addTextCrumb(pht('Personal')); - break; - case 'global': - $crumbs->addTextCrumb(pht('Global')); - break; + + // TODO: This stuff might need a little tweaking at some point, since it + // causes "Global" and "Personal" to show up in contexts where they don't + // make sense, notably Projects. + if ($item_action != 'view') { + $navigation->selectFilter('item.configure'); + switch ($this->getMenuType()) { + case 'personal': + $crumbs->addTextCrumb(pht('Personal')); + break; + case 'global': + $crumbs->addTextCrumb(pht('Global')); + break; + } } switch ($item_action) { case 'view': + $navigation->selectFilter($selected_item->getItemIdentifier()); + $content = $this->buildItemViewContent($selected_item); + $crumbs->addTextCrumb($selected_item->getDisplayName()); + if (!$content) { + return new Aphront404Response(); + } break; case 'configure': $content = $this->buildItemConfigureContent($item_list); @@ -225,6 +246,7 @@ public function buildResponse() { if ($this->getShowNavigation()) { $page->setNavigation($navigation); } + return $page; } @@ -269,13 +291,8 @@ public function buildNavigation() { if (count($items) == 1) { $item = head($items); if ($item->getKey() === null) { - $builtin_key = $menu_item->getBuiltinKey(); - $item_phid = $menu_item->getPHID(); - if ($builtin_key !== null) { - $item->setKey($builtin_key); - } else if ($item_phid !== null) { - $item->setKey($item_phid); - } + $item_identifier = $menu_item->getItemIdentifier(); + $item->setKey($item_identifier); } } @@ -326,6 +343,7 @@ private function loadItems() { foreach ($stored_items as $stored_item) { $impl = $stored_item->getMenuItem(); $impl->setViewer($viewer); + $impl->setEngine($this); } // Merge the stored items into the builtin items. If a builtin item has @@ -442,6 +460,7 @@ private function loadBuiltinProfileItems() { $item = clone $item; $item->setViewer($viewer); + $item->setEngine($this); $builtin ->setProfilePHID($object->getPHID()) @@ -546,6 +565,10 @@ private function buildItemReorderContent(array $items) { ->setURI($this->getConfigureURI()); } + private function buildItemViewContent( + PhabricatorProfileMenuItemConfiguration $item) { + return $item->newPageContent(); + } private function buildItemConfigureContent(array $items) { $viewer = $this->getViewer(); diff --git a/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php index c9a13bd4ab..002e5778d5 100644 --- a/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorDashboardProfileMenuItem.php @@ -21,6 +21,11 @@ public function canAddToObject($object) { return true; } + public function canMakeDefault( + PhabricatorProfileMenuItemConfiguration $config) { + return true; + } + public function attachDashboard($dashboard) { $this->dashboard = $dashboard; return $this; @@ -28,14 +33,39 @@ public function attachDashboard($dashboard) { public function getDashboard() { $dashboard = $this->dashboard; + if (!$dashboard) { return null; } else if ($dashboard->isArchived()) { return null; } + return $dashboard; } + public function newPageContent( + PhabricatorProfileMenuItemConfiguration $config) { + $viewer = $this->getViewer(); + + $dashboard_phid = $config->getMenuItemProperty('dashboardPHID'); + + // Reload the dashboard to attach panels, which we need for rendering. + $dashboard = id(new PhabricatorDashboardQuery()) + ->setViewer($viewer) + ->withPHIDs(array($dashboard_phid)) + ->needPanels(true) + ->executeOne(); + if (!$dashboard) { + return null; + } + + $engine = id(new PhabricatorDashboardRenderingEngine()) + ->setViewer($viewer) + ->setDashboard($dashboard); + + return $engine->renderDashboard(); + } + public function willBuildNavigationItems(array $items) { $viewer = $this->getViewer(); $dashboard_phids = array(); @@ -100,7 +130,7 @@ protected function newNavigationMenuItems( $icon = $dashboard->getIcon(); $name = $this->getDisplayName($config); - $href = $dashboard->getViewURI(); + $href = $this->getItemViewURI($config); $item = $this->newItem() ->setHref($href) diff --git a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php index 6f1342d4c0..061afc7fad 100644 --- a/src/applications/search/menuitem/PhabricatorProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorProfileMenuItem.php @@ -3,6 +3,7 @@ abstract class PhabricatorProfileMenuItem extends Phobject { private $viewer; + private $engine; final public function buildNavigationMenuItems( PhabricatorProfileMenuItemConfiguration $config) { @@ -55,6 +56,15 @@ public function getViewer() { return $this->viewer; } + public function setEngine(PhabricatorProfileMenuEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function getEngine() { + return $this->engine; + } + final public function getMenuItemKey() { return $this->getPhobjectClassConstant('MENUITEMKEY'); } @@ -70,6 +80,20 @@ protected function newItem() { return new PHUIListItemView(); } + public function newPageContent( + PhabricatorProfileMenuItemConfiguration $config) { + return null; + } + + public function getItemViewURI( + PhabricatorProfileMenuItemConfiguration $config) { + + $engine = $this->getEngine(); + $key = $config->getItemIdentifier(); + + return $engine->getItemURI("view/{$key}/"); + } + public function validateTransactions( PhabricatorProfileMenuItemConfiguration $config, $field_key, diff --git a/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php b/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php index 107fa63c72..f178cc49fe 100644 --- a/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php +++ b/src/applications/search/storage/PhabricatorProfileMenuItemConfiguration.php @@ -185,6 +185,19 @@ public function isDefault() { return ($this->getVisibility() === self::VISIBILITY_DEFAULT); } + public function getItemIdentifier() { + $id = $this->getID(); + + if ($id) { + return (int)$id; + } + + return $this->getBuiltinKey(); + } + + public function newPageContent() { + return $this->getMenuItem()->newPageContent($this); + } /* -( PhabricatorPolicyInterface )----------------------------------------- */ From 027de23970c071715d3e05544e6e59636c32a45d Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 4 Feb 2017 07:08:49 -0800 Subject: [PATCH 063/865] Fix a policy error for restricted applications in a profile menu Ref T12174. This could improperly raise a policy error. Instead, hide the menu item. Auditors: chad --- .../menuitem/PhabricatorApplicationProfileMenuItem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php index 92477522ba..aa42d56cfb 100644 --- a/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorApplicationProfileMenuItem.php @@ -60,12 +60,12 @@ private function getApplication( $viewer = $this->getViewer(); $phid = $config->getMenuItemProperty('application'); - $app = id(new PhabricatorApplicationQuery()) + $apps = id(new PhabricatorApplicationQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) - ->executeOne(); + ->execute(); - return $app; + return head($apps); } protected function newNavigationMenuItems( From eabb4072d03e0131532c1a55d627aee4eede05a4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 4 Feb 2017 09:41:03 -0800 Subject: [PATCH 064/865] (stable) Add more color/CSS to phui-icon-selector Summary: Fixes T12205. These got over-ridden at a global scale (correctly) and need to adjust local scopes better. Also make it more bluer. Test Plan: Go to Edit Dashboard, and pick a new icon for a Dashboard. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12205 Differential Revision: https://secure.phabricator.com/D17312 --- resources/celerity/map.php | 4 ++-- .../rsrc/css/phui/phui-icon-set-selector.css | 21 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 7bf3396ca8..750c4b0be4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -151,7 +151,7 @@ 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', 'rsrc/css/phui/phui-header-view.css' => '92935c02', 'rsrc/css/phui/phui-hovercard.css' => 'e904f5dc', - 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', + 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '09f46dd9', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', @@ -864,7 +864,7 @@ 'phui-header-view-css' => '92935c02', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'e904f5dc', - 'phui-icon-set-selector-css' => '1ab67aad', + 'phui-icon-set-selector-css' => '87db8fee', 'phui-icon-view-css' => '09f46dd9', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', diff --git a/webroot/rsrc/css/phui/phui-icon-set-selector.css b/webroot/rsrc/css/phui/phui-icon-set-selector.css index 09a0362246..c66c2a6e55 100644 --- a/webroot/rsrc/css/phui/phui-icon-set-selector.css +++ b/webroot/rsrc/css/phui/phui-icon-set-selector.css @@ -3,8 +3,10 @@ */ button.icon-button { - background: #f7f7f7; - border: 1px solid {$lightblueborder}; + background-color: #F7F7F9; + background-image: linear-gradient(to bottom, #ffffff, #f1f0f1); + border: 1px solid rgba({$alphablue},.2); + color: {$darkgreytext}; position: relative; width: 16px; height: 16px; @@ -15,6 +17,14 @@ button.icon-button { box-sizing: content-box; } +button.icon-button:hover { + border: 1px solid rgba({$alphablue},.5); +} + +button.icon-button .phui-icon-view { + color: {$darkbluetext}; +} + .icon-grid { text-align: center; } @@ -24,6 +34,9 @@ button.icon-button { } button.icon-button.selected { - background: {$bluebackground}; - border: 1px solid {$blueborder}; + border: 1px solid {$sky}; +} + +button.icon-button.selected .phui-icon-view { + color: {$sky}; } From c3bdcb4ca85487921909f0202aa760e8ed61404a Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 4 Feb 2017 16:06:57 -0800 Subject: [PATCH 065/865] (stable) Allow users who can edit a dashboard to remove invalid / restricted panels Summary: Ref T12207. Currently, to remove a panel from a dashboard, it must be a valid panel which you can see. Instead, only require that the panel PHID actually be listed somewhere in the dashboard's internal list of panels. This interacts with the "multiple instances of a panel" issue described in some more depth in T12207. In particular: - Currently, you can sort of add multiple copies of a panel to a dashboard, sometimes? Maybe? - This leads to great tragedy. This doesn't fix up the workflow with respect to multiple copies of a panel. We still remove by panel PHID (not by column/position or internal ID) so if a dashboard has multiple copies of the same panel for some reason, I think this workflow removes one of them arbitrarily (at best) or perhaps does something worse. I'm just treating this behavior as undefined for the moment. Test Plan: - Removed an invalid/hidden panel from a dashboard as a user with permission to edit that dashboard. - Tried to remove a made-up panel with a totally bogus PHID, got 404'd. - Viewed a dashboard with a restricted panel. - Put a hidden panel inside a tab panel, viewed it as a user who could not see it and a user who could. Reviewers: chad Reviewed By: chad Subscribers: swisspol Maniphest Tasks: T12207 Differential Revision: https://secure.phabricator.com/D17314 --- ...bricatorDashboardPanelRenderController.php | 1 + ...habricatorDashboardPanelViewController.php | 1 + ...bricatorDashboardRemovePanelController.php | 29 ++++++++--- ...abricatorDashboardPanelRenderingEngine.php | 52 +++++++++++++------ .../PhabricatorDashboardRenderingEngine.php | 1 + .../PhabricatorDashboardTabsPanelType.php | 1 + .../PhabricatorDashboardRemarkupRule.php | 1 + 7 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php index 0a0e7e30c0..8f25a0f9bb 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php @@ -34,6 +34,7 @@ public function handleRequest(AphrontRequest $request) { $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($panel) + ->setPanelPHID($panel->getPHID()) ->setParentPanelPHIDs($parent_phids) ->setHeaderMode($request->getStr('headerMode')) ->setDashboardID($request->getInt('dashboardID')) diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php index 9fd9e1840d..4b5f1b45be 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelViewController.php @@ -38,6 +38,7 @@ public function handleRequest(AphrontRequest $request) { $rendered_panel = id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($panel) + ->setPanelPHID($panel->getPHID()) ->setParentPanelPHIDs(array()) ->renderPanel(); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php b/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php index 2fa8e83a10..c30b06c596 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php @@ -20,12 +20,25 @@ public function handleRequest(AphrontRequest $request) { return new Aphront404Response(); } + // NOTE: If you can edit a dashboard, you can remove panels from it even + // if you don't have permission to see them or they aren't valid. We only + // require that the panel be present on the dashboard. + $v_panel = $request->getStr('panelPHID'); - $panel = id(new PhabricatorDashboardPanelQuery()) - ->setViewer($viewer) - ->withPHIDs(array($v_panel)) - ->executeOne(); - if (!$panel) { + + $panel_on_dashboard = false; + $layout = $dashboard->getLayoutConfigObject(); + $columns = $layout->getPanelLocations(); + foreach ($columns as $column) { + foreach ($column as $column_panel_phid) { + if ($column_panel_phid == $v_panel) { + $panel_on_dashboard = true; + break; + } + } + } + + if (!$panel_on_dashboard) { return new Aphront404Response(); } @@ -43,11 +56,11 @@ public function handleRequest(AphrontRequest $request) { ->setNewValue( array( '-' => array( - $panel->getPHID() => $panel->getPHID(), + $v_panel => $v_panel, ), )); - $layout_config->removePanel($panel->getPHID()); + $layout_config->removePanel($v_panel); $dashboard->setLayoutConfigFromObject($layout_config); $editor = id(new PhabricatorDashboardTransactionEditor()) @@ -67,7 +80,7 @@ public function handleRequest(AphrontRequest $request) { ->appendChild(pht('Are you sure you want to remove this panel?')); return $this->newDialog() - ->setTitle(pht('Remove Panel %s', $panel->getMonogram())) + ->setTitle(pht('Remove Panel')) ->appendChild($form->buildLayoutView()) ->addCancelButton($redirect_uri) ->addSubmitButton(pht('Remove Panel')); diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php index 286d749a18..fbd5aaa2bb 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php @@ -7,6 +7,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { const HEADER_MODE_EDIT = 'edit'; private $panel; + private $panelPHID; private $viewer; private $enableAsyncRendering; private $parentPanelPHIDs; @@ -66,6 +67,15 @@ public function getPanel() { return $this->panel; } + public function setPanelPHID($panel_phid) { + $this->panelPHID = $panel_phid; + return $this; + } + + public function getPanelPHID() { + return $this->panelPHID; + } + public function renderPanel() { $panel = $this->getPanel(); $viewer = $this->getViewer(); @@ -255,32 +265,40 @@ private function addPanelHeaderActions( PHUIHeaderView $header) { $panel = $this->getPanel(); - if (!$panel) { - return $header; - } - $dashboard_id = $this->getDashboardID(); - $edit_uri = id(new PhutilURI( - '/dashboard/panel/edit/'.$panel->getID().'/')); - if ($dashboard_id) { - $edit_uri->setQueryParam('dashboardID', $dashboard_id); + + if ($panel) { + $panel_id = $panel->getID(); + + $edit_uri = "/dashboard/panel/edit/{$panel_id}/"; + $edit_uri = new PhutilURI($edit_uri); + if ($dashboard_id) { + $edit_uri->setQueryParam('dashboardID', $dashboard_id); + } + + $action_edit = id(new PHUIIconView()) + ->setIcon('fa-pencil') + ->setWorkflow(true) + ->setHref((string)$edit_uri); + + $header->addActionItem($action_edit); } - $action_edit = id(new PHUIIconView()) - ->setIcon('fa-pencil') - ->setWorkflow(true) - ->setHref((string)$edit_uri); - $header->addActionItem($action_edit); if ($dashboard_id) { - $uri = id(new PhutilURI( - '/dashboard/removepanel/'.$dashboard_id.'/')) - ->setQueryParam('panelPHID', $panel->getPHID()); + $panel_phid = $this->getPanelPHID(); + + $remove_uri = "/dashboard/removepanel/{$dashboard_id}/"; + $remove_uri = id(new PhutilURI($remove_uri)) + ->setQueryParam('panelPHID', $panel_phid); + $action_remove = id(new PHUIIconView()) ->setIcon('fa-trash-o') - ->setHref((string)$uri) + ->setHref((string)$remove_uri) ->setWorkflow(true); + $header->addActionItem($action_remove); } + return $header; } diff --git a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php index f9af84973e..d1e1d3111c 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php @@ -55,6 +55,7 @@ public function renderDashboard() { ->setViewer($viewer) ->setDashboardID($dashboard->getID()) ->setEnableAsyncRendering(true) + ->setPanelPHID($panel_phid) ->setParentPanelPHIDs(array()) ->setHeaderMode($h_mode); diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php index 4a0dae02f1..3cb758a11a 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php @@ -91,6 +91,7 @@ public function renderPanelContent( ->setEnableAsyncRendering(true) ->setParentPanelPHIDs($parent_phids) ->setPanel($panel) + ->setPanelPHID($panel->getPHID()) ->setHeaderMode($no_headers) ->renderPanel(); } else { diff --git a/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php b/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php index 6b0a48dc01..d96852b10e 100644 --- a/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php +++ b/src/applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php @@ -32,6 +32,7 @@ protected function renderObjectEmbed( return id(new PhabricatorDashboardPanelRenderingEngine()) ->setViewer($viewer) ->setPanel($object) + ->setPanelPHID($object->getPHID()) ->setParentPanelPHIDs($parent_phids) ->renderPanel(); From 3e10f69570c0eb007f85917e16a249ea70d2dda8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 17 Feb 2017 17:44:47 -0800 Subject: [PATCH 066/865] (stable) Update Celerity map for the stable branch. --- resources/celerity/map.php | 122 ++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 750c4b0be4..d2953c72dd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,9 +7,9 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => 'e25569a9', + 'conpherence.pkg.css' => 'a520d619', 'conpherence.pkg.js' => '6249a1cf', - 'core.pkg.css' => '867a3ad4', + 'core.pkg.css' => '0d7ecd3b', 'core.pkg.js' => '1fa7c0c5', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '4815647b', @@ -26,15 +26,15 @@ 'rsrc/css/aphront/multi-column.css' => '84cc6640', 'rsrc/css/aphront/notification.css' => '3f6c89c9', 'rsrc/css/aphront/panel-view.css' => '8427b78d', - 'rsrc/css/aphront/phabricator-nav-view.css' => 'b29426e9', - 'rsrc/css/aphront/table-view.css' => '213a5981', + 'rsrc/css/aphront/phabricator-nav-view.css' => 'e58a4a30', + 'rsrc/css/aphront/table-view.css' => '6ca8e057', 'rsrc/css/aphront/tokenizer.css' => '9a8cb501', 'rsrc/css/aphront/tooltip.css' => '173b9431', 'rsrc/css/aphront/typeahead-browse.css' => '8904346a', - 'rsrc/css/aphront/typeahead.css' => 'd4f16145', + 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => '62c04564', + 'rsrc/css/application/base/main-menu-view.css' => '5294060f', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', 'rsrc/css/application/base/standard-page-view.css' => '894d8a25', @@ -47,15 +47,15 @@ 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => 'd82e130c', 'rsrc/css/application/conpherence/header-pane.css' => 'db93ebc6', - 'rsrc/css/application/conpherence/menu.css' => '4f51db5a', + 'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', 'rsrc/css/application/conpherence/message-pane.css' => 'b085d40d', 'rsrc/css/application/conpherence/notification.css' => '965db05b', - 'rsrc/css/application/conpherence/participant-pane.css' => 'ac1baaa8', + 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', 'rsrc/css/application/conpherence/transaction.css' => '85129c68', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '16c52f5c', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', - 'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127', + 'rsrc/css/application/dashboard/dashboard.css' => '0921c307', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => '6a9bdf9c', @@ -81,8 +81,8 @@ 'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => '1898e534', - 'rsrc/css/application/people/people-picture-menu-item.css' => '1ac65ef7', - 'rsrc/css/application/people/people-profile.css' => '2473d929', + 'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34', + 'rsrc/css/application/people/people-profile.css' => '4df76faf', 'rsrc/css/application/phame/phame.css' => '53fa6236', 'rsrc/css/application/pholio/pholio-edit.css' => '07676f51', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', @@ -97,21 +97,21 @@ 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96', 'rsrc/css/application/project/project-card-view.css' => 'f25746f5', - 'rsrc/css/application/project/project-view.css' => 'ceabdbaa', + 'rsrc/css/application/project/project-view.css' => '9f6ce0e1', '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', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', - 'rsrc/css/application/search/application-search-view.css' => '20ae9d85', + 'rsrc/css/application/search/application-search-view.css' => '66ee5d46', 'rsrc/css/application/search/search-results.css' => '64ad079a', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '50dd5fa6', + 'rsrc/css/core/core.css' => '9f4cb463', 'rsrc/css/core/remarkup.css' => '4a2de2bb', 'rsrc/css/core/syntax.css' => '769d3498', 'rsrc/css/core/z-index.css' => '5e72c4e0', - 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', + 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', @@ -119,27 +119,27 @@ 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-source-code-view.css' => '4383192f', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', - 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'eb5c774b', + 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', 'rsrc/css/phui/calendar/phui-calendar.css' => '477acfaa', 'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '19f9369b', 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'f12cbc9f', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bff632a4', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '5c383524', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', - 'rsrc/css/phui/phui-action-list.css' => '5679229f', + 'rsrc/css/phui/phui-action-list.css' => 'f980c059', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => '3baef8db', - 'rsrc/css/phui/phui-basic-nav-view.css' => '3d4b207b', + 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', 'rsrc/css/phui/phui-button.css' => '7eaff361', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', - 'rsrc/css/phui/phui-cms.css' => 'be43c8a8', + 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '48fbd65d', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', - 'rsrc/css/phui/phui-crumbs-view.css' => 'f82868f2', + 'rsrc/css/phui/phui-crumbs-view.css' => 'b743f73e', 'rsrc/css/phui/phui-curtain-view.css' => '947bf1a4', 'rsrc/css/phui/phui-document-pro.css' => 'f56738ed', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', @@ -149,7 +149,7 @@ 'rsrc/css/phui/phui-form-view.css' => 'adca31ce', 'rsrc/css/phui/phui-form.css' => '5815af7b', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => '92935c02', + 'rsrc/css/phui/phui-header-view.css' => 'fef6a54e', 'rsrc/css/phui/phui-hovercard.css' => 'e904f5dc', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '09f46dd9', @@ -170,7 +170,7 @@ 'rsrc/css/phui/phui-tag-view.css' => '84d65f26', 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', 'rsrc/css/phui/phui-two-column-view.css' => '8a1074c7', - 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'f0551a33', + 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', 'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92', 'rsrc/css/phui/workboards/phui-workpanel.css' => 'a3a63478', @@ -392,7 +392,7 @@ 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', 'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => 'edf8a145', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', - 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '019f36c4', + 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -433,7 +433,7 @@ 'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc', 'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => 'bee502c8', 'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => 'fbe497e7', - 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '3f5d6dbf', + 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => 'a6b98425', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c', 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5', @@ -552,11 +552,11 @@ 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '213a5981', + 'aphront-table-view-css' => '6ca8e057', 'aphront-tokenizer-control-css' => '9a8cb501', 'aphront-tooltip-css' => '173b9431', - 'aphront-typeahead-control-css' => 'd4f16145', - 'application-search-view-css' => '20ae9d85', + 'aphront-typeahead-control-css' => '8a84cc7d', + 'application-search-view-css' => '66ee5d46', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'changeset-view-manager' => 'a2828756', @@ -565,10 +565,10 @@ 'config-page-css' => 'c1d5121b', 'conpherence-durable-column-view' => 'd82e130c', 'conpherence-header-pane-css' => 'db93ebc6', - 'conpherence-menu-css' => '4f51db5a', + 'conpherence-menu-css' => '3d8e5c9c', 'conpherence-message-pane-css' => 'b085d40d', 'conpherence-notification-css' => '965db05b', - 'conpherence-participant-pane-css' => 'ac1baaa8', + 'conpherence-participant-pane-css' => '604a8b02', 'conpherence-thread-manager' => 'c8b5ee6f', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', @@ -583,7 +583,7 @@ 'diffusion-icons-css' => 'd678600a', 'diffusion-readme-css' => '297373eb', 'diffusion-source-css' => '750add59', - 'diviner-shared-css' => 'aa3656aa', + 'diviner-shared-css' => '896f1d43', 'font-aleo' => '8bdb2835', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', @@ -617,7 +617,7 @@ 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => 'f411b6ae', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', - 'javelin-behavior-dashboard-move-panels' => '019f36c4', + 'javelin-behavior-dashboard-move-panels' => '408bf173', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '4b3c4443', @@ -712,7 +712,7 @@ 'javelin-behavior-select-on-click' => '4e3e79a6', 'javelin-behavior-setup-check-https' => '491416b3', 'javelin-behavior-slowvote-embed' => '887ad43f', - 'javelin-behavior-stripe-payment-form' => '3f5d6dbf', + 'javelin-behavior-stripe-payment-form' => 'a6b98425', 'javelin-behavior-test-payment-form' => 'fc91ab6c', 'javelin-behavior-time-typeahead' => '522431f7', 'javelin-behavior-toggle-class' => '92b9ec77', @@ -778,15 +778,15 @@ 'owners-path-editor-css' => '2f00933b', 'paste-css' => '1898e534', 'path-typeahead' => 'f7fc67ec', - 'people-picture-menu-item-css' => '1ac65ef7', - 'people-profile-css' => '2473d929', - 'phabricator-action-list-view-css' => '5679229f', + 'people-picture-menu-item-css' => 'a06f7f34', + 'people-profile-css' => '4df76faf', + 'phabricator-action-list-view-css' => 'f980c059', 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '50dd5fa6', + 'phabricator-core-css' => '9f4cb463', 'phabricator-countdown-css' => '16c52f5c', - 'phabricator-dashboard-css' => 'bc6f2127', + 'phabricator-dashboard-css' => '0921c307', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -797,8 +797,8 @@ 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => '4a021c10', - 'phabricator-main-menu-view' => '62c04564', - 'phabricator-nav-view-css' => 'b29426e9', + 'phabricator-main-menu-view' => '5294060f', + 'phabricator-nav-view-css' => 'e58a4a30', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', 'phabricator-notification-menu-css' => '6a697e43', @@ -838,19 +838,19 @@ 'phriction-document-css' => '4282e4ad', 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => '3baef8db', - 'phui-basic-nav-view-css' => '3d4b207b', + 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', 'phui-button-css' => '7eaff361', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', - 'phui-calendar-list-css' => 'eb5c774b', + 'phui-calendar-list-css' => '576be600', 'phui-calendar-month-css' => '8e10e92c', 'phui-chart-css' => '6bf6f78e', - 'phui-cms-css' => 'be43c8a8', + 'phui-cms-css' => '504b4b23', 'phui-comment-form-css' => '48fbd65d', 'phui-comment-panel-css' => 'f50152ad', - 'phui-crumbs-view-css' => 'f82868f2', + 'phui-crumbs-view-css' => 'b743f73e', 'phui-curtain-view-css' => '947bf1a4', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', @@ -861,7 +861,7 @@ 'phui-form-css' => '5815af7b', 'phui-form-view-css' => 'adca31ce', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => '92935c02', + 'phui-header-view-css' => 'fef6a54e', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'e904f5dc', 'phui-icon-set-selector-css' => '87db8fee', @@ -878,7 +878,7 @@ 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => 'f12cbc9f', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => 'bff632a4', + 'phui-oi-list-view-css' => '5c383524', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', @@ -891,7 +891,7 @@ 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => 'bc523970', 'phui-two-column-view-css' => '8a1074c7', - 'phui-workboard-color-css' => 'f0551a33', + 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', 'phui-workcard-view-css' => 'cca5fa92', 'phui-workpanel-view-css' => 'a3a63478', @@ -906,7 +906,7 @@ 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => 'fbd45f96', 'project-card-view-css' => 'f25746f5', - 'project-view-css' => 'ceabdbaa', + 'project-view-css' => '9f6ce0e1', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', @@ -927,14 +927,6 @@ 'javelin-request', 'javelin-typeahead-source', ), - '019f36c4' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '01fca1f0' => array( 'javelin-behavior', 'javelin-workflow', @@ -1161,10 +1153,13 @@ 'javelin-workflow', 'javelin-stratcom', ), - '3f5d6dbf' => array( + '408bf173' => array( 'javelin-behavior', 'javelin-dom', - 'phortune-credit-card-form', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', ), '40a6a403' => array( 'javelin-install', @@ -1282,6 +1277,9 @@ 'javelin-vector', 'javelin-typeahead-static-source', ), + '5294060f' => array( + 'phui-theme-css', + ), '5359e785' => array( 'javelin-install', 'javelin-util', @@ -1367,9 +1365,6 @@ 'javelin-magical-init', 'javelin-util', ), - '62c04564' => array( - 'phui-theme-css', - ), '62dfea03' => array( 'javelin-install', 'javelin-util', @@ -1761,6 +1756,11 @@ 'phuix-icon-view', 'phabricator-busy', ), + 'a6b98425' => array( + 'javelin-behavior', + 'javelin-dom', + 'phortune-credit-card-form', + ), 'a6f7a73b' => array( 'javelin-behavior', 'javelin-stratcom', From 216f6be11ece53cb1daafc8fff636dbdb0d7ef3d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 28 Feb 2017 07:13:35 -0800 Subject: [PATCH 067/865] (stable) Add "--pool" and "--duration" flags to daemon CLI tools Summary: Ref T12331. These changes are intended to make it easier to debug T12331 since I'm having difficulty reproducing the issue locally. Test Plan: - Ran `bin/phd debug task --pool 4` and got an autoscaling pool. - Ran `bin/worker flood --duration 3` and got some 3-second-long tasks to execute with `bin/worker execute ...`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12331 Differential Revision: https://secure.phabricator.com/D17431 --- ...habricatorDaemonManagementDebugWorkflow.php | 7 +++++++ .../__tests__/PhabricatorTestWorker.php | 9 ++++++++- ...habricatorWorkerManagementFloodWorkflow.php | 18 ++++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php index 9a535af617..e861d238f2 100644 --- a/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php @@ -21,6 +21,12 @@ protected function didConstruct() { 'name' => 'argv', 'wildcard' => true, ), + array( + 'name' => 'pool', + 'param' => 'count', + 'help' => pht('Maximum pool size.'), + 'default' => 1, + ), array( 'name' => 'as-current-user', 'help' => pht( @@ -43,6 +49,7 @@ public function execute(PhutilArgumentParser $args) { $config = array( 'class' => array_shift($argv), 'label' => 'debug', + 'pool' => (int)$args->getArg('pool'), 'argv' => $argv, ); diff --git a/src/infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php b/src/infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php index 86e83acb82..f899ec1775 100644 --- a/src/infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php +++ b/src/infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php @@ -24,7 +24,14 @@ public function getWaitBeforeRetry(PhabricatorWorkerTask $task) { } protected function doWork() { - switch (idx($this->getTaskData(), 'doWork')) { + $data = $this->getTaskData(); + + $duration = idx($data, 'duration'); + if ($duration) { + usleep($duration * 1000000); + } + + switch (idx($data, 'doWork')) { case 'fail-temporary': throw new Exception(pht('Temporary failure!')); case 'fail-permanent': diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php index 9bf52cfb71..fb067ddaac 100644 --- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php +++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php @@ -11,12 +11,24 @@ protected function didConstruct() { pht( 'Flood the queue with test tasks. This command is intended for '. 'use when developing and debugging Phabricator.')) - ->setArguments(array()); + ->setArguments( + array( + array( + 'name' => 'duration', + 'param' => 'seconds', + 'help' => pht( + 'Queue tasks which require a specific amount of wall time to '. + 'complete. By default, tasks complete as quickly as possible.'), + 'default' => 0, + ), + )); } public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); + $duration = (float)$args->getArg('duration'); + $console->writeOut( "%s\n", pht('Adding many test tasks to worker queue. Use ^C to exit.')); @@ -25,7 +37,9 @@ public function execute(PhutilArgumentParser $args) { while (true) { PhabricatorWorker::scheduleTask( 'PhabricatorTestWorker', - array()); + array( + 'duration' => $duration, + )); if (($n++ % 100) === 0) { $console->writeOut('.'); From 6f879559707c57006a1a95d839e2e2e6c64a5ff6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 16 Mar 2017 09:06:22 -0700 Subject: [PATCH 068/865] (stable) Correct an issue where "View Raw File" in Differential generated a file with overbroad permissions Summary: Via HackerOne. When you view a raw file in Differential, we currently generate a permanent file with default permissions. This may be incorrect: default permissions may be broader than the diff's permissions. The other three methods of downloading/viewing raw files ("Download" in Diffusion and Differential, "View Raw" in Diffusion and Differential) already apply policies correctly and generate temporary files. However, this workflow was missed when other workflows were updated. Beyond updating the workflow, delete any files we've generated in the past. This wipes the slate clean on any security issues and frees up a little disk space. Test Plan: - Ran migration script, saw existing files get purged. - Did "View Raw File", got a new file. - Verified that the file was temporary and properly attached to the diff, with "NO ONE" permissions. - Double-checked that Diffusion already runs policy logic correctly and applies appropriate policies. - Double-checked that "Download Raw Diff" in Differential already runs policy logic correctly. - Double-chekced that "Download Raw Diff" in Diffusion already runs policy logic correctly. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17504 --- .../sql/autopatches/20170316.rawfiles.01.php | 53 +++++++++++++++++++ .../DifferentialChangesetViewController.php | 8 ++- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20170316.rawfiles.01.php diff --git a/resources/sql/autopatches/20170316.rawfiles.01.php b/resources/sql/autopatches/20170316.rawfiles.01.php new file mode 100644 index 0000000000..df2fa93c9a --- /dev/null +++ b/resources/sql/autopatches/20170316.rawfiles.01.php @@ -0,0 +1,53 @@ +establishConnection('w'); +$viewer = PhabricatorUser::getOmnipotentUser(); + +$iterator = new LiskRawMigrationIterator( + $conn, + $table->getTableName()); + +echo tsprintf( + "%s\n", + pht('Purging old raw changeset file caches...')); + +$keys = array( + 'raw:new:phid', + 'raw:old:phid', +); + +foreach ($iterator as $changeset) { + try { + $metadata = phutil_json_decode($changeset['metadata']); + + $phids = array(); + foreach ($keys as $key) { + if (isset($metadata[$key])) { + $phids[] = $metadata[$key]; + } + } + + foreach ($phids as $phid) { + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + if ($file) { + id(new PhabricatorDestructionEngine()) + ->destroyObject($file); + } + } + + // NOTE: We don't bother updating the changeset record itself: we already + // regenerate the cache properly if the stored value isn't valid. + + } catch (Exception $ex) { + // Just move on if we run into trouble. + } +} diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php index ff0da2f821..b5bfe7464d 100644 --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -350,13 +350,19 @@ private function buildRawFileResponse( $data = $changeset->makeOldFile(); } + $diff = $changeset->getDiff(); + $file = PhabricatorFile::newFromFileData( $data, array( - 'name' => $changeset->getFilename(), + 'name' => $changeset->getFilename(), 'mime-type' => 'text/plain', + 'ttl' => phutil_units('24 hours in seconds'), + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, )); + $file->attachToObject($diff->getPHID()); + $metadata[$key] = $file->getPHID(); $changeset->setMetadata($metadata); $changeset->save(); From 56dd1b297c3e5cdbb477acc7435d6aa5749f33f2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 18 Mar 2017 17:29:44 -0700 Subject: [PATCH 069/865] (stable) Don't use "--" to separate flags and arguments in "git ls-remote" Summary: Fixes T12416. See that task for discussion. Slightly older versions of `git` do not appear to support use of `--` to separate flags and arguments. Test Plan: - Ran `bin/repository update PHABX`. - In T12416, had a user with Git 2.1.4 confirm that `git ls-remote X` worked while `git ls-remote -- X` failed. - Read `git help ls-remote` to look for any kind of suspicious `--destroy-the-world` flags, didn't see any that made me uneasy. Reviewers: chad, avivey Reviewed By: avivey Maniphest Tasks: T12416 Differential Revision: https://secure.phabricator.com/D17508 --- .../repository/engine/PhabricatorRepositoryPullEngine.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 1219e678d5..b144ecfd96 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -392,8 +392,11 @@ private function installGitHook() { private function loadGitRemoteRefs(PhabricatorRepository $repository) { $remote_envelope = $repository->getRemoteURIEnvelope(); + // NOTE: "git ls-remote" does not support "--" until circa January 2016. + // See T12416. None of the flags to "ls-remote" appear dangerous, and + // other checks make it difficult to configure a suspicious remote URI. list($stdout) = $repository->execxRemoteCommand( - 'ls-remote -- %P', + 'ls-remote %P', $remote_envelope); $map = array(); From b2c029cbad5d966f7c4ce9aa90ca33881988f018 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 25 Mar 2017 04:14:32 -0700 Subject: [PATCH 070/865] (stable) Make the default Trigger hibernation 3 minutes instead of 5 seconds The `min()` vs `max()` fix in D17560 meant that the Trigger daemon only hibernates for 5 seconds, so we do a full GC sweep every 5 seconds. This ends up eating a fair amount of CPU for no real benefit. The GC cursors should move to persistent storage, but just bump this default up in the meantime. Auditors: chad --- src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php index 02ac55a160..9561f3d18a 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php @@ -275,7 +275,7 @@ private function executeTriggers() { * @return int Number of seconds to sleep for. */ private function getSleepDuration() { - $sleep = 5; + $sleep = phutil_units('3 minutes in seconds'); $next_triggers = id(new PhabricatorWorkerTriggerQuery()) ->setViewer($this->getViewer()) From 8f7983a5be3a56db5b79dc7c3a0eb470f1d7ca02 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 25 Mar 2017 05:01:32 -0700 Subject: [PATCH 071/865] (stable) Only hibernate the Taskmaster after 15 seconds of inactivity Under some workloads, the taskmaster may hibernate and launch more rapidly than it should. Require 15 seconds of inactivity before hibernating. Also hibernate for longer. Auditors: chad --- .../daemon/workers/PhabricatorTaskmasterDaemon.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php index 6cbbd8698e..57a69843a4 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php @@ -44,8 +44,11 @@ protected function run() { $sleep = 0; } else { - if ($this->shouldHibernate(60)) { - break; + if ($this->getIdleDuration() > 15) { + $hibernate_duration = phutil_units('3 minutes in seconds'); + if ($this->shouldHibernate($hibernate_duration)) { + break; + } } // When there's no work, sleep for one second. The pool will From 520d3b9392041893e10b43a133263c49b5d341aa Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 3 Apr 2017 08:35:29 -0700 Subject: [PATCH 072/865] (stable) Don't downgrade accepts on update (fix "sticky accept") Summary: Fixes T12496. Sticky accept was accidentally impacted by the "void" changes in D17566. Instead, don't always downgrade all accepts/rejects: on update, we only want to downgrade accepts. Test Plan: - With sticky accept off, updated an accepted revision: new state is "needs review". - With sticky accept on, updated an accepted revision: new state is "accepted" (sticky accept working correctly). - Did "reject" + "request review" to make sure that still works, worked fine. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12496 Differential Revision: https://secure.phabricator.com/D17605 --- .../editor/DifferentialTransactionEditor.php | 14 ++++++++++++-- .../DifferentialRevisionVoidTransaction.php | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index bf7faa2700..f24bc7abda 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -339,12 +339,22 @@ protected function expandTransaction( } } - if ($downgrade_accepts || $downgrade_rejects) { + $downgrade = array(); + if ($downgrade_accepts) { + $downgrade[] = DifferentialReviewerStatus::STATUS_ACCEPTED; + } + + if ($downgrade_accepts) { + $downgrade[] = DifferentialReviewerStatus::STATUS_REJECTED; + } + + if ($downgrade) { $void_type = DifferentialRevisionVoidTransaction::TRANSACTIONTYPE; + $results[] = id(new DifferentialTransaction()) ->setTransactionType($void_type) ->setIgnoreOnNoEffect(true) - ->setNewValue(true); + ->setNewValue($downgrade); } $is_commandeer = false; diff --git a/src/applications/differential/xaction/DifferentialRevisionVoidTransaction.php b/src/applications/differential/xaction/DifferentialRevisionVoidTransaction.php index ae684d94fe..5073a9c464 100644 --- a/src/applications/differential/xaction/DifferentialRevisionVoidTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionVoidTransaction.php @@ -28,7 +28,7 @@ public function generateNewValue($object, $value) { AND reviewerStatus IN (%Ls)', $table_name, $object->getPHID(), - $this->getVoidableStatuses()); + $value); return ipull($rows, 'reviewerPHID'); } @@ -47,11 +47,11 @@ public function applyExternalEffects($object, $value) { 'UPDATE %T SET voidedPHID = %s WHERE revisionPHID = %s AND voidedPHID IS NULL - AND reviewerStatus IN (%Ls)', + AND reviewerPHID IN (%Ls)', $table_name, $this->getActingAsPHID(), $object->getPHID(), - $this->getVoidableStatuses()); + $value); } public function shouldHide() { From 061375fde6525ea3e6eb701ddc570c5a422d7ffc Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 4 Apr 2017 18:33:59 -0700 Subject: [PATCH 073/865] (stable) Fix a copy/paste typo with sticky accept The root issue here is actually just that I cherry-picked stable locally but did not push it. However, this is a minor issue I also caught while double-checking things. Auditors: chad --- .../differential/editor/DifferentialTransactionEditor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index f24bc7abda..dd6666c653 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -344,7 +344,7 @@ protected function expandTransaction( $downgrade[] = DifferentialReviewerStatus::STATUS_ACCEPTED; } - if ($downgrade_accepts) { + if ($downgrade_rejects) { $downgrade[] = DifferentialReviewerStatus::STATUS_REJECTED; } From 944f7da486708db3f4f4491cf7c6bc3ab919fa60 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 10 Apr 2017 15:46:35 -0700 Subject: [PATCH 074/865] (stable) Correct two parameter strictness issues with file uploads Summary: Fixes T12531. Strictness fallout from adding typechecking in D17616. - `chunkedHash` is not a real parameter, so the new typechecking was unhappy about it. - `mime-type` no longer allows `null`. Test Plan: - Ran `arc upload --conduit-uri ... 12MB.zero` on a 12MB file full of zeroes. - Before patch: badness, failure, fallback to one-shot uploads. - After patch: success and glory. Reviewers: chad Subscribers: joshuaspence Maniphest Tasks: T12531 Differential Revision: https://secure.phabricator.com/D17651 --- .../conduit/FileUploadChunkConduitAPIMethod.php | 15 ++++++++++----- .../files/storage/PhabricatorFile.php | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php index 949bdde28a..b4d1a23a3f 100644 --- a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php +++ b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php @@ -61,15 +61,20 @@ protected function execute(ConduitAPIRequest $request) { $mime_type = 'application/octet-stream'; } + $params = array( + 'name' => $file->getMonogram().'.chunk-'.$chunk->getID(), + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + ); + + if ($mime_type !== null) { + $params['mime-type'] = 'application/octet-stream'; + } + // NOTE: These files have a view policy which prevents normal access. They // are only accessed through the storage engine. $chunk_data = PhabricatorFile::newFromFileData( $data, - array( - 'name' => $file->getMonogram().'.chunk-'.$chunk->getID(), - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - 'mime-type' => $mime_type, - )); + $params); $chunk->setDataFilePHID($chunk_data->getPHID())->save(); diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 4faecbd557..db15fb43e0 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -251,6 +251,11 @@ public static function newChunkedFile( $file->setMimeType('application/octet-stream'); $chunked_hash = idx($params, 'chunkedHash'); + + // Get rid of this parameter now; we aren't passing it any further down + // the stack. + unset($params['chunkedHash']); + if ($chunked_hash) { $file->setContentHash($chunked_hash); } else { From f624c8238123e2049c0893654df316505b67f2bd Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 11 Apr 2017 09:51:01 -0700 Subject: [PATCH 075/865] (stable) Fix an issue where rejecting reviewers weren't powerful enough Summary: Previously, "reject" and "reject older" were separate statuses. Now, they're both shades of "reject". Set the "older reject" flag properly when we find a non-current reject. Test Plan: - User A accepts a revision. - User B rejects it. - Author updates it. - Before patch: incorrectly transitions to "accepted" ("older" reject is ignored). - After patch: correctly transitions to "needs review". Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17653 --- .../differential/editor/DifferentialTransactionEditor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index dd6666c653..0086c101e2 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -672,6 +672,8 @@ protected function applyFinalEffects( $active_phid = $active_diff->getPHID(); if ($reviewer->isRejected($active_phid)) { $has_rejecting_reviewer = true; + } else { + $has_rejecting_older_reviewer = true; } break; case DifferentialReviewerStatus::STATUS_REJECTED_OLDER: From 167e58479cd5fa20dcf9d225cb6370956cb2eb4b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 11 Apr 2017 10:54:46 -0700 Subject: [PATCH 076/865] (stable) Remove 'isPartial' parameter with no effect Summary: Fixes T12536. Nothing reads this parameter; `PhabricatorFile::newChunkedFile` sets the `isPartial` flag automatically. Test Plan: Grepped for `isPartial`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12536 Differential Revision: https://secure.phabricator.com/D17654 --- .../files/uploadsource/PhabricatorFileUploadSource.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index b4578e9e56..0ef33a2521 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -137,10 +137,6 @@ private function writeChunkedFile() { $parameters = $this->getNewFileParameters(); - $parameters = array( - 'isPartial' => true, - ) + $parameters; - $data_length = $this->getDataLength(); if ($data_length !== null) { $length = $data_length; From 517372a1e0c48d0bc1961af06e132b8537776565 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 15 Apr 2017 06:08:00 -0700 Subject: [PATCH 077/865] (stable) Fix join and remove policy checks for Conpherence Summary: I think these got munged when I removed CAN_JOIN. - If you can view the room, you can join it. - ~~If you can view the room, you can add others to it.~~ This rule adjustment was removed, see discussion on the revision. - If you are a participant in the room, you can remove yourself. - If you can edit a room, you can remove anyone. Test Plan: Normal feature set: - Create a new room that only I can edit, viewable by all users. - Leave room (bye k thx) - Create another room, myself only - Join room from second account - See ability to only remove myself - Remove myself - Rejoin - Add third account - Log into first account - Boot off randos - Test joining by green button, message, and by + sign. Policy consistency: - As a user who can not edit the room, tried to add other members. Received policy exception. The `+` button is currently visible and enabled for all users (even users who have not joined the room) but this is pre-existing. Reviewers: chad Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17696 --- .../controller/ConpherenceViewController.php | 3 --- .../conpherence/editor/ConpherenceEditor.php | 24 +++++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 6b953b9de4..7a152a1e2e 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -147,9 +147,6 @@ private function renderFormContent() { $user = $this->getRequest()->getUser(); $participating = $conpherence->getParticipantIfExists($user->getPHID()); - if (!$participating && $user->isLoggedIn()) { - return null; - } $draft = PhabricatorDraft::newFromUserAndKey( $user, $conpherence->getPHID()); diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index e358700348..c53aab7655 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -341,13 +341,23 @@ protected function requireCapabilities( $add = array_keys(array_diff_key($new_map, $old_map)); $rem = array_keys(array_diff_key($old_map, $new_map)); - $actor_phid = $this->requireActor()->getPHID(); - - // You need CAN_EDIT to change participants other than yourself. - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); + $actor_phid = $this->getActingAsPHID(); + + $is_join = (($add === array($actor_phid)) && !$rem); + $is_leave = (($rem === array($actor_phid)) && !$add); + + if ($is_join) { + // Anyone can join a thread they can see. + } else if ($is_leave) { + // Anyone can leave a thread. + } else { + // You need CAN_EDIT to add or remove participants. For additional + // discussion, see D17696 and T4411. + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_EDIT); + } break; case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: From ab3c81039c2f32f082cb5eb62711db6f7a5df68f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Apr 2017 07:31:44 -0700 Subject: [PATCH 078/865] (stable) Fix a bad variable in global typehaead order/limit code Summary: Ref T12538. I missed this in D17695, which renamed the variable. The logic was also a little off since `jj` is an index, not a count. Test Plan: Typed `con` in global search, which hits "Con-pherence", "Con-duit" and "Con-fig", plus a bunch of other stuff. Got results after patch. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12538 Differential Revision: https://secure.phabricator.com/D17700 --- resources/celerity/map.php | 28 +++++++++---------- .../rsrc/js/core/behavior-search-typeahead.js | 5 ++-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d9eb3a42e0..fead6aa9dc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ 'conpherence.pkg.css' => '437d3b5a', 'conpherence.pkg.js' => '281b1a73', 'core.pkg.css' => 'b2ad82f4', - 'core.pkg.js' => 'bf3b5cdf', + 'core.pkg.js' => 'deabcef7', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', @@ -510,7 +510,7 @@ 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => '0f2a0820', + 'rsrc/js/core/behavior-search-typeahead.js' => 'eded9ee8', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -666,7 +666,7 @@ 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => '0ca788bd', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => '0f2a0820', + 'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8', 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -988,17 +988,6 @@ 'phuix-autocomplete', 'javelin-mask', ), - '0f2a0820' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -2155,6 +2144,17 @@ 'javelin-dom', 'phabricator-draggable-list', ), + 'eded9ee8' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index 864d45ff29..fc752a6079 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -94,7 +94,6 @@ JX.behavior('phabricator-search-typeahead', function(config) { // If we have more results than fit, limit each type of result to 3, so // we show 3 applications, then 3 users, etc. For jump items, we show only // one result. - var jj; var results = []; for (ii = 0; ii < type_order.length; ii++) { @@ -108,8 +107,8 @@ JX.behavior('phabricator-search-typeahead', function(config) { // - we have more items than will fit in the typeahead, and this // is the 4..Nth result of its type. - var skip = ((current_type == 'jump') && (jj > 1)) || - ((list.length > config.limit) && (type_count > 3)); + var skip = ((current_type == 'jump') && (jj >= 1)) || + ((list.length > config.limit) && (jj >= 3)); if (skip) { continue; } From 2d7a64783ca20f06e1a63163e893c366ccdb3a74 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Apr 2017 08:56:35 -0700 Subject: [PATCH 079/865] (stable) Stem fulltext tokens before filtering them for stopwords Summary: Fixes T12596. A query for a token (like "having") which stems to a stopword (like "have") currently survives filtering. Stem it first so it gets caught. Also, for InnoDB, a custom stopword table can be configured. If it is, read that instead of the default stopword list (I configured it locally, but the default list is reasonable so we never formally recommended installs configure it). Test Plan: Queried for words that stem to stopwords, saw them filtered: {F4915843} Queried for the original problem query and saw "having" caught with "have" in the stopword list: {F4915844} Fiddled with local InnoDB stopword table config and saw the stopword list get loaded correctly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12596 Differential Revision: https://secure.phabricator.com/D17728 --- .../PhabricatorMySQLFulltextStorageEngine.php | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php index 5bb9943e59..b22076cf92 100644 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -228,6 +228,13 @@ private function newFulltextSubquery( $fulltext_tokens[$key] = $fulltext_token; $value = $token->getValue(); + + // If the value is unquoted, we'll stem it in the query, so stem it + // here before performing filtering tests. See T12596. + if (!$token->isQuoted()) { + $value = $stemmer->stemToken($value); + } + if (phutil_utf8_strlen($value) < $min_length) { $fulltext_token->setIsShort(true); continue; @@ -479,16 +486,32 @@ private function newEngineLimits(AphrontDatabaseConnection $conn) { try { $result = queryfx_one( $conn, - 'SELECT @@innodb_ft_min_token_size innodb_max'); + 'SELECT @@innodb_ft_min_token_size innodb_max, + @@innodb_ft_server_stopword_table innodb_stopword_config'); } catch (AphrontQueryException $ex) { $result = null; } if ($result) { $min_len = $result['innodb_max']; + + $stopword_config = $result['innodb_stopword_config']; + if (preg_match('(/)', $stopword_config)) { + // If the setting is nonempty and contains a slash, query the + // table the user has configured. + $parts = explode('/', $stopword_config); + list($stopword_database, $stopword_table) = $parts; + } else { + // Otherwise, query the InnoDB default stopword table. + $stopword_database = 'INFORMATION_SCHEMA'; + $stopword_table = 'INNODB_FT_DEFAULT_STOPWORD'; + } + $stopwords = queryfx_all( $conn, - 'SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD'); + 'SELECT * FROM %T.%T', + $stopword_database, + $stopword_table); $stopwords = ipull($stopwords, 'value'); $stopwords = array_fuse($stopwords); From a8729747f8e2c591b99edb9911f91653e9d30297 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 21 Apr 2017 16:55:00 -0700 Subject: [PATCH 080/865] (stable) Trivial edit to make `master` merge cleanly. --- src/applications/conpherence/editor/ConpherenceEditor.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index c53aab7655..e8239821c6 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -358,7 +358,6 @@ protected function requireCapabilities( $object, PhabricatorPolicyCapability::CAN_EDIT); } - break; case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE: case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE: From 033c4ace0de67c38fdfdfbf1da593cfd49aef61b Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 21 Apr 2017 16:57:55 -0700 Subject: [PATCH 081/865] (stable) Rebuild Celerity map after merge. --- resources/celerity/map.php | 251 ++++++++++++++++++++----------------- 1 file changed, 133 insertions(+), 118 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index fead6aa9dc..7937f6556e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,20 +7,25 @@ */ return array( 'names' => array( - 'conpherence.pkg.css' => '437d3b5a', - 'conpherence.pkg.js' => '281b1a73', - 'core.pkg.css' => 'b2ad82f4', - 'core.pkg.js' => 'deabcef7', - 'darkconsole.pkg.js' => 'e7393ebb', + 'conpherence.pkg.css' => 'ff161f2d', + 'conpherence.pkg.js' => 'b5b51108', + 'core.pkg.css' => '005d943f', + 'core.pkg.js' => '47a69358', + 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '90b30783', 'differential.pkg.js' => 'ddfeb49b', - 'diffusion.pkg.css' => '91c5d3a6', + 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '5ab2753f', + 'rsrc/audio/basic/alert.mp3' => '98461568', + 'rsrc/audio/basic/bing.mp3' => 'ab8603a5', + 'rsrc/audio/basic/pock.mp3' => '0cc772f5', + 'rsrc/audio/basic/tap.mp3' => 'fc2fd796', + 'rsrc/audio/basic/ting.mp3' => '17660001', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', - 'rsrc/css/aphront/dark-console.css' => 'f54bf286', + 'rsrc/css/aphront/dark-console.css' => '53798a6d', 'rsrc/css/aphront/dialog-view.css' => '685c7e2d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => '84cc6640', @@ -45,12 +50,13 @@ 'rsrc/css/application/config/config-template.css' => '8f18fa41', 'rsrc/css/application/config/setup-issue.css' => 'f794cfc3', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', + 'rsrc/css/application/conpherence/color.css' => 'abb4c358', 'rsrc/css/application/conpherence/durable-column.css' => '89ea6bef', - 'rsrc/css/application/conpherence/header-pane.css' => '4082233d', - 'rsrc/css/application/conpherence/menu.css' => '3d8e5c9c', - 'rsrc/css/application/conpherence/message-pane.css' => 'd1fc13e1', + 'rsrc/css/application/conpherence/header-pane.css' => 'cb6f4e19', + 'rsrc/css/application/conpherence/menu.css' => '6953e7ec', + 'rsrc/css/application/conpherence/message-pane.css' => 'b0f55ecc', 'rsrc/css/application/conpherence/notification.css' => 'cef0a3fc', - 'rsrc/css/application/conpherence/participant-pane.css' => '604a8b02', + 'rsrc/css/application/conpherence/participant-pane.css' => '26a3ce56', 'rsrc/css/application/conpherence/transaction.css' => '85129c68', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => '16c52f5c', @@ -65,8 +71,8 @@ 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-icons.css' => 'd678600a', - 'rsrc/css/application/diffusion/diffusion-readme.css' => '297373eb', + 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', + 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', @@ -133,7 +139,7 @@ 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '96787bae', + 'rsrc/css/phui/phui-button.css' => '8d23596a', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -148,7 +154,7 @@ 'rsrc/css/phui/phui-form-view.css' => '6175808d', 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => '9cf828ce', + 'rsrc/css/phui/phui-header-view.css' => 'e082678d', 'rsrc/css/phui/phui-hovercard.css' => 'ae091fc5', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '12b387a1', @@ -158,11 +164,11 @@ 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', 'rsrc/css/phui/phui-list.css' => '12eb8ce6', - 'rsrc/css/phui/phui-object-box.css' => '8b289e3d', + 'rsrc/css/phui/phui-object-box.css' => '9cff003c', 'rsrc/css/phui/phui-pager.css' => '77d8a794', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-property-list-view.css' => '2dc7993f', - 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', + 'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863', 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', @@ -234,7 +240,7 @@ 'rsrc/externals/javelin/lib/DOM.js' => '805b806a', 'rsrc/externals/javelin/lib/History.js' => 'd4505101', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', - 'rsrc/externals/javelin/lib/Leader.js' => 'fea0eb47', + 'rsrc/externals/javelin/lib/Leader.js' => '7f243deb', 'rsrc/externals/javelin/lib/Mask.js' => '8a41885b', 'rsrc/externals/javelin/lib/Quicksand.js' => '6b8ef10b', 'rsrc/externals/javelin/lib/Request.js' => '94b750d2', @@ -245,7 +251,7 @@ 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', - 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', + 'rsrc/externals/javelin/lib/WebSocket.js' => '3ffe32d6', 'rsrc/externals/javelin/lib/Workflow.js' => '1e911d0f', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', @@ -361,19 +367,19 @@ 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', + 'rsrc/js/application/aphlict/Aphlict.js' => 'e1d4b11a', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '3c547a81', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'd5a2d665', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', 'rsrc/js/application/calendar/behavior-event-all-day.js' => 'b41537c9', 'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'c8b5ee6f', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '4d863052', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'aa3bd034', - 'rsrc/js/application/conpherence/behavior-menu.js' => '7524fcfa', + 'rsrc/js/application/conpherence/behavior-menu.js' => 'c9b99b77', 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', @@ -396,7 +402,7 @@ 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', - 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7', + 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', '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' => '49ae8328', @@ -446,7 +452,7 @@ 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '94c65b72', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', - 'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea', + 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', 'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '93d0c9e3', 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', @@ -482,7 +488,6 @@ 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', - 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', @@ -521,6 +526,9 @@ 'rsrc/js/core/behavior-user-menu.js' => '31420f77', 'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d', 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', + 'rsrc/js/core/darkconsole/DarkLog.js' => 'c8e1ffe3', + 'rsrc/js/core/darkconsole/DarkMessage.js' => 'c48cccdd', + 'rsrc/js/core/darkconsole/behavior-dark-console.js' => '17bb8539', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', @@ -536,7 +544,7 @@ 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', - 'aphront-dark-console-css' => 'f54bf286', + 'aphront-dark-console-css' => '53798a6d', 'aphront-dialog-view-css' => '685c7e2d', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => '84cc6640', @@ -552,13 +560,14 @@ 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-page-css' => 'c1d5121b', + 'conpherence-color-css' => 'abb4c358', 'conpherence-durable-column-view' => '89ea6bef', - 'conpherence-header-pane-css' => '4082233d', - 'conpherence-menu-css' => '3d8e5c9c', - 'conpherence-message-pane-css' => 'd1fc13e1', + 'conpherence-header-pane-css' => 'cb6f4e19', + 'conpherence-menu-css' => '6953e7ec', + 'conpherence-message-pane-css' => 'b0f55ecc', 'conpherence-notification-css' => 'cef0a3fc', - 'conpherence-participant-pane-css' => '604a8b02', - 'conpherence-thread-manager' => 'c8b5ee6f', + 'conpherence-participant-pane-css' => '26a3ce56', + 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => '41af6d25', @@ -569,8 +578,8 @@ 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-icons-css' => 'd678600a', - 'diffusion-readme-css' => '297373eb', + 'diffusion-icons-css' => 'a6a1e2ba', + 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', @@ -581,10 +590,10 @@ 'herald-rule-editor' => 'd6a7e717', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', - 'javelin-aphlict' => '5359e785', + 'javelin-aphlict' => 'e1d4b11a', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', - 'javelin-behavior-aphlict-listen' => 'fb20ac8d', + 'javelin-behavior-aphlict-listen' => '3c547a81', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -598,12 +607,12 @@ 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => '9a6dd75c', 'javelin-behavior-config-reorder-fields' => 'b6993408', - 'javelin-behavior-conpherence-menu' => '7524fcfa', + 'javelin-behavior-conpherence-menu' => 'c9b99b77', 'javelin-behavior-conpherence-participant-pane' => '8604caa8', 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', - 'javelin-behavior-dark-console' => 'f411b6ae', + 'javelin-behavior-dark-console' => '17bb8539', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '408bf173', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', @@ -670,7 +679,7 @@ 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', - 'javelin-behavior-phabricator-transaction-list' => '13c739ea', + 'javelin-behavior-phabricator-transaction-list' => '1f6794f6', 'javelin-behavior-phabricator-watch-anchor' => '9f36c42d', 'javelin-behavior-pholio-mock-edit' => 'bee502c8', 'javelin-behavior-pholio-mock-view' => 'fbe497e7', @@ -712,7 +721,7 @@ 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', - 'javelin-diffusion-locate-file-source' => 'b42eddc7', + 'javelin-diffusion-locate-file-source' => 'c93358e3', 'javelin-dom' => '805b806a', 'javelin-dynval' => 'f6555212', 'javelin-event' => '2ee659ce', @@ -720,7 +729,7 @@ 'javelin-history' => 'd4505101', 'javelin-install' => '05270951', 'javelin-json' => '69adf288', - 'javelin-leader' => 'fea0eb47', + 'javelin-leader' => '7f243deb', 'javelin-magical-init' => '3010e992', 'javelin-mask' => '8a41885b', 'javelin-quicksand' => '6b8ef10b', @@ -751,7 +760,7 @@ 'javelin-view-interpreter' => 'f829edb3', 'javelin-view-renderer' => '6c2b09a2', 'javelin-view-visitor' => 'efe49472', - 'javelin-websocket' => 'e292eaf4', + 'javelin-websocket' => '3ffe32d6', 'javelin-workboard-board' => '8935deef', 'javelin-workboard-card' => 'c587b80f', 'javelin-workboard-column' => '21df4ff5', @@ -774,6 +783,8 @@ 'phabricator-content-source-view-css' => '4b8b05d4', 'phabricator-core-css' => '9f4cb463', 'phabricator-countdown-css' => '16c52f5c', + 'phabricator-darklog' => 'c8e1ffe3', + 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -829,7 +840,7 @@ 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '96787bae', + 'phui-button-css' => '8d23596a', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -849,7 +860,7 @@ 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => '9cf828ce', + 'phui-header-view-css' => 'e082678d', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'ae091fc5', 'phui-icon-set-selector-css' => '87db8fee', @@ -861,7 +872,7 @@ 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', 'phui-list-view-css' => '12eb8ce6', - 'phui-object-box-css' => '8b289e3d', + 'phui-object-box-css' => '9cff003c', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => 'f12cbc9f', @@ -871,7 +882,7 @@ 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', 'phui-property-list-view-css' => '2dc7993f', - 'phui-remarkup-preview-css' => '1a8f2591', + 'phui-remarkup-preview-css' => '54a34863', 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', @@ -998,19 +1009,21 @@ 'javelin-dom', 'javelin-typeahead-normalizer', ), - '13c739ea' => array( + '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', - 'javelin-workflow', 'javelin-dom', - 'javelin-uri', - 'phabricator-textareautils', + 'javelin-history', ), - '1499a8cb' => array( + '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', + 'javelin-util', 'javelin-dom', - 'javelin-history', + 'javelin-request', + 'phabricator-keyboard-shortcut', + 'phabricator-darklog', + 'phabricator-darkmessage', ), '185bbd53' => array( 'javelin-install', @@ -1052,6 +1065,14 @@ 'javelin-uri', 'javelin-routable', ), + '1f6794f6' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'javelin-uri', + 'phabricator-textareautils', + ), '1fe2510c' => array( 'javelin-install', 'javelin-dom', @@ -1126,6 +1147,20 @@ 'javelin-dom', 'javelin-magical-init', ), + '3c547a81' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'javelin-stratcom', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', + 'javelin-router', + 'javelin-util', + 'javelin-leader', + 'javelin-sound', + 'phabricator-notification', + ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', @@ -1141,6 +1176,9 @@ 'javelin-workflow', 'javelin-stratcom', ), + '3ffe32d6' => array( + 'javelin-install', + ), '408bf173' => array( 'javelin-behavior', 'javelin-dom', @@ -1241,6 +1279,17 @@ 'javelin-uri', 'phabricator-notification', ), + '4d863052' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1280,13 +1329,6 @@ '5294060f' => array( 'phui-theme-css', ), - '5359e785' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1430,20 +1472,6 @@ 'javelin-vector', 'javelin-dom', ), - '7524fcfa' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1489,6 +1517,9 @@ 'javelin-behavior', 'javelin-history', ), + '7f243deb' => array( + 'javelin-install', + ), '8018ee50' => array( 'javelin-install', 'javelin-util', @@ -1835,12 +1866,6 @@ 'b3e7d692' => array( 'javelin-install', ), - 'b42eddc7' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-typeahead-preloaded-source', - 'javelin-util', - ), 'b59e1e96' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1947,17 +1972,6 @@ 'c7ccd872' => array( 'phui-fontkit-css', ), - 'c8b5ee6f' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), 'c90a04fc' => array( 'javelin-dom', 'javelin-dynval', @@ -1966,11 +1980,31 @@ 'javelin-install', 'javelin-util', ), + 'c93358e3' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-typeahead-preloaded-source', + 'javelin-util', + ), 'c989ade3' => array( 'javelin-install', 'javelin-util', 'javelin-stratcom', ), + 'c9b99b77' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), 'ca3f91eb' => array( 'javelin-behavior', 'javelin-dom', @@ -2092,14 +2126,18 @@ 'javelin-dom', 'phabricator-draggable-list', ), + 'e1d4b11a' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), 'e1ff79b1' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), - 'e292eaf4' => array( - 'javelin-install', - ), 'e2e0a072' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2173,14 +2211,6 @@ 'f12cbc9f' => array( 'phui-oi-list-view-css', ), - 'f411b6ae' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-util', - 'javelin-dom', - 'javelin-request', - 'phabricator-keyboard-shortcut', - ), 'f50152ad' => array( 'phui-timeline-view-css', ), @@ -2203,20 +2233,6 @@ 'javelin-install', 'javelin-dom', ), - 'fb20ac8d' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'javelin-leader', - 'javelin-sound', - 'phabricator-notification', - ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', @@ -2242,14 +2258,12 @@ 'javelin-view-visitor', 'javelin-util', ), - 'fea0eb47' => array( - 'javelin-install', - ), ), 'packages' => array( 'conpherence.pkg.css' => array( 'conpherence-durable-column-view', 'conpherence-menu-css', + 'conpherence-color-css', 'conpherence-message-pane-css', 'conpherence-notification-css', 'conpherence-transaction-css', @@ -2370,6 +2384,7 @@ 'javelin-behavior-toggle-class', 'javelin-behavior-lightbox-attachments', 'phabricator-busy', + 'javelin-sound', 'javelin-aphlict', 'phabricator-notification', 'javelin-behavior-aphlict-listen', From 72976909b1bfa0a3ddd05c3efc737dbb7275f321 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 22 Apr 2017 16:01:10 +0000 Subject: [PATCH 082/865] (stable) Fix variable name for theme_class Summary: Fixes T12622. This variable is mis-named. Test Plan: Visit a room I have not joined. Reviewers: epriestley, amckinley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12622 Differential Revision: https://secure.phabricator.com/D17767 --- src/applications/conpherence/storage/ConpherenceThread.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 7e4c3dc09f..0c70ef2cb5 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -204,7 +204,7 @@ public function getDisplayData(PhabricatorUser $viewer) { } $user_participation = $this->getParticipantIfExists($viewer->getPHID()); - $theme = ConpherenceRoomSettings::COLOR_LIGHT; + $theme_class = ConpherenceRoomSettings::COLOR_LIGHT; if ($user_participation) { $user_seen_count = $user_participation->getSeenMessageCount(); $participant = $this->getParticipant($viewer->getPHID()); From c65ef0401e20502eae73b2dbcd1b6c3a5bbc3a21 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 22 Apr 2017 09:21:40 -0700 Subject: [PATCH 083/865] (stable) Fix Conpherence theme variables for both logged-out and logged-in users Summary: Ref T12622. Test Plan: As a logged-out and logged-in user, loaded Conpherence threads. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12622 Differential Revision: https://secure.phabricator.com/D17768 --- src/applications/conpherence/storage/ConpherenceThread.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 0c70ef2cb5..bb355154a4 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -204,17 +204,18 @@ public function getDisplayData(PhabricatorUser $viewer) { } $user_participation = $this->getParticipantIfExists($viewer->getPHID()); - $theme_class = ConpherenceRoomSettings::COLOR_LIGHT; + $theme = ConpherenceRoomSettings::COLOR_LIGHT; if ($user_participation) { $user_seen_count = $user_participation->getSeenMessageCount(); $participant = $this->getParticipant($viewer->getPHID()); $settings = $participant->getSettings(); $theme = idx($settings, 'theme', $theme); - $theme_class = ConpherenceRoomSettings::getThemeClass($theme); } else { $user_seen_count = 0; } + $unread_count = $this->getMessageCount() - $user_seen_count; + $theme_class = ConpherenceRoomSettings::getThemeClass($theme); $title = $this->getTitle(); $topic = $this->getTopic(); From 46b50a5995cbdac49434aa2ced2b33155e748a3f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 26 Apr 2017 11:48:44 -0700 Subject: [PATCH 084/865] (stable) Reduce the impact of `bin/storage dump` Summary: Ref T12646. - Use "wb1" instead of "wb" to use level 1 gzip compression (faster, less compressy). Locally, this went about 2x faster and the output only grew 4% larger. - LinesOfALargeExecFuture does a lot of unnecessary string operations, and can boil down to a busy wait. The process is pretty saturated by I/O so this isn't the end of the world, but just use raw ExecFuture with FutureIterator so that we wait in `select()`. - Also, nice the process to +19 so we try to give other things CPU. Test Plan: - Ran `bin/storage dump --compress --output ...`. - Saw CPU time for my local database drop from ~240s to ~90s, with a 4% larger output. Most of this was adding the `1`, but the ExecFuture thing helped a little, too. - I'm not sure what a great way to test `nice` in a local environment is and it's system dependent anyway, but nothing got worse / blew up. - Used `gzcat | head` and `gzcat | tail` on the result to sanity-check that everything was preserved. Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12646 Differential Revision: https://secure.phabricator.com/D17795 --- ...abricatorStorageManagementDumpWorkflow.php | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index 7195e735c6..4dc5c64042 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -138,6 +138,13 @@ public function didExecute(PhutilArgumentParser $args) { $command = csprintf('mysqldump %Ls', $argv); } + // Decrease the CPU priority of this process so it doesn't contend with + // other more important things. + if (function_exists('proc_nice')) { + proc_nice(19); + } + + // If we aren't writing to a file, just passthru the command. if ($output_file === null) { return phutil_passthru('%C', $command); @@ -148,7 +155,7 @@ public function didExecute(PhutilArgumentParser $args) { // a full disk). See T6996 for discussion. if ($is_compress) { - $file = gzopen($output_file, 'wb'); + $file = gzopen($output_file, 'wb1'); } else { $file = fopen($output_file, 'wb'); } @@ -162,23 +169,35 @@ public function didExecute(PhutilArgumentParser $args) { $future = new ExecFuture('%C', $command); - $lines = new LinesOfALargeExecFuture($future); - try { - foreach ($lines as $line) { - $line = $line."\n"; - if ($is_compress) { - $ok = gzwrite($file, $line); - } else { - $ok = fwrite($file, $line); + $iterator = id(new FutureIterator(array($future))) + ->setUpdateInterval(0.100); + foreach ($iterator as $ready) { + list($stdout, $stderr) = $future->read(); + $future->discardBuffers(); + + if (strlen($stderr)) { + fwrite(STDERR, $stderr); } - if ($ok !== strlen($line)) { - throw new Exception( - pht( - 'Failed to write %d byte(s) to file "%s".', - new PhutilNumber(strlen($line)), - $output_file)); + if (strlen($stdout)) { + if ($is_compress) { + $ok = gzwrite($file, $stdout); + } else { + $ok = fwrite($file, $stdout); + } + + if ($ok !== strlen($stdout)) { + throw new Exception( + pht( + 'Failed to write %d byte(s) to file "%s".', + new PhutilNumber(strlen($stdout)), + $output_file)); + } + } + + if ($ready !== null) { + $ready->resolvex(); } } From 733ae2a2e94c7713bea4cc8c71e3bb2f18a34615 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 May 2017 17:00:02 -0700 Subject: [PATCH 085/865] Explicitly quote "From" name part when submitting mail to the Mailgun API Summary: We are submitting `epriestley (Evan Priestley) `, but should be submitting `"epriestley (Evan Priestley)" `. Add the missing quotes. Test Plan: Locally, this makes the API calls work against the Mailgun sandbox domain. Reviewers: chad, amckinley Reviewed By: chad, amckinley Differential Revision: https://secure.phabricator.com/D17831 --- .../adapter/PhabricatorMailImplementationMailgunAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php index 0890ac3004..cfe6491fe0 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php @@ -86,7 +86,7 @@ public function send() { $from = idx($this->params, 'from'); if (idx($this->params, 'from-name')) { - $params['from'] = "{$this->params['from-name']} <{$from}>"; + $params['from'] = "\"{$this->params['from-name']}\" <{$from}>"; } else { $params['from'] = $from; } From 22c3e49b5176f97a18d2c51a4baf223d2ca05add Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 5 May 2017 19:50:05 -0700 Subject: [PATCH 086/865] (stable) Fix file attach bug in Macro Summary: This was mis-tested by only using one account, which could always see the image. External transaction moved file attachment to the modular transaction for file and audio instead. Test Plan: Test adding audio and a macro on a pleb account, visit with normal account and see macro fine. Reviewers: epriestley, amckinley Reviewed By: amckinley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17836 --- .../editor/PhabricatorMacroEditEngine.php | 2 +- .../macro/editor/PhabricatorMacroEditor.php | 38 ------------------- .../PhabricatorMacroAudioTransaction.php | 28 ++++++++++++++ .../PhabricatorMacroFileTransaction.php | 28 ++++++++++++++ 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/src/applications/macro/editor/PhabricatorMacroEditEngine.php b/src/applications/macro/editor/PhabricatorMacroEditEngine.php index 3f472ff0ce..2cafea937f 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditEngine.php +++ b/src/applications/macro/editor/PhabricatorMacroEditEngine.php @@ -6,7 +6,7 @@ final class PhabricatorMacroEditEngine const ENGINECONST = 'macro.image'; public function getEngineName() { - return pht('Macro Imagea'); + return pht('Macro Image'); } public function getSummaryHeader() { diff --git a/src/applications/macro/editor/PhabricatorMacroEditor.php b/src/applications/macro/editor/PhabricatorMacroEditor.php index d9067c5367..5d28b78f5f 100644 --- a/src/applications/macro/editor/PhabricatorMacroEditor.php +++ b/src/applications/macro/editor/PhabricatorMacroEditor.php @@ -19,44 +19,6 @@ public function getCreateObjectTitleForFeed($author, $object) { return pht('%s created %s.', $author, $object); } - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case PhabricatorMacroFileTransaction::TRANSACTIONTYPE: - case PhabricatorMacroAudioTransaction::TRANSACTIONTYPE: - // When changing a macro's image or audio, attach the underlying files - // to the macro (and detach the old files). - $old = $xaction->getOldValue(); - $new = $xaction->getNewValue(); - $all = array(); - if ($old) { - $all[] = $old; - } - if ($new) { - $all[] = $new; - } - - $files = id(new PhabricatorFileQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($all) - ->execute(); - $files = mpull($files, null, 'getPHID'); - - $old_file = idx($files, $old); - if ($old_file) { - $old_file->detachFromObject($object->getPHID()); - } - - $new_file = idx($files, $new); - if ($new_file) { - $new_file->attachToObject($object->getPHID()); - } - break; - } - } - protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php index aacf9f4016..26dc64c1f3 100644 --- a/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroAudioTransaction.php @@ -13,6 +13,34 @@ public function applyInternalEffects($object, $value) { $object->setAudioPHID($value); } + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + public function getTitle() { $new = $this->getNewValue(); $old = $this->getOldValue(); diff --git a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php index 77c9f24dd9..35091cd585 100644 --- a/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php +++ b/src/applications/macro/xaction/PhabricatorMacroFileTransaction.php @@ -13,6 +13,34 @@ public function applyInternalEffects($object, $value) { $object->setFilePHID($value); } + public function applyExternalEffects($object, $value) { + $old = $this->generateOldValue($object); + $new = $value; + $all = array(); + if ($old) { + $all[] = $old; + } + if ($new) { + $all[] = $new; + } + + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getActor()) + ->withPHIDs($all) + ->execute(); + $files = mpull($files, null, 'getPHID'); + + $old_file = idx($files, $old); + if ($old_file) { + $old_file->detachFromObject($object->getPHID()); + } + + $new_file = idx($files, $new); + if ($new_file) { + $new_file->attachToObject($object->getPHID()); + } + } + public function getTitle() { return pht( '%s changed the image for this macro.', From b0df33e6ecd715ae67552ce57030581b158f5623 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 5 May 2017 19:55:29 -0700 Subject: [PATCH 087/865] (stable) Don't apply patches or mark patches applied with `bin/storage upgrade --dryrun` Summary: Fixes T12682. Test Plan: Ran `bin/storage upgrade --dryrun` repeatedly with un-applied patches, saw it not apply them and not mark them applied. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12682 Differential Revision: https://secure.phabricator.com/D17837 --- .../workflow/PhabricatorStorageManagementWorkflow.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index e915cedb8d..48d753781d 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -1090,7 +1090,9 @@ final private function doUpgradeSchemata( } $t_begin = microtime(true); - $api->applyPatch($patch); + if (!$is_dryrun) { + $api->applyPatch($patch); + } $t_end = microtime(true); $duration = ($t_end - $t_begin); @@ -1100,7 +1102,9 @@ final private function doUpgradeSchemata( // If we're explicitly reapplying this patch, we don't need to // mark it as applied. if (!isset($state_map[$ref_key][$key])) { - $api->markPatchApplied($key, ($t_end - $t_begin)); + if (!$is_dryrun) { + $api->markPatchApplied($key, ($t_end - $t_begin)); + } $applied_map[$ref_key][$key] = true; } } From bc6d25ffc0594ccf49ab7e4a7e88e4a257de1eb1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 14 May 2017 14:15:06 -0700 Subject: [PATCH 088/865] Move Board Manage actions up a level Summary: Moves "reorder columns" and "change background" up a level, redesigns "manage" page to be a little cleaner. Test Plan: Change colors, reorder columns, manage page, disable board, re-enable board. Reviewers: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D17879 --- ...icatorProjectBoardBackgroundController.php | 3 +- ...habricatorProjectBoardManageController.php | 103 +++++------------- ...abricatorProjectBoardReorderController.php | 4 +- .../PhabricatorProjectBoardViewController.php | 29 +++-- 4 files changed, 51 insertions(+), 88 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php index 3ba1f03a56..99260d1770 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardBackgroundController.php @@ -28,6 +28,7 @@ public function handleRequest(AphrontRequest $request) { $this->setProject($board); $id = $board->getID(); + $view_uri = $this->getApplicationURI("board/{$id}/"); $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); if ($request->isFormPost()) { @@ -47,7 +48,7 @@ public function handleRequest(AphrontRequest $request) { ->applyTransactions($board, $xactions); return id(new AphrontRedirectResponse()) - ->setURI($manage_uri); + ->setURI($view_uri); } $nav = $this->getProfileMenu(); diff --git a/src/applications/project/controller/PhabricatorProjectBoardManageController.php b/src/applications/project/controller/PhabricatorProjectBoardManageController.php index 75bff07106..aecb5aa42a 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardManageController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardManageController.php @@ -31,112 +31,58 @@ public function handleRequest(AphrontRequest $request) { $board_id = $board->getID(); $header = $this->buildHeaderView($board); - $actions = $this->buildActionView($board); - $properties = $this->buildPropertyView($board); - - $properties->setActionList($actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$board_id}/"); $crumbs->addTextCrumb(pht('Manage')); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $crumbs->setBorder(true); $nav = $this->getProfileMenu(); + $columns_list = $this->buildColumnsList($board, $columns); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($columns_list); $title = array( pht('Manage Workboard'), $board->getDisplayName(), ); - $columns_list = $this->buildColumnsList($board, $columns); - return $this->newPage() ->setTitle($title) ->setNavigation($nav) ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - $columns_list, - )); + ->appendChild($view); } private function buildHeaderView(PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); - - $header = id(new PHUIHeaderView()) - ->setUser($viewer) - ->setHeader(pht('Workboard: %s', $board->getDisplayName())); - - return $header; - } - - private function buildActionView(PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); - $id = $board->getID(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $board, PhabricatorPolicyCapability::CAN_EDIT); - $reorder_uri = $this->getApplicationURI("board/{$id}/reorder/"); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-exchange') - ->setName(pht('Reorder Columns')) - ->setHref($reorder_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - - $background_uri = $this->getApplicationURI("board/{$id}/background/"); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-paint-brush') - ->setName(pht('Change Background Color')) - ->setHref($background_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - + $id = $board->getID(); $disable_uri = $this->getApplicationURI("board/{$id}/disable/"); - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-ban') - ->setName(pht('Disable Board')) - ->setHref($disable_uri) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-ban') + ->setText(pht('Disable Board')) + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true); - return $actions; - } - - private function buildPropertyView( - PhabricatorProject $board) { - $viewer = $this->getRequest()->getUser(); - - $properties = id(new PHUIPropertyListView()) + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Workboard: %s', $board->getDisplayName())) ->setUser($viewer) - ->setObject($board); - - $background = $board->getDisplayWorkboardBackgroundColor(); - if ($background !== null) { - $map = PhabricatorProjectWorkboardBackgroundColor::getOptions(); - $map = ipull($map, 'name'); + ->setPolicyObject($board) + ->setProfileHeader(true) + ->addActionLink($button); - $name = idx($map, $background, $background); - $properties->addProperty(pht('Background Color'), $name); - } - - return $properties; + return $header; } private function buildColumnsList( @@ -165,6 +111,11 @@ private function buildColumnsList( if ($column->isHidden()) { $item->setDisabled(true); + $item->addAttribute(pht('Hidden')); + $item->setImageIcon('fa-columns grey'); + } else { + $item->addAttribute(pht('Visible')); + $item->setImageIcon('fa-columns'); } $view->addItem($item); diff --git a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php index 05f1dd2d43..fc348bb02f 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php @@ -23,13 +23,13 @@ public function handleRequest(AphrontRequest $request) { $this->setProject($project); $project_id = $project->getID(); - $manage_uri = $this->getApplicationURI("board/{$project_id}/manage/"); + $view_uri = $this->getApplicationURI("board/{$project_id}/"); $reorder_uri = $this->getApplicationURI("board/{$project_id}/reorder/"); if ($request->isFormPost()) { // User clicked "Done", make sure the page reloads to show the new // column order. - return id(new AphrontRedirectResponse())->setURI($manage_uri); + return id(new AphrontRedirectResponse())->setURI($view_uri); } $columns = id(new PhabricatorProjectColumnQuery()) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 3b117c747f..6a7083b10d 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -705,10 +705,21 @@ private function buildManageMenu( ->setDisabled(!$can_edit) ->setWorkflow(true); + $reorder_uri = $this->getApplicationURI("board/{$id}/reorder/"); $manage_items[] = id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setName(pht('Manage Board')) - ->setHref($manage_uri); + ->setIcon('fa-exchange') + ->setName(pht('Reorder Columns')) + ->setHref($reorder_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true); + + $background_uri = $this->getApplicationURI("board/{$id}/background/"); + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-paint-brush') + ->setName(pht('Change Background Color')) + ->setHref($background_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(false); if ($show_hidden) { $hidden_uri = $this->getURIWithState() @@ -727,6 +738,12 @@ private function buildManageMenu( ->setName($hidden_text) ->setHref($hidden_uri); + $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); + $manage_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-gear') + ->setName(pht('Manage Workboard')) + ->setHref($manage_uri); + $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( @@ -734,12 +751,6 @@ private function buildManageMenu( PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); - $manage_items[] = id(new PhabricatorActionView()) - ->setIcon('fa-list-ul') - ->setName(pht('Batch Edit Visible Tasks...')) - ->setHref($batch_edit_uri) - ->setDisabled(!$can_batch_edit); - $manage_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($manage_items as $item) { From 5fee24fa43119d531057af33415b30589373b601 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 May 2017 06:36:28 -0700 Subject: [PATCH 089/865] (stable) Add required needMembers/needWatchers calls to Project Profile/Subprojects tabs Summary: Fixes T12710. See that task for discussion. This is pretty ugly/redundant but not broken. (Feel free to reject this and pursue something else.) Test Plan: - For a project with active subprojects/milestones, viewed the project profile and subprojects tabs. - After patch: they're ugly, but no longer fatal. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12710 Differential Revision: https://secure.phabricator.com/D17882 --- .../controller/PhabricatorProjectProfileController.php | 2 ++ .../controller/PhabricatorProjectSubprojectsController.php | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 4c726f5884..60254ddcca 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -262,6 +262,8 @@ private function buildSubprojectList(PhabricatorProject $project) { ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) + ->needMembers(true) + ->needWatchers(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index 4a89bb4cde..a25a41fad0 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -31,6 +31,8 @@ public function handleRequest(AphrontRequest $request) { ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) + ->needMembers(true) + ->needWatchers(true) ->withIsMilestone(false) ->execute(); } else { @@ -42,6 +44,8 @@ public function handleRequest(AphrontRequest $request) { ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) + ->needMembers(true) + ->needWatchers(true) ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); From 40228c56a9b7039dea72629ac8c4c55f516230e3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 14 May 2017 17:32:24 -0700 Subject: [PATCH 090/865] (stable) Add Member/Watcher info to search results Summary: Fixes T12707 Adds additional information to search results for if user is a member or a watcher of a project. Also removed the icon colors, which I'll find a better way to denote in future. Test Plan: Join a project, watch a project, view results list in /projects/ Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12707 Differential Revision: https://secure.phabricator.com/D17880 --- .../query/PhabricatorProjectSearchEngine.php | 4 +++- .../project/view/PhabricatorProjectListView.php | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 75c2045f50..70931a8b00 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -13,7 +13,9 @@ public function getApplicationClassName() { public function newQuery() { return id(new PhabricatorProjectQuery()) - ->needImages(true); + ->needImages(true) + ->needMembers(true) + ->needWatchers(true); } protected function buildCustomSearchFields() { diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 3d6044f2b1..38c845167c 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -15,6 +15,7 @@ public function getProjects() { public function renderList() { $viewer = $this->getUser(); + $viewer_phid = $viewer->getPHID(); $projects = $this->getProjects(); $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); @@ -26,10 +27,8 @@ public function renderList() { $id = $project->getID(); $icon = $project->getDisplayIconIcon(); - $color = $project->getColor(); - $icon_icon = id(new PHUIIconView()) - ->setIcon("{$icon} {$color}"); + ->setIcon($icon); $icon_name = $project->getDisplayIconName(); @@ -49,6 +48,17 @@ public function renderList() { $item->setDisabled(true); } + $is_member = $project->isUserMember($viewer_phid); + $is_watcher = $project->isUserWatcher($viewer_phid); + + if ($is_member) { + $item->addIcon('fa-user', pht('Member')); + } + + if ($is_watcher) { + $item->addIcon('fa-eye', pht('Watching')); + } + $list->addItem($item); } From fcebaa5097f326123254c912522cd18c71e32460 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 15 May 2017 09:02:39 -0700 Subject: [PATCH 091/865] (stable) Restrict watching and member project display better Summary: Fixes T12713. We don't need to show watching and member info on other views other than ApplicationSearch (for now) so add a few methods to restrict the calls. Test Plan: Visit project search, profile, project home, project home with subprojects Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12713 Differential Revision: https://secure.phabricator.com/D17883 --- .../PhabricatorProjectProfileController.php | 2 -- ...habricatorProjectSubprojectsController.php | 4 --- .../query/PhabricatorProjectSearchEngine.php | 2 ++ .../view/PhabricatorProjectListView.php | 29 ++++++++++++++----- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index 60254ddcca..4c726f5884 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -262,8 +262,6 @@ private function buildSubprojectList(PhabricatorProject $project) { ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withStatuses( array( PhabricatorProjectStatus::STATUS_ACTIVE, diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index a25a41fad0..4a89bb4cde 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -31,8 +31,6 @@ public function handleRequest(AphrontRequest $request) { ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withIsMilestone(false) ->execute(); } else { @@ -44,8 +42,6 @@ public function handleRequest(AphrontRequest $request) { ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) ->needImages(true) - ->needMembers(true) - ->needWatchers(true) ->withIsMilestone(true) ->setOrderVector(array('milestoneNumber', 'id')) ->execute(); diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 70931a8b00..df13278812 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -229,6 +229,8 @@ protected function renderResultList( $list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($projects) + ->setShowWatching(true) + ->setShowMember(true) ->renderList(); return id(new PhabricatorApplicationSearchResultView()) diff --git a/src/applications/project/view/PhabricatorProjectListView.php b/src/applications/project/view/PhabricatorProjectListView.php index 38c845167c..994e78ce76 100644 --- a/src/applications/project/view/PhabricatorProjectListView.php +++ b/src/applications/project/view/PhabricatorProjectListView.php @@ -3,6 +3,8 @@ final class PhabricatorProjectListView extends AphrontView { private $projects; + private $showMember; + private $showWatching; public function setProjects(array $projects) { $this->projects = $projects; @@ -13,6 +15,16 @@ public function getProjects() { return $this->projects; } + public function setShowWatching($watching) { + $this->showWatching = $watching; + return $this; + } + + public function setShowMember($member) { + $this->showMember = $member; + return $this; + } + public function renderList() { $viewer = $this->getUser(); $viewer_phid = $viewer->getPHID(); @@ -48,15 +60,18 @@ public function renderList() { $item->setDisabled(true); } - $is_member = $project->isUserMember($viewer_phid); - $is_watcher = $project->isUserWatcher($viewer_phid); - - if ($is_member) { - $item->addIcon('fa-user', pht('Member')); + if ($this->showMember) { + $is_member = $project->isUserMember($viewer_phid); + if ($is_member) { + $item->addIcon('fa-user', pht('Member')); + } } - if ($is_watcher) { - $item->addIcon('fa-eye', pht('Watching')); + if ($this->showWatching) { + $is_watcher = $project->isUserWatcher($viewer_phid); + if ($is_watcher) { + $item->addIcon('fa-eye', pht('Watching')); + } } $list->addItem($item); From 73ed316c5d2e5bb4092940729827f7877306394e Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Mon, 15 May 2017 13:22:05 -0700 Subject: [PATCH 092/865] (stable) Fix breakage of Pholio Summary: Removal of `PholioMockEditor::applyCustomInternalTransaction()` in D17868 broke creation of new mocks in Pholio. Puts the empty method back until we finish migrating Pholio to modular transactions. Test Plan: Created some mocks, observed lack of unhandled exception. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D17889 --- src/applications/pholio/editor/PholioMockEditor.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index 6f5bf1df2b..322eaa7267 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -184,6 +184,12 @@ private function getImageForXaction( return null; } + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + return; + } + protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { From 1d89c1ed24aee9183e9e66305c93f595b723c71d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 19 May 2017 21:40:08 -0700 Subject: [PATCH 093/865] (stable) Add sound to logged out Conpherence Summary: Fixes T12735. Adds a sound if the user is logged out, skips checking a setting. Test Plan: set participants to null and verify sound plays, no exceptions1111 Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12735 Differential Revision: https://secure.phabricator.com/D17973 --- .../controller/ConpherenceUpdateController.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index a814c64452..a792a5a4d4 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -369,9 +369,17 @@ private function loadAndRenderUpdates( ->setViewer($user); $dropdown_query->execute(); - $sounds = $this->getSoundForParticipant($user, $participant); - $receive_sound = $sounds[ConpherenceRoomSettings::SOUND_RECEIVE]; - $mention_sound = $sounds[ConpherenceRoomSettings::SOUND_MENTION]; + $map = ConpherenceRoomSettings::getSoundMap(); + $default_receive = ConpherenceRoomSettings::DEFAULT_RECEIVE_SOUND; + $receive_sound = $map[$default_receive]['rsrc']; + $mention_sound = null; + + // Get the user's defaults if logged in + if ($participant) { + $sounds = $this->getSoundForParticipant($user, $participant); + $receive_sound = $sounds[ConpherenceRoomSettings::SOUND_RECEIVE]; + $mention_sound = $sounds[ConpherenceRoomSettings::SOUND_MENTION]; + } $content = array( 'non_update' => $non_update, From ab80d3ef0c6202c760e18ce14c2fa7b1b898ece1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:27:21 -0700 Subject: [PATCH 094/865] (stable) For the diff banner, detect the current changeset better Summary: Ref T12733. Currently, we detect the changeset which is in the middle of the screen as the current changeset. This doesn't always get us the most intuitive changeset, particularly after a navigation from the scroll objective list: when you jump to changeset "X", you'd tend to expect "X" to be shown in the header, but the //next// changeset may be shown if "X" is too short. Instead, select the changeset near the top of the screen (spanning an invisible line slightly below the banner). Test Plan: Scrolled and jumped through a document with long and short changesets, saw a more intuitive changeset selected by the banner. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17976 --- resources/celerity/map.php | 14 +++++++------- .../js/application/diff/DiffChangesetList.js | 16 ++++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e50d8bbd1a..019856296b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => 'e822b496', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4d7dd14e', - 'differential.pkg.js' => '68a4fa60', + 'differential.pkg.js' => '6d05ad4c', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,7 +391,7 @@ 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => '5c68c40c', + 'rsrc/js/application/diff/DiffChangesetList.js' => '541206ba', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', @@ -778,7 +778,7 @@ 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => '5c68c40c', + 'phabricator-diff-changeset-list' => '541206ba', 'phabricator-diff-inline' => '77e14b60', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1316,6 +1316,10 @@ '5294060f' => array( 'phui-theme-css', ), + '541206ba' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), '54774a28' => array( 'phui-inline-comment-view-css', ), @@ -1368,10 +1372,6 @@ 'javelin-stratcom', 'javelin-dom', ), - '5c68c40c' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), '5e2634b9' => array( 'javelin-behavior', 'javelin-aphlict', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index e256372852..b615ca79eb 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -1358,16 +1358,20 @@ JX.install('DiffChangesetList', { return null; } - var v = JX.Vector.getViewport(); + // We're going to find the changeset which spans an invisible line a + // little underneath the bottom of the banner. This makes the header + // tick over from "A.txt" to "B.txt" just as "A.txt" scrolls completely + // offscreen. + var detect_height = 64; + for (var ii = 0; ii < this._changesets.length; ii++) { var changeset = this._changesets[ii]; var c = changeset.getVectors(); - // If the changeset starts above the upper half of the screen... - if (c.pos.y < (s.y + (v.y / 2))) { - // ...and ends below the lower half of the screen, this is the - // current visible changeset. - if ((c.pos.y + c.dim.y) > (s.y + (v.y / 2))) { + // If the changeset starts above the line... + if (c.pos.y <= (s.y + detect_height)) { + // ...and ends below the line, this is the current visible changeset. + if ((c.pos.y + c.dim.y) >= (s.y + detect_height)) { return changeset; } } From 7ee20f573d4a5a751ec084adf780ef2df7cfa1ac Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 07:18:34 -0700 Subject: [PATCH 095/865] (stable) Show "objectives" UI only if prototypes are enabled Summary: See D17955. Test Plan: Loaded a revision, no longer saw annotations with prototypes off. Still saw annotations with prototypes on. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D17983 --- resources/celerity/map.php | 34 +++++++++---------- .../view/DifferentialChangesetListView.php | 4 +++ .../js/application/diff/DiffChangesetList.js | 10 +++++- .../differential/behavior-populate.js | 3 +- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 019856296b..5b6c54b2f1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => 'e822b496', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '4d7dd14e', - 'differential.pkg.js' => '6d05ad4c', + 'differential.pkg.js' => '0dfe037d', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,14 +391,14 @@ 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => '541206ba', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', + 'rsrc/js/application/differential/behavior-populate.js' => '1de8bf63', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -620,7 +620,7 @@ 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', - 'javelin-behavior-differential-populate' => '5e41c819', + 'javelin-behavior-differential-populate' => '1de8bf63', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -778,7 +778,7 @@ 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => '541206ba', + 'phabricator-diff-changeset-list' => 'a716ca27', 'phabricator-diff-inline' => '77e14b60', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1042,6 +1042,14 @@ 'javelin-workflow', 'phabricator-scroll-objective', ), + '1de8bf63' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1316,10 +1324,6 @@ '5294060f' => array( 'phui-theme-css', ), - '541206ba' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), '54774a28' => array( 'phui-inline-comment-view-css', ), @@ -1378,14 +1382,6 @@ 'phabricator-phtize', 'javelin-dom', ), - '5e41c819' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1730,6 +1726,10 @@ 'javelin-stratcom', 'javelin-dom', ), + 'a716ca27' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 4ac2612ecd..ea8a5e42f1 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -203,11 +203,15 @@ public function render() { $this->requireResource('aphront-tooltip-css'); + $show_objectives = + PhabricatorEnv::getEnvConfig('phabricator.show-prototypes'); + $this->initBehavior( 'differential-populate', array( 'changesetViewIDs' => $ids, 'inlineURI' => $this->inlineURI, + 'showObjectives' => $show_objectives, 'pht' => array( 'Open in Editor' => pht('Open in Editor'), 'Show All Context' => pht('Show All Context'), diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index b615ca79eb..8e35a92f80 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -120,6 +120,7 @@ JX.install('DiffChangesetList', { _rangeTarget: null, _bannerNode: null, + _showObjectives: false, sleep: function() { this._asleep = true; @@ -137,7 +138,9 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); - this._objectives.show(); + if (this._showObjectives) { + this._objectives.show(); + } if (this._initialized) { return; @@ -195,6 +198,11 @@ JX.install('DiffChangesetList', { this._installKey('q', label, this._onkeyhide); }, + setShowObjectives: function(show) { + this._showObjectives = show; + return this; + }, + isAsleep: function() { return this._asleep; }, diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js index e5b0d5039d..b1b5bd415c 100644 --- a/webroot/rsrc/js/application/differential/behavior-populate.js +++ b/webroot/rsrc/js/application/differential/behavior-populate.js @@ -60,7 +60,8 @@ JX.behavior('differential-populate', function(config, statics) { var changeset_list = new JX.DiffChangesetList() .setTranslations(JX.phtize(config.pht)) - .setInlineURI(config.inlineURI); + .setInlineURI(config.inlineURI) + .setShowObjectives(config.showObjectives); // Install and activate the current page. var page_id = JX.Quicksand.getCurrentPageID(); From 1495c80e62ea5f548749fe753f2c5445e6eb09ce Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:10:32 -0700 Subject: [PATCH 096/865] (stable) Hide the Differential scroll objective list on trackpad systems Summary: Ref T12733. In the longer run I'd like to just push this out from the edge, but that currently gets us into trouble since we start bumping into content. On my system, the trackpad scrollbar also expands in size when moused over, so the minimum number of pixels we need to push it out is approximatley 15px. This hits body content and the persistent chat. For now, just disable this element on trackpad systems. Test Plan: Disconnected all USB peripherals, quit and relaunched Safari, saw no objective list. Reconnected mouse, relaunched Safari, saw objective list. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17974 --- resources/celerity/map.php | 47 ++++++++++--------- .../differential/changeset-view.css | 5 ++ .../rsrc/externals/javelin/lib/Scrollbar.js | 8 ++-- .../application/diff/ScrollObjectiveList.js | 6 +++ 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5b6c54b2f1..bf2fa84a72 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '5ffe8b79', - 'core.pkg.js' => 'e822b496', + 'core.pkg.js' => '6b2da600', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '4d7dd14e', - 'differential.pkg.js' => '0dfe037d', + 'differential.pkg.css' => 'bf87589e', + 'differential.pkg.js' => 'ee4f14c5', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => '54774a28', + 'rsrc/css/application/differential/changeset-view.css' => 'fa476ec0', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -247,7 +247,7 @@ 'rsrc/externals/javelin/lib/Resource.js' => '44959b73', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', - 'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c', + 'rsrc/externals/javelin/lib/Scrollbar.js' => '9065f639', 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => 'c989ade3', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', @@ -394,7 +394,7 @@ 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', - 'rsrc/js/application/diff/ScrollObjectiveList.js' => '1ca4d9db', + 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -567,7 +567,7 @@ 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => '54774a28', + 'differential-changeset-view-css' => 'fa476ec0', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -732,7 +732,7 @@ 'javelin-resource' => '44959b73', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', - 'javelin-scrollbar' => '087e919c', + 'javelin-scrollbar' => '9065f639', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6ad39b6f', 'javelin-tokenizer' => '8d3bc1b2', @@ -800,7 +800,7 @@ 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', 'phabricator-scroll-objective' => '0eee7a00', - 'phabricator-scroll-objective-list' => '1ca4d9db', + 'phabricator-scroll-objective-list' => '085dd101', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -956,11 +956,14 @@ 'javelin-stratcom', 'javelin-util', ), - '087e919c' => array( - 'javelin-install', + '085dd101' => array( 'javelin-dom', + 'javelin-util', 'javelin-stratcom', - 'javelin-vector', + 'javelin-install', + 'javelin-workflow', + 'javelin-scrollbar', + 'phabricator-scroll-objective', ), '08f4ccc3' => array( 'phui-oi-list-view-css', @@ -1034,14 +1037,6 @@ 'javelin-request', 'javelin-uri', ), - '1ca4d9db' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'phabricator-scroll-objective', - ), '1de8bf63' => array( 'javelin-behavior', 'javelin-dom', @@ -1324,9 +1319,6 @@ '5294060f' => array( 'phui-theme-css', ), - '54774a28' => array( - 'phui-inline-comment-view-css', - ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1611,6 +1603,12 @@ 'javelin-dom', 'javelin-request', ), + '9065f639' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '92b9ec77' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2208,6 +2206,9 @@ 'javelin-install', 'javelin-dom', ), + 'fa476ec0' => array( + 'phui-inline-comment-view-css', + ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index eee0e167f3..6a2552fbd1 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -423,6 +423,11 @@ tr.differential-inline-loading { overflow: hidden; } +.scroll-objective-list.has-aesthetic-scrollbar { + /* For now, hide this element on systems with aesthetic scrollbars. */ + display: none; +} + .scroll-objective { display: block; position: absolute; diff --git a/webroot/rsrc/externals/javelin/lib/Scrollbar.js b/webroot/rsrc/externals/javelin/lib/Scrollbar.js index 7596939581..cd69eb4def 100644 --- a/webroot/rsrc/externals/javelin/lib/Scrollbar.js +++ b/webroot/rsrc/externals/javelin/lib/Scrollbar.js @@ -34,7 +34,7 @@ JX.install('Scrollbar', { // width. If it doesn't, we're already in an environment with an aesthetic // scrollbar (like Safari on OSX with no mouse connected, or an iPhone) // and we don't need to do anything. - if (JX.Scrollbar._getScrollbarControlWidth() === 0) { + if (JX.Scrollbar.getScrollbarControlWidth() === 0) { return; } @@ -104,7 +104,7 @@ JX.install('Scrollbar', { /** * Compute the width of the browser's scrollbar control, in pixels. */ - _getScrollbarControlWidth: function() { + getScrollbarControlWidth: function() { var self = JX.Scrollbar; if (self._controlWidth === null) { @@ -140,7 +140,7 @@ JX.install('Scrollbar', { // If this browser and OS don't render a real scrollbar control, we // need to leave a margin. Generally, this is OSX with no mouse attached. - if (self._getScrollbarControlWidth() === 0) { + if (self.getScrollbarControlWidth() === 0) { return 12; } @@ -357,7 +357,7 @@ JX.install('Scrollbar', { */ _resizeViewport: function() { var fdim = JX.Vector.getDim(this._frame); - fdim.x += JX.Scrollbar._getScrollbarControlWidth(); + fdim.x += JX.Scrollbar.getScrollbarControlWidth(); fdim.setDim(this._viewport); }, diff --git a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js index f5beb751e6..7aae1fb1b4 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js +++ b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js @@ -5,6 +5,7 @@ * javelin-stratcom * javelin-install * javelin-workflow + * javelin-scrollbar * phabricator-scroll-objective * @javelin */ @@ -81,6 +82,11 @@ JX.install('ScrollObjectiveList', { document.body.appendChild(node); + // If we're on OSX without a mouse or some other system with zero-width + // trackpad-style scrollbars, adjust the display appropriately. + var aesthetic = (JX.Scrollbar.getScrollbarControlWidth() === 0); + JX.DOM.alterClass(node, 'has-aesthetic-scrollbar', aesthetic); + var d = JX.Vector.getDocument(); var list_dimensions = JX.Vector.getDim(node); From 1bccdd69cb924a73b3178b38a601d45a1d3d8ded Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:19:30 -0700 Subject: [PATCH 097/865] (stable) Stop long filenames in objective list tooltips from being cut off Summary: Ref T12733. Currently, long filenames get cut off at 160px. Instead, don't cut them off. Test Plan: Before: {F4968401} After: {F4968402} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17975 --- resources/celerity/map.php | 38 +++++++++---------- .../js/application/diff/ScrollObjective.js | 1 + webroot/rsrc/js/core/ToolTip.js | 6 ++- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bf2fa84a72..bbd3c5e317 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '5ffe8b79', - 'core.pkg.js' => '6b2da600', + 'core.pkg.js' => 'a8eda64a', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => 'bf87589e', - 'differential.pkg.js' => 'ee4f14c5', + 'differential.pkg.js' => '24d1acf0', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -393,7 +393,7 @@ 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', - 'rsrc/js/application/diff/ScrollObjective.js' => '0eee7a00', + 'rsrc/js/application/diff/ScrollObjective.js' => '2e069f79', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -480,7 +480,7 @@ 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', - 'rsrc/js/core/ToolTip.js' => '8fadb715', + 'rsrc/js/core/ToolTip.js' => '74caa17f', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', @@ -799,7 +799,7 @@ 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '0eee7a00', + 'phabricator-scroll-objective' => '2e069f79', 'phabricator-scroll-objective-list' => '085dd101', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', @@ -808,7 +808,7 @@ 'phabricator-standard-page-view' => 'eb5b80c5', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', - 'phabricator-tooltip' => '8fadb715', + 'phabricator-tooltip' => '74caa17f', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => 'd19198c8', @@ -980,13 +980,6 @@ 'javelin-dom', 'javelin-router', ), - '0eee7a00' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -1120,6 +1113,13 @@ 'javelin-install', 'javelin-event', ), + '2e069f79' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + ), '2ee659ce' => array( 'javelin-install', ), @@ -1462,6 +1462,12 @@ 'javelin-vector', 'javelin-dom', ), + '74caa17f' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1587,12 +1593,6 @@ 'javelin-stratcom', 'javelin-install', ), - '8fadb715' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-vector', - ), '8ff5e24c' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js index 4e6fa55d6a..c9fd4bb67e 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjective.js +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -100,6 +100,7 @@ JX.install('ScrollObjective', { JX.Stratcom.addSigil(node, 'has-tooltip'); JX.Stratcom.getData(node).tip = tip; JX.Stratcom.getData(node).align = 'W'; + JX.Stratcom.getData(node).size = 'auto'; return this; }, diff --git a/webroot/rsrc/js/core/ToolTip.js b/webroot/rsrc/js/core/ToolTip.js index 6fef44ab55..64a2deb301 100644 --- a/webroot/rsrc/js/core/ToolTip.js +++ b/webroot/rsrc/js/core/ToolTip.js @@ -50,7 +50,11 @@ JX.install('Tooltip', { { className: 'jx-tooltip-container' }, node_inner); - node.style.maxWidth = scale + 'px'; + if (scale == 'auto') { + node.style.maxWidth = ''; + } else { + node.style.maxWidth = scale + 'px'; + } JX.Tooltip.hide(); self._node = node; From 429a0ce4e699b507165b7672bf4cdd42d65c5da6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 04:58:09 -0700 Subject: [PATCH 098/865] (stable) Make Differential objective markers show a brighter "editing" state Summary: Ref T12733. - While editing a comment, show a pink star ({icon star, color=pink}) with a tooltip. - Slight UI tweaks, including draft comments getting an indigo pencil ({icon pencil, color=indigo}). Test Plan: {F4968470} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17977 --- resources/celerity/map.php | 40 +++++++++---------- .../view/DifferentialChangesetListView.php | 2 + .../differential/changeset-view.css | 5 ++- .../rsrc/js/application/diff/DiffInline.js | 29 +++++++++++++- webroot/rsrc/js/core/ToolTip.js | 4 ++ 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bbd3c5e317..97b4f15a35 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '5ffe8b79', - 'core.pkg.js' => 'a8eda64a', + 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => 'bf87589e', - 'differential.pkg.js' => '24d1acf0', + 'differential.pkg.css' => '7d4cfa59', + 'differential.pkg.js' => 'f94e941c', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -64,7 +64,7 @@ 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'fa476ec0', + 'rsrc/css/application/differential/changeset-view.css' => 'acfd58f6', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -392,7 +392,7 @@ 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '77e14b60', + 'rsrc/js/application/diff/DiffInline.js' => 'fa07d36e', 'rsrc/js/application/diff/ScrollObjective.js' => '2e069f79', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -480,7 +480,7 @@ 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', - 'rsrc/js/core/ToolTip.js' => '74caa17f', + 'rsrc/js/core/ToolTip.js' => '358b8c04', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', @@ -567,7 +567,7 @@ 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'fa476ec0', + 'differential-changeset-view-css' => 'acfd58f6', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -779,7 +779,7 @@ 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '77e14b60', + 'phabricator-diff-inline' => 'fa07d36e', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -808,7 +808,7 @@ 'phabricator-standard-page-view' => 'eb5b80c5', 'phabricator-textareautils' => '320810c8', 'phabricator-title' => '485aaa6c', - 'phabricator-tooltip' => '74caa17f', + 'phabricator-tooltip' => '358b8c04', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => 'd19198c8', @@ -1137,6 +1137,12 @@ 'javelin-dom', 'javelin-workflow', ), + '358b8c04' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1462,12 +1468,6 @@ 'javelin-vector', 'javelin-dom', ), - '74caa17f' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-vector', - ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1480,9 +1480,6 @@ 'javelin-reactor', 'javelin-util', ), - '77e14b60' => array( - 'javelin-dom', - ), '782ab6e7' => array( 'javelin-behavior', 'javelin-dom', @@ -1788,6 +1785,9 @@ 'phuix-autocomplete', 'javelin-mask', ), + 'acfd58f6' => array( + 'phui-inline-comment-view-css', + ), 'ae95d984' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2206,8 +2206,8 @@ 'javelin-install', 'javelin-dom', ), - 'fa476ec0' => array( - 'phui-inline-comment-view-css', + 'fa07d36e' => array( + 'javelin-dom', ), 'fbe497e7' => array( 'javelin-behavior', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index ea8a5e42f1..880fcb872d 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -232,6 +232,8 @@ public function render() { 'Loading...' => pht('Loading...'), + 'Editing Comment' => pht('Editing Comment'), + 'Jump to next change.' => pht('Jump to next change.'), 'Jump to previous change.' => pht('Jump to previous change.'), 'Jump to next file.' => pht('Jump to next file.'), diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 6a2552fbd1..6a29e1269b 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -419,7 +419,7 @@ tr.differential-inline-loading { border-style: solid; border-color: rgba(255, 255, 255, 0.95); border-width: 1px 0 1px 1px; - box-shadow: -1px 0 2px rgba(255, 255, 255, 0.25); + box-shadow: -1px 0 2px rgba(255, 255, 255, 0.10); overflow: hidden; } @@ -433,7 +433,8 @@ tr.differential-inline-loading { position: absolute; box-sizing: border-box; cursor: pointer; - left: 8px; + text-align: middle; + left: 7px; } .scroll-objective .phui-icon-view { diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index e66fb544c5..32cf0ac5a1 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -32,6 +32,7 @@ JX.install('DiffInline', { _isDraft: null, _isFixed: null, + _isEditing: false, bindToRow: function(row) { this._row = row; @@ -176,6 +177,12 @@ JX.install('DiffInline', { return this._changeset; }, + setEditing: function(editing) { + this._isEditing = editing; + this.updateObjective(); + return this; + }, + _onobjective: function() { this.getChangeset().getChangesetList().selectInline(this); }, @@ -194,12 +201,20 @@ JX.install('DiffInline', { return; } + var pht = changeset.getChangesetList().getTranslations(); + var icon = 'fa-comment'; var color = 'bluegrey'; + var tooltip = null; - if (this._isDraft) { + if (this._isEditing) { + icon = 'fa-star'; + color = 'pink'; + tooltip = pht('Editing Comment'); + } else if (this._isDraft) { // This inline is an unsubmitted draft. icon = 'fa-pencil'; + color = 'indigo'; } else if (this._isFixed) { // This inline has been marked done. icon = 'fa-check'; @@ -212,6 +227,7 @@ JX.install('DiffInline', { objective .setIcon(icon) .setColor(color) + .setTooltip(tooltip) .show(); }, @@ -511,7 +527,12 @@ JX.install('DiffInline', { this._undoRow = this._drawRows(template, cursor, mode, text); }, + _drawContentRows: function(rows) { + return this._drawRows(rows, null, 'content'); + }, + _drawEditRows: function(rows) { + this.setEditing(true); return this._drawRows(rows, null, 'edit'); }, @@ -560,6 +581,8 @@ JX.install('DiffInline', { 'click', 'inline-edit-cancel', JX.bind(this, this._oncancel, row_meta))); + } else if (type == 'content') { + // No special listeners for these rows. } else { row_meta.listeners.push( JX.DOM.listen( @@ -645,6 +668,7 @@ JX.install('DiffInline', { } this._removeRow(row); + this.setEditing(false); this.setInvisible(false); @@ -670,6 +694,7 @@ JX.install('DiffInline', { this.setLoading(false); this.setInvisible(false); + this.setEditing(false); this._onupdate(response); }, @@ -677,7 +702,7 @@ JX.install('DiffInline', { _onupdate: function(response) { var new_row; if (response.markup) { - new_row = this._drawEditRows(JX.$H(response.markup).getNode()).node; + new_row = this._drawContentRows(JX.$H(response.markup).getNode()).node; } // TODO: Save the old row so the action it's undo-able if it was a diff --git a/webroot/rsrc/js/core/ToolTip.js b/webroot/rsrc/js/core/ToolTip.js index 64a2deb301..635c9466ad 100644 --- a/webroot/rsrc/js/core/ToolTip.js +++ b/webroot/rsrc/js/core/ToolTip.js @@ -21,6 +21,10 @@ JX.install('Tooltip', { return; } + if (content === null) { + return; + } + if (__DEV__) { switch (align) { case 'N': From 87a4940924bcf7e19b234cae2ad63f6ee1925b5d Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:26:51 -0700 Subject: [PATCH 099/865] (stable) Fix a diff objective issue where objectives could appear in the wrong place Summary: Ref T12733. When creating a new comment, the objective could appear to far up in the scrollbar because we were anchoring it to an invisible row. Instead, anchor to the "edit" row while editing. Test Plan: Created a new comment at the very top of a file, saw "File, Star" icons instead of "Star, File". Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17978 --- resources/celerity/map.php | 30 +++++++++---------- .../rsrc/js/application/diff/DiffInline.js | 16 ++++++++-- .../js/application/diff/ScrollObjective.js | 1 + 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 97b4f15a35..968862b535 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => 'f94e941c', + 'differential.pkg.js' => 'fc6a23eb', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,8 +392,8 @@ 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => 'fa07d36e', - 'rsrc/js/application/diff/ScrollObjective.js' => '2e069f79', + 'rsrc/js/application/diff/DiffInline.js' => '93cbb03f', + 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -779,7 +779,7 @@ 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => 'fa07d36e', + 'phabricator-diff-inline' => '93cbb03f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -799,7 +799,7 @@ 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '2e069f79', + 'phabricator-scroll-objective' => '9df4e4e2', 'phabricator-scroll-objective-list' => '085dd101', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', @@ -1113,13 +1113,6 @@ 'javelin-install', 'javelin-event', ), - '2e069f79' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '2ee659ce' => array( 'javelin-install', ), @@ -1611,6 +1604,9 @@ 'javelin-stratcom', 'javelin-dom', ), + '93cbb03f' => array( + 'javelin-dom', + ), '93d0c9e3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1675,6 +1671,13 @@ '9d9685d6' => array( 'phui-oi-list-view-css', ), + '9df4e4e2' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2206,9 +2209,6 @@ 'javelin-install', 'javelin-dom', ), - 'fa07d36e' => array( - 'javelin-dom', - ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 32cf0ac5a1..77a0da3ec7 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -74,9 +74,10 @@ JX.install('DiffInline', { this._changesetID = data.changesetID; - this.updateObjective(); this.setInvisible(false); + this.updateObjective(); + return this; }, @@ -166,9 +167,11 @@ JX.install('DiffInline', { this._changeset = changeset; var objectives = changeset.getChangesetList().getObjectives(); + + // Create this inline's objective, but don't show it yet. this._objective = objectives.newObjective() - .setCallback(JX.bind(this, this._onobjective)); - this.updateObjective(); + .setCallback(JX.bind(this, this._onobjective)) + .hide(); return this; }, @@ -206,11 +209,17 @@ JX.install('DiffInline', { var icon = 'fa-comment'; var color = 'bluegrey'; var tooltip = null; + var anchor = this._row; if (this._isEditing) { icon = 'fa-star'; color = 'pink'; tooltip = pht('Editing Comment'); + + // If we're editing, anchor to the row with the editor instead of the + // actual comment row (which is invisible and can have a misleading + // position). + anchor = this._row.nextSibling; } else if (this._isDraft) { // This inline is an unsubmitted draft. icon = 'fa-pencil'; @@ -225,6 +234,7 @@ JX.install('DiffInline', { } objective + .setAnchor(anchor) .setIcon(icon) .setColor(color) .setTooltip(tooltip) diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js index c9fd4bb67e..b8fb169914 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjective.js +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -111,6 +111,7 @@ JX.install('ScrollObjective', { hide: function() { this._visible = false; + return this; }, isVisible: function() { From aabb791c174bc5f16f2cc0c783aa7fab8e3e3bc5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:33:59 -0700 Subject: [PATCH 100/865] (stable) When a user cancels a new inline, clear it from the objective list Summary: Ref T12733. Currently, creating a new inline and then canceling it leaves a marker in the objective list. Instead, remove the marker. Test Plan: - Created an empty inline, cancelled. Created a non-empty inline, cancelled. No objective marker in either case. - Created a new normal inline, objective marker. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17979 --- resources/celerity/map.php | 12 ++++++------ webroot/rsrc/js/application/diff/DiffInline.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 968862b535..402ae9013e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => 'fc6a23eb', + 'differential.pkg.js' => 'd7e3edd5', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,7 +392,7 @@ 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '93cbb03f', + 'rsrc/js/application/diff/DiffInline.js' => 'ca0fafde', 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -779,7 +779,7 @@ 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '93cbb03f', + 'phabricator-diff-inline' => 'ca0fafde', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1604,9 +1604,6 @@ 'javelin-stratcom', 'javelin-dom', ), - '93cbb03f' => array( - 'javelin-dom', - ), '93d0c9e3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1979,6 +1976,9 @@ 'phabricator-shaped-request', 'conpherence-thread-manager', ), + 'ca0fafde' => array( + 'javelin-dom', + ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 77a0da3ec7..e7c18ef6ad 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -33,6 +33,7 @@ JX.install('DiffInline', { _isDraft: null, _isFixed: null, _isEditing: false, + _isNew: false, bindToRow: function(row) { this._row = row; @@ -73,6 +74,7 @@ JX.install('DiffInline', { this._isGhost = data.isGhost; this._changesetID = data.changesetID; + this._isNew = false; this.setInvisible(false); @@ -87,6 +89,7 @@ JX.install('DiffInline', { this._length = parseInt(data.length, 10); this._isNewFile = data.isNewFile; this._changesetID = data.changesetID; + this._isNew = true; // Insert the comment after any other comments which already appear on // the same row. @@ -110,6 +113,7 @@ JX.install('DiffInline', { this._length = inline._length; this._isNewFile = inline._isNewFile; this._changesetID = inline._changesetID; + this._isNew = true; this._replyToCommentPHID = inline._phid; @@ -198,6 +202,13 @@ JX.install('DiffInline', { return; } + // If this is a new comment which we aren't editing, don't show anything: + // the use started a comment or reply, then cancelled it. + if (this._isNew && !this._isEditing) { + objective.hide(); + return; + } + var changeset = this.getChangeset(); if (!changeset.isVisible()) { objective.hide(); From 253f7b0bb9bdeb54281a2ab4b2655a1ec9bcf0cf Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:39:07 -0700 Subject: [PATCH 101/865] (stable) Show a snippet when hovering inlines in the objective list Summary: Ref T12733. Shows a comment snippet when hovering inlines in the objective list. Test Plan: {F4968490} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17980 --- resources/celerity/map.php | 12 ++++++------ .../diff/view/PHUIDiffInlineCommentDetailView.php | 9 +++++---- webroot/rsrc/js/application/diff/DiffInline.js | 4 +++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 402ae9013e..46443e7ecc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => 'd7e3edd5', + 'differential.pkg.js' => '06cddcc0', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,7 +392,7 @@ 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => 'ca0fafde', + 'rsrc/js/application/diff/DiffInline.js' => '4478f8ac', 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -779,7 +779,7 @@ 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => 'ca0fafde', + 'phabricator-diff-inline' => '4478f8ac', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -1203,6 +1203,9 @@ 'javelin-workflow', 'javelin-workboard-controller', ), + '4478f8ac' => array( + 'javelin-dom', + ), '44959b73' => array( 'javelin-util', 'javelin-uri', @@ -1976,9 +1979,6 @@ 'phabricator-shaped-request', 'conpherence-thread-manager', ), - 'ca0fafde' => array( - 'javelin-dom', - ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 9fc1e1bded..821834431a 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -421,6 +421,11 @@ public function render() { $actions, )); + $snippet = id(new PhutilUTF8StringTruncator()) + ->setMaximumGlyphs(96) + ->truncateString($inline->getContent()); + $metadata['snippet'] = pht('%s: %s', $author, $snippet); + $markup = javelin_tag( 'div', array( @@ -444,10 +449,6 @@ public function render() { phutil_tag_div('phabricator-remarkup', $content)), )); - $snippet = id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(96) - ->truncateString($inline->getContent()); - $summary = phutil_tag( 'div', array( diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index e7c18ef6ad..1f0d503b9f 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -22,6 +22,7 @@ JX.install('DiffInline', { _undoRow: null, _replyToCommentPHID: null, _originalText: null, + _snippet: null, _isDeleted: false, _isInvisible: false, @@ -75,6 +76,7 @@ JX.install('DiffInline', { this._changesetID = data.changesetID; this._isNew = false; + this._snippet = data.snippet; this.setInvisible(false); @@ -219,7 +221,7 @@ JX.install('DiffInline', { var icon = 'fa-comment'; var color = 'bluegrey'; - var tooltip = null; + var tooltip = this._snippet; var anchor = this._row; if (this._isEditing) { From e6843b1c00f383dc5abae27adc7c1412866a9159 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:48:38 -0700 Subject: [PATCH 102/865] (stable) Show "reply" inlines as replies in the objective list Summary: Ref T12733. Show a "reply" icon for replies, and make them stack directly under their parent. Test Plan: {F4968500} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17981 --- resources/celerity/map.php | 52 +++++++++---------- .../rsrc/js/application/diff/DiffInline.js | 5 ++ .../js/application/diff/ScrollObjective.js | 18 +++++++ .../application/diff/ScrollObjectiveList.js | 9 +++- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 46443e7ecc..1d430926a6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => '06cddcc0', + 'differential.pkg.js' => '886eadff', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -392,9 +392,9 @@ 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '4478f8ac', - 'rsrc/js/application/diff/ScrollObjective.js' => '9df4e4e2', - 'rsrc/js/application/diff/ScrollObjectiveList.js' => '085dd101', + 'rsrc/js/application/diff/DiffInline.js' => '1478c3b2', + 'rsrc/js/application/diff/ScrollObjective.js' => '7e8877e7', + 'rsrc/js/application/diff/ScrollObjectiveList.js' => '6120e99a', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', @@ -779,7 +779,7 @@ 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '4478f8ac', + 'phabricator-diff-inline' => '1478c3b2', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -799,8 +799,8 @@ 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '9df4e4e2', - 'phabricator-scroll-objective-list' => '085dd101', + 'phabricator-scroll-objective' => '7e8877e7', + 'phabricator-scroll-objective-list' => '6120e99a', 'phabricator-search-results-css' => 'f87d23ad', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -956,15 +956,6 @@ 'javelin-stratcom', 'javelin-util', ), - '085dd101' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-scrollbar', - 'phabricator-scroll-objective', - ), '08f4ccc3' => array( 'phui-oi-list-view-css', ), @@ -990,6 +981,9 @@ 'javelin-dom', 'javelin-typeahead-normalizer', ), + '1478c3b2' => array( + 'javelin-dom', + ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1203,9 +1197,6 @@ 'javelin-workflow', 'javelin-workboard-controller', ), - '4478f8ac' => array( - 'javelin-dom', - ), '44959b73' => array( 'javelin-util', 'javelin-uri', @@ -1393,6 +1384,15 @@ 'javelin-stratcom', 'javelin-dom', ), + '6120e99a' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-scrollbar', + 'phabricator-scroll-objective', + ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', @@ -1501,6 +1501,13 @@ '7e41274a' => array( 'javelin-install', ), + '7e8877e7' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + ), '7ebaeed3' => array( 'herald-rule-editor', 'javelin-behavior', @@ -1671,13 +1678,6 @@ '9d9685d6' => array( 'phui-oi-list-view-css', ), - '9df4e4e2' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index 1f0d503b9f..b0a8202e54 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -223,6 +223,7 @@ JX.install('DiffInline', { var color = 'bluegrey'; var tooltip = this._snippet; var anchor = this._row; + var should_stack = false; if (this._isEditing) { icon = 'fa-star'; @@ -244,6 +245,9 @@ JX.install('DiffInline', { } else if (this._isGhost) { icon = 'fa-comment-o'; color = 'grey'; + } else if (this._replyToCommentPHID) { + icon = 'fa-reply'; + should_stack = true; } objective @@ -251,6 +255,7 @@ JX.install('DiffInline', { .setIcon(icon) .setColor(color) .setTooltip(tooltip) + .setShouldStack(should_stack) .show(); }, diff --git a/webroot/rsrc/js/application/diff/ScrollObjective.js b/webroot/rsrc/js/application/diff/ScrollObjective.js index b8fb169914..1e596bd816 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjective.js +++ b/webroot/rsrc/js/application/diff/ScrollObjective.js @@ -26,6 +26,7 @@ JX.install('ScrollObjective', { _visible: false, _callback: false, + _stack: false, getNode: function() { if (!this._node) { @@ -104,6 +105,23 @@ JX.install('ScrollObjective', { return this; }, + + /** + * Should this objective always stack immediately under the previous + * objective? + * + * This allows related objectives (like "comment, reply, reply") to be + * rendered in a tight sequence. + */ + setShouldStack: function(stack) { + this._stack = stack; + return this; + }, + + shouldStack: function() { + return this._stack; + }, + show: function() { this._visible = true; return this; diff --git a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js index 7aae1fb1b4..ce47877d1a 100644 --- a/webroot/rsrc/js/application/diff/ScrollObjectiveList.js +++ b/webroot/rsrc/js/application/diff/ScrollObjectiveList.js @@ -112,7 +112,8 @@ JX.install('ScrollObjectiveList', { items.push({ offset: offset, - node: objective_node + node: objective_node, + objective: objective }); } @@ -130,7 +131,11 @@ JX.install('ScrollObjectiveList', { offset = item.offset; if (min !== null) { - offset = Math.max(offset, min); + if (item.objective.shouldStack()) { + offset = min; + } else { + offset = Math.max(offset, min); + } } min = offset + 15; From 4d2c7e4d3d5d61e34b7e2af3df0e901d89d29433 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 20 May 2017 05:59:47 -0700 Subject: [PATCH 103/865] (stable) Show the curent selected inline in the objective list Summary: Ref T12733. When an inline is selected, make it stand out so you can see where you are in the document more clearly. Test Plan: {F4968509} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12733 Differential Revision: https://secure.phabricator.com/D17982 --- resources/celerity/map.php | 24 +++++++++---------- .../js/application/diff/DiffChangesetList.js | 16 ++++++++++++- .../rsrc/js/application/diff/DiffInline.js | 7 ++++++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1d430926a6..26ebc596f3 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => '599698a7', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => '886eadff', + 'differential.pkg.js' => '1d120743', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -391,8 +391,8 @@ 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => 'a716ca27', - 'rsrc/js/application/diff/DiffInline.js' => '1478c3b2', + 'rsrc/js/application/diff/DiffChangesetList.js' => '7a184082', + 'rsrc/js/application/diff/DiffInline.js' => '19582231', 'rsrc/js/application/diff/ScrollObjective.js' => '7e8877e7', 'rsrc/js/application/diff/ScrollObjectiveList.js' => '6120e99a', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', @@ -778,8 +778,8 @@ 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => 'a716ca27', - 'phabricator-diff-inline' => '1478c3b2', + 'phabricator-diff-changeset-list' => '7a184082', + 'phabricator-diff-inline' => '19582231', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -981,9 +981,6 @@ 'javelin-dom', 'javelin-typeahead-normalizer', ), - '1478c3b2' => array( - 'javelin-dom', - ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1003,6 +1000,9 @@ '185bbd53' => array( 'javelin-install', ), + 19582231 => array( + 'javelin-dom', + ), '19f9369b' => array( 'phui-oi-list-view-css', ), @@ -1488,6 +1488,10 @@ 'javelin-behavior', 'javelin-quicksand', ), + '7a184082' => array( + 'javelin-install', + 'phabricator-scroll-objective-list', + ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1724,10 +1728,6 @@ 'javelin-stratcom', 'javelin-dom', ), - 'a716ca27' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 8e35a92f80..c344c96de8 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -538,10 +538,24 @@ JX.install('DiffChangesetList', { }, _setSelectionState: function(item, manager) { - this._cursorItem = item; + // If we had an inline selected before, we need to update it after + // changing our selection to clear the selected state. Then, update the + // new one to add the selected state. + var old_inline = this.getSelectedInline(); + this._cursorItem = item; this._redrawSelection(manager, true); + var new_inline = this.getSelectedInline(); + + if (old_inline) { + old_inline.updateObjective(); + } + + if (new_inline) { + new_inline.updateObjective(); + } + return this; }, diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index b0a8202e54..bfc2a3e258 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -250,6 +250,13 @@ JX.install('DiffInline', { should_stack = true; } + if (changeset.getChangesetList().getSelectedInline() === this) { + // TODO: Maybe add some other kind of effect here, since we're only + // using color to show this? + color = 'yellow'; + } + + objective .setAnchor(anchor) .setIcon(icon) From e6825051c700066ad08c098bc152a67ca7d20d81 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 May 2017 16:05:04 -0700 Subject: [PATCH 104/865] (stable) Rebuild Celery map on "stable" after merges Normally this isn't necessary, but there was an unusually large amount of cherry-picking last week and things got a little out of sync. --- resources/celerity/map.php | 86 +++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 26ebc596f3..a2119c98a4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,8 +9,8 @@ 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '5ffe8b79', - 'core.pkg.js' => '599698a7', + 'core.pkg.css' => '19f6f61f', + 'core.pkg.js' => '21d34805', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', 'differential.pkg.js' => '1d120743', @@ -35,11 +35,11 @@ 'rsrc/css/aphront/table-view.css' => '34cf86b4', 'rsrc/css/aphront/tokenizer.css' => '9a8cb501', 'rsrc/css/aphront/tooltip.css' => '173b9431', - 'rsrc/css/aphront/typeahead-browse.css' => '8904346a', + 'rsrc/css/aphront/typeahead-browse.css' => '4f82e510', 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => '5294060f', + 'rsrc/css/application/base/main-menu-view.css' => 'de9fe8c4', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', @@ -71,6 +71,7 @@ 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', + 'rsrc/css/application/diffusion/diffusion-history.css' => '0c596546', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -109,7 +110,7 @@ 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/application-search-view.css' => '66ee5d46', - 'rsrc/css/application/search/search-results.css' => 'f87d23ad', + 'rsrc/css/application/search/search-results.css' => '8f8e08ed', 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', @@ -131,7 +132,7 @@ 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '412bef1a', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'ed19241b', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -139,14 +140,14 @@ 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => '8d23596a', + 'rsrc/css/phui/phui-button.css' => 'ccd8c6c5', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', 'rsrc/css/phui/phui-curtain-view.css' => '55dd0e59', - 'rsrc/css/phui/phui-document-pro.css' => '62c4dcbf', + 'rsrc/css/phui/phui-document-pro.css' => 'bb18da6b', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', @@ -154,10 +155,10 @@ 'rsrc/css/phui/phui-form-view.css' => '6175808d', 'rsrc/css/phui/phui-form.css' => 'a5570f70', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => 'e082678d', + 'rsrc/css/phui/phui-header-view.css' => 'a3d1aecd', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', - 'rsrc/css/phui/phui-icon.css' => '12b387a1', + 'rsrc/css/phui/phui-icon.css' => '4c6d624c', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6e217679', @@ -172,7 +173,7 @@ 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', - 'rsrc/css/phui/phui-tag-view.css' => 'cc4fd402', + 'rsrc/css/phui/phui-tag-view.css' => '3fa7765e', 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', @@ -433,7 +434,7 @@ 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/projects/WorkboardBoard.js' => '8935deef', 'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f', - 'rsrc/js/application/projects/WorkboardColumn.js' => '21df4ff5', + 'rsrc/js/application/projects/WorkboardColumn.js' => '758b4758', 'rsrc/js/application/projects/WorkboardController.js' => '26167537', 'rsrc/js/application/projects/behavior-project-boards.js' => '4250a34e', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', @@ -513,7 +514,7 @@ 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => 'eded9ee8', + 'rsrc/js/core/behavior-search-typeahead.js' => 'd0a99ab4', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -574,6 +575,7 @@ 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', + 'diffusion-history-css' => '0c596546', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', @@ -666,7 +668,7 @@ 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => 'eded9ee8', + 'javelin-behavior-phabricator-search-typeahead' => 'd0a99ab4', 'javelin-behavior-phabricator-show-older-transactions' => 'ae95d984', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -754,7 +756,7 @@ 'javelin-websocket' => '3ffe32d6', 'javelin-workboard-board' => '8935deef', 'javelin-workboard-card' => 'c587b80f', - 'javelin-workboard-column' => '21df4ff5', + 'javelin-workboard-column' => '758b4758', 'javelin-workboard-controller' => '26167537', 'javelin-workflow' => '1e911d0f', 'maniphest-batch-editor' => 'b0f0b6d5', @@ -790,7 +792,7 @@ 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', - 'phabricator-main-menu-view' => '5294060f', + 'phabricator-main-menu-view' => 'de9fe8c4', 'phabricator-nav-view-css' => 'faf6a6fc', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', @@ -801,7 +803,7 @@ 'phabricator-remarkup-css' => 'd1a5e11e', 'phabricator-scroll-objective' => '7e8877e7', 'phabricator-scroll-objective-list' => '6120e99a', - 'phabricator-search-results-css' => 'f87d23ad', + 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => '4383192f', @@ -836,7 +838,7 @@ 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => '8d23596a', + 'phui-button-css' => 'ccd8c6c5', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -849,18 +851,18 @@ 'phui-curtain-view-css' => '55dd0e59', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', - 'phui-document-view-pro-css' => '62c4dcbf', + 'phui-document-view-pro-css' => 'bb18da6b', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', 'phui-fontkit-css' => '1320ed01', 'phui-form-css' => 'a5570f70', 'phui-form-view-css' => '6175808d', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => 'e082678d', + 'phui-header-view-css' => 'a3d1aecd', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', - 'phui-icon-view-css' => '12b387a1', + 'phui-icon-view-css' => '4c6d624c', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', @@ -873,7 +875,7 @@ 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '412bef1a', + 'phui-oi-list-view-css' => 'ed19241b', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', @@ -882,7 +884,7 @@ 'phui-segment-bar-view-css' => 'b1d1b892', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => 'd5263e49', - 'phui-tag-view-css' => 'cc4fd402', + 'phui-tag-view-css' => '3fa7765e', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => '313c7f22', 'phui-two-column-view-css' => 'ce9fa0b7', @@ -912,7 +914,7 @@ 'syntax-default-css' => '9923583c', 'syntax-highlighting-css' => 'cae95e89', 'tokens-css' => '3d0f239e', - 'typeahead-browse-css' => '8904346a', + 'typeahead-browse-css' => '4f82e510', 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( @@ -1060,10 +1062,6 @@ 'javelin-install', 'javelin-dom', ), - '21df4ff5' => array( - 'javelin-install', - 'javelin-workboard-card', - ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1309,9 +1307,6 @@ 'javelin-vector', 'javelin-typeahead-static-source', ), - '5294060f' => array( - 'phui-theme-css', - ), '54b612ba' => array( 'javelin-color', 'javelin-install', @@ -1464,6 +1459,10 @@ 'javelin-vector', 'javelin-dom', ), + '758b4758' => array( + 'javelin-install', + 'javelin-workboard-card', + ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2014,6 +2013,17 @@ 'javelin-vector', 'phabricator-diff-inline', ), + 'd0a99ab4' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', @@ -2086,6 +2096,9 @@ 'javelin-typeahead-ondemand-source', 'javelin-dom', ), + 'de9fe8c4' => array( + 'phui-theme-css', + ), 'e0ec7f2f' => array( 'javelin-behavior', 'javelin-dom', @@ -2155,17 +2168,6 @@ 'javelin-dom', 'phabricator-draggable-list', ), - 'eded9ee8' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', From e632db4fc0e9d42ec7aa828ed47239b27ba98d8a Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 May 2017 18:54:21 -0700 Subject: [PATCH 105/865] (stable) Add needActiveDiffs to differential.createcomment method Summary: Ref T12766. Adds missing attachment for stacking actions in differential Test Plan: Asked end user to verify patch. Reviewers: epriestley, amckinley Reviewed By: epriestley, amckinley Subscribers: reed, Korvin Maniphest Tasks: T12766 Differential Revision: https://secure.phabricator.com/D18038 --- .../conduit/DifferentialCreateCommentConduitAPIMethod.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php index 52736d6f3b..943c3e7702 100644 --- a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php @@ -49,6 +49,7 @@ protected function execute(ConduitAPIRequest $request) { ->withIDs(array($request->getValue('revision_id'))) ->needReviewers(true) ->needReviewerAuthority(true) + ->needActiveDiffs(true) ->executeOne(); if (!$revision) { throw new ConduitException('ERR_BAD_REVISION'); From d3b7a0f37c975397455d633254e9b4a1246023f9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 May 2017 17:23:25 -0700 Subject: [PATCH 106/865] (stable) Fix lightbox circle icons Summary: These are unfortunatly manually built so I missed them in testing circle view changes. Test Plan: Test lightbox, conpherence, uiexamples Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18036 --- resources/celerity/map.php | 24 +++++++++---------- src/view/page/PhabricatorStandardPageView.php | 3 ++- .../js/core/behavior-lightbox-attachments.js | 4 ++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a2119c98a4..241f9acf08 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => '19f6f61f', - 'core.pkg.js' => '21d34805', + 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '7d4cfa59', 'differential.pkg.js' => '1d120743', @@ -501,7 +501,7 @@ 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', - 'rsrc/js/core/behavior-lightbox-attachments.js' => 'a5c57c24', + 'rsrc/js/core/behavior-lightbox-attachments.js' => '560f41da', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f', @@ -644,7 +644,7 @@ 'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-icon-composer' => '8499b6ab', 'javelin-behavior-launch-icon-composer' => '48086888', - 'javelin-behavior-lightbox-attachments' => 'a5c57c24', + 'javelin-behavior-lightbox-attachments' => '560f41da', 'javelin-behavior-line-chart' => 'e4232876', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', @@ -1332,6 +1332,15 @@ 'javelin-vector', 'javelin-dom', ), + '560f41da' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-mask', + 'javelin-util', + 'phuix-icon-view', + 'phabricator-busy', + ), '58dea2fa' => array( 'javelin-install', 'javelin-util', @@ -1708,15 +1717,6 @@ 'javelin-uri', 'phabricator-notification', ), - 'a5c57c24' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-mask', - 'javelin-util', - 'phuix-icon-view', - 'phabricator-busy', - ), 'a6b98425' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index ffa82ab1b9..a138d077b5 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -268,7 +268,8 @@ protected function willRenderPage() { } $icon = id(new PHUIIconView()) - ->setIcon('fa-download'); + ->setIcon('fa-download') + ->addClass('phui-icon-circle-icon'); $lightbox_id = celerity_generate_unique_node_id(); $download_form = phabricator_form( $user, diff --git a/webroot/rsrc/js/core/behavior-lightbox-attachments.js b/webroot/rsrc/js/core/behavior-lightbox-attachments.js index 929222f2f9..b9ea0dd8db 100644 --- a/webroot/rsrc/js/core/behavior-lightbox-attachments.js +++ b/webroot/rsrc/js/core/behavior-lightbox-attachments.js @@ -169,7 +169,7 @@ JX.behavior('lightbox-attachments', function (config) { ); var commentIcon = new JX.PHUIXIconView() - .setIcon('fa-comments') + .setIcon('fa-comments phui-icon-circle-icon') .getNode(); var commentButton = JX.$N('a', @@ -181,7 +181,7 @@ JX.behavior('lightbox-attachments', function (config) { commentIcon ); var closeIcon = new JX.PHUIXIconView() - .setIcon('fa-times') + .setIcon('fa-times phui-icon-circle-icon') .getNode(); var closeButton = JX.$N('a', From 69369aebd30f1c0e7d54cfda0299f0b32284bfd7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Jun 2017 13:29:14 -0700 Subject: [PATCH 107/865] (stable) Fix up the Celerity map on `stable`. --- resources/celerity/map.php | 253 +++++++++++++------------------------ 1 file changed, 91 insertions(+), 162 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 241f9acf08..702bc0e609 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,11 +9,11 @@ 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '19f6f61f', + 'core.pkg.css' => 'ea94e844', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '7d4cfa59', - 'differential.pkg.js' => '1d120743', + 'differential.pkg.css' => 'a2755617', + 'differential.pkg.js' => '9cab3335', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '84c8f8fd', 'favicon.ico' => '30672e08', @@ -33,13 +33,13 @@ 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'faf6a6fc', 'rsrc/css/aphront/table-view.css' => '34cf86b4', - 'rsrc/css/aphront/tokenizer.css' => '9a8cb501', + 'rsrc/css/aphront/tokenizer.css' => '15d5ff71', 'rsrc/css/aphront/tooltip.css' => '173b9431', 'rsrc/css/aphront/typeahead-browse.css' => '4f82e510', 'rsrc/css/aphront/typeahead.css' => '8a84cc7d', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', - 'rsrc/css/application/base/main-menu-view.css' => 'de9fe8c4', + 'rsrc/css/application/base/main-menu-view.css' => '16053029', 'rsrc/css/application/base/notification-menu.css' => '6a697e43', 'rsrc/css/application/base/phui-theme.css' => '9f261c6b', 'rsrc/css/application/base/standard-page-view.css' => 'eb5b80c5', @@ -64,14 +64,14 @@ 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'acfd58f6', + 'rsrc/css/application/differential/changeset-view.css' => '983751ee', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => '0c596546', + 'rsrc/css/application/diffusion/diffusion-history.css' => 'de70e348', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '18bd3910', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -114,16 +114,19 @@ 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '9f4cb463', + 'rsrc/css/core/core.css' => '23beb330', 'rsrc/css/core/remarkup.css' => 'd1a5e11e', 'rsrc/css/core/syntax.css' => 'cae95e89', - 'rsrc/css/core/z-index.css' => '998f3ce1', + 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', 'rsrc/css/font/font-awesome.css' => 'e838e088', 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-source-code-view.css' => '4383192f', + 'rsrc/css/phui/button/phui-button-bar.css' => '39fe680c', + 'rsrc/css/phui/button/phui-button-simple.css' => '081cfeea', + 'rsrc/css/phui/button/phui-button.css' => '9f13ddcc', 'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893', 'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c', @@ -132,7 +135,7 @@ 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'ed19241b', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '43752968', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'c01858f4', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', @@ -140,7 +143,6 @@ 'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '269cbc99', - 'rsrc/css/phui/phui-button.css' => 'ccd8c6c5', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => '57af2e14', @@ -158,7 +160,7 @@ 'rsrc/css/phui/phui-header-view.css' => 'a3d1aecd', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', - 'rsrc/css/phui/phui-icon.css' => '4c6d624c', + 'rsrc/css/phui/phui-icon.css' => '4c46b6ba', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6e217679', @@ -391,15 +393,13 @@ 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'cf4e2140', - 'rsrc/js/application/diff/DiffChangesetList.js' => '7a184082', - 'rsrc/js/application/diff/DiffInline.js' => '19582231', - 'rsrc/js/application/diff/ScrollObjective.js' => '7e8877e7', - 'rsrc/js/application/diff/ScrollObjectiveList.js' => '6120e99a', + 'rsrc/js/application/diff/DiffChangeset.js' => 'aaaf4cb5', + 'rsrc/js/application/diff/DiffChangesetList.js' => '85abc805', + 'rsrc/js/application/diff/DiffInline.js' => '1d17130f', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-populate.js' => '1de8bf63', + 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -454,17 +454,6 @@ 'rsrc/js/application/transactions/behavior-transaction-list.js' => '1f6794f6', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', 'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '93d0c9e3', - 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', - 'rsrc/js/application/uiexample/ReactorButtonExample.js' => 'd19198c8', - 'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '519705ea', - 'rsrc/js/application/uiexample/ReactorFocusExample.js' => '40a6a403', - 'rsrc/js/application/uiexample/ReactorInputExample.js' => '886fd850', - 'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '47c794d8', - 'rsrc/js/application/uiexample/ReactorRadioExample.js' => '988040b4', - 'rsrc/js/application/uiexample/ReactorSelectExample.js' => 'a155550f', - 'rsrc/js/application/uiexample/ReactorSendClassExample.js' => '1def2711', - 'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => 'b1f0ccee', - 'rsrc/js/application/uiexample/busy-example.js' => '60479091', 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', @@ -487,6 +476,7 @@ 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', + 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', 'rsrc/js/core/behavior-device.js' => 'bb1dd507', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', @@ -536,7 +526,9 @@ 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', + 'rsrc/js/phuix/PHUIXButtonView.js' => '0f13520b', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', + 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), @@ -549,7 +541,7 @@ 'aphront-multi-column-view-css' => '84cc6640', 'aphront-panel-view-css' => '8427b78d', 'aphront-table-view-css' => '34cf86b4', - 'aphront-tokenizer-control-css' => '9a8cb501', + 'aphront-tokenizer-control-css' => '15d5ff71', 'aphront-tooltip-css' => '173b9431', 'aphront-typeahead-control-css' => '8a84cc7d', 'application-search-view-css' => '66ee5d46', @@ -568,14 +560,14 @@ 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'acfd58f6', + 'differential-changeset-view-css' => '983751ee', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => '0c596546', + 'diffusion-history-css' => 'de70e348', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '18bd3910', 'diffusion-source-css' => '750add59', @@ -622,7 +614,7 @@ 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', - 'javelin-behavior-differential-populate' => '1de8bf63', + 'javelin-behavior-differential-populate' => '5e41c819', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -655,7 +647,7 @@ 'javelin-behavior-passphrase-credential-control' => '3cb0b2fc', 'javelin-behavior-phabricator-active-nav' => 'e379b58e', 'javelin-behavior-phabricator-autofocus' => '7319e029', - 'javelin-behavior-phabricator-busy-example' => '60479091', + 'javelin-behavior-phabricator-clipboard-copy' => 'b0b8f86d', 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', @@ -681,6 +673,7 @@ 'javelin-behavior-phui-hovercards' => 'bcaccd64', 'javelin-behavior-phui-submenu' => 'a6f7a73b', 'javelin-behavior-phui-tab-group' => '0a0b10e9', + 'javelin-behavior-phuix-example' => '68af71ca', 'javelin-behavior-policy-control' => 'd0c516d5', 'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-project-boards' => '4250a34e', @@ -774,14 +767,14 @@ 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '9f4cb463', + 'phabricator-core-css' => '23beb330', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'cf4e2140', - 'phabricator-diff-changeset-list' => '7a184082', - 'phabricator-diff-inline' => '19582231', + 'phabricator-diff-changeset' => 'aaaf4cb5', + 'phabricator-diff-changeset-list' => '85abc805', + 'phabricator-diff-inline' => '1d17130f', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -792,7 +785,7 @@ 'phabricator-flag-css' => 'bba8f811', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', - 'phabricator-main-menu-view' => 'de9fe8c4', + 'phabricator-main-menu-view' => '16053029', 'phabricator-nav-view-css' => 'faf6a6fc', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '3f6c89c9', @@ -801,8 +794,6 @@ 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', 'phabricator-remarkup-css' => 'd1a5e11e', - 'phabricator-scroll-objective' => '7e8877e7', - 'phabricator-scroll-objective-list' => '6120e99a', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -812,17 +803,7 @@ 'phabricator-title' => '485aaa6c', 'phabricator-tooltip' => '358b8c04', 'phabricator-ui-example-css' => '528b19de', - 'phabricator-uiexample-javelin-view' => 'd4a14807', - 'phabricator-uiexample-reactor-button' => 'd19198c8', - 'phabricator-uiexample-reactor-checkbox' => '519705ea', - 'phabricator-uiexample-reactor-focus' => '40a6a403', - 'phabricator-uiexample-reactor-input' => '886fd850', - 'phabricator-uiexample-reactor-mouseover' => '47c794d8', - 'phabricator-uiexample-reactor-radio' => '988040b4', - 'phabricator-uiexample-reactor-select' => 'a155550f', - 'phabricator-uiexample-reactor-sendclass' => '1def2711', - 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '998f3ce1', + 'phabricator-zindex-css' => '9d8f7c4b', 'phame-css' => 'b3a0b3a3', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', @@ -838,7 +819,9 @@ 'phui-basic-nav-view-css' => 'a0705f53', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '269cbc99', - 'phui-button-css' => 'ccd8c6c5', + 'phui-button-bar-css' => '39fe680c', + 'phui-button-css' => '9f13ddcc', + 'phui-button-simple-css' => '081cfeea', 'phui-calendar-css' => '477acfaa', 'phui-calendar-day-css' => '572b1893', 'phui-calendar-list-css' => '576be600', @@ -862,7 +845,7 @@ 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', - 'phui-icon-view-css' => '4c6d624c', + 'phui-icon-view-css' => '4c46b6ba', 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6e217679', @@ -875,7 +858,7 @@ 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => 'ed19241b', + 'phui-oi-list-view-css' => '43752968', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => '77d8a794', 'phui-pinboard-view-css' => '2495140e', @@ -895,6 +878,7 @@ 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => 'b3465b9b', 'phuix-autocomplete' => 'f6699267', + 'phuix-button-view' => '0f13520b', 'phuix-dropdown-menu' => '8018ee50', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', @@ -952,6 +936,9 @@ 'javelin-stratcom', 'javelin-workflow', ), + '081cfeea' => array( + 'phui-button-css', + ), '0825c27a' => array( 'javelin-behavior', 'javelin-dom', @@ -973,6 +960,10 @@ 'javelin-dom', 'javelin-router', ), + '0f13520b' => array( + 'javelin-install', + 'javelin-dom', + ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -989,6 +980,13 @@ 'javelin-dom', 'javelin-history', ), + '15d5ff71' => array( + 'aphront-typeahead-control-css', + 'phui-tag-view-css', + ), + 16053029 => array( + 'phui-theme-css', + ), '17bb8539' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1002,9 +1000,6 @@ '185bbd53' => array( 'javelin-install', ), - 19582231 => array( - 'javelin-dom', - ), '19f9369b' => array( 'phui-oi-list-view-css', ), @@ -1026,18 +1021,8 @@ 'javelin-request', 'javelin-uri', ), - '1de8bf63' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), - '1def2711' => array( - 'javelin-install', + '1d17130f' => array( 'javelin-dom', - 'javelin-reactor-dom', ), '1e911d0f' => array( 'javelin-stratcom', @@ -1128,6 +1113,10 @@ 'javelin-dom', 'javelin-vector', ), + '39fe680c' => array( + 'phui-button-css', + 'phui-button-simple-css', + ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1176,11 +1165,6 @@ 'javelin-workflow', 'phabricator-draggable-list', ), - '40a6a403' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 42126667 => array( 'javelin-behavior', 'javelin-dom', @@ -1215,11 +1199,6 @@ 'javelin-view-renderer', 'javelin-install', ), - '47c794d8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 48086888 => array( 'javelin-behavior', 'javelin-dom', @@ -1286,11 +1265,6 @@ 'javelin-typeahead-source', 'javelin-util', ), - '519705ea' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), '51c5ad07' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1371,6 +1345,14 @@ 'phabricator-phtize', 'javelin-dom', ), + '5e41c819' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1379,24 +1361,11 @@ 'phabricator-prefab', 'javelin-json', ), - 60479091 => array( - 'phabricator-busy', - 'javelin-behavior', - ), '60821bc7' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), - '6120e99a' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-scrollbar', - 'phabricator-scroll-objective', - ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', @@ -1419,6 +1388,11 @@ '6882e80a' => array( 'javelin-dom', ), + '68af71ca' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-button-view', + ), '69adf288' => array( 'javelin-install', ), @@ -1496,10 +1470,6 @@ 'javelin-behavior', 'javelin-quicksand', ), - '7a184082' => array( - 'javelin-install', - 'phabricator-scroll-objective-list', - ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1513,13 +1483,6 @@ '7e41274a' => array( 'javelin-install', ), - '7e8877e7' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - ), '7ebaeed3' => array( 'herald-rule-editor', 'javelin-behavior', @@ -1551,6 +1514,9 @@ 'javelin-dom', 'javelin-stratcom', ), + '85abc805' => array( + 'javelin-install', + ), '85ee8ce6' => array( 'aphront-dialog-view-css', ), @@ -1568,13 +1534,6 @@ 'phabricator-keyboard-shortcut', 'javelin-stratcom', ), - '886fd850' => array( - 'javelin-install', - 'javelin-reactor-dom', - 'javelin-view-html', - 'javelin-view-interpreter', - 'javelin-view-renderer', - ), '887ad43f' => array( 'javelin-behavior', 'javelin-request', @@ -1662,10 +1621,8 @@ 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), - '988040b4' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', + '983751ee' => array( + 'phui-inline-comment-view-css', ), '9a6dd75c' => array( 'javelin-behavior', @@ -1676,10 +1633,6 @@ 'phuix-icon-view', 'javelin-behavior-phabricator-gesture', ), - '9a8cb501' => array( - 'aphront-typeahead-control-css', - 'phui-tag-view-css', - ), '9bbf3762' => array( 'javelin-behavior', 'javelin-dom', @@ -1704,11 +1657,6 @@ 'javelin-util', 'phabricator-keyboard-shortcut', ), - 'a155550f' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-reactor-dom', - ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1768,6 +1716,17 @@ 'javelin-util', 'phabricator-prefab', ), + 'aaaf4cb5' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), 'ab2f381b' => array( 'javelin-request', 'javelin-behavior', @@ -1787,9 +1746,6 @@ 'phuix-autocomplete', 'javelin-mask', ), - 'acfd58f6' => array( - 'phui-inline-comment-view-css', - ), 'ae95d984' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1802,10 +1758,10 @@ 'javelin-dom', 'phuix-dropdown-menu', ), - 'b1f0ccee' => array( - 'javelin-install', + 'b0b8f86d' => array( + 'javelin-behavior', 'javelin-dom', - 'javelin-reactor-dom', + 'javelin-stratcom', ), 'b23b49e6' => array( 'javelin-behavior', @@ -2002,17 +1958,6 @@ 'cd2b9b77' => array( 'phui-oi-list-view-css', ), - 'cf4e2140' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'd0a99ab4' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2034,13 +1979,6 @@ 'javelin-workflow', 'phuix-icon-view', ), - 'd19198c8' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-util', - 'javelin-dynval', - 'javelin-reactor-dom', - ), 'd254d646' => array( 'javelin-util', ), @@ -2050,11 +1988,6 @@ 'javelin-uri', 'javelin-util', ), - 'd4a14807' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-view', - ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', @@ -2096,9 +2029,6 @@ 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'de9fe8c4' => array( - 'phui-theme-css', - ), 'e0ec7f2f' => array( 'javelin-behavior', 'javelin-dom', @@ -2258,6 +2188,7 @@ 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', + 'phui-button-simple-css', 'phui-theme-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', @@ -2435,8 +2366,6 @@ 'javelin-behavior-load-blame', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', - 'phabricator-scroll-objective', - 'phabricator-scroll-objective-list', 'phabricator-diff-inline', 'phabricator-diff-changeset', 'phabricator-diff-changeset-list', From acaa2e148f578dff279fc8b2614aaafe30026d72 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 2 Jun 2017 16:10:59 -0700 Subject: [PATCH 108/865] (stable) Fix an issue where Phriction moves to new locations would fail with a "content required" error Summary: Ref T12793. I'd like to understand exactly when we broke this, but this seems to be a minimal fix that shouldn't do anything surprising. When you move document `/a/` to `/a/b/` and that path doesn't exist yet, the Content transaction currently fails because there's "no content". The content gets added later by the "move" transaction but this is implicit. To make this work, just ignore the "missing field" error. This is a little roundabout but unlikely to break anything in weird ways. Test Plan: - Moved document `/a/b/` to `/a/b/c/`. - Before patch: error about missing content. - After patch: move worked properly. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12793 Differential Revision: https://secure.phabricator.com/D18069 --- .../phriction/controller/PhrictionMoveController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/phriction/controller/PhrictionMoveController.php b/src/applications/phriction/controller/PhrictionMoveController.php index f25465c3c0..b82681f0a7 100644 --- a/src/applications/phriction/controller/PhrictionMoveController.php +++ b/src/applications/phriction/controller/PhrictionMoveController.php @@ -60,6 +60,7 @@ public function handleRequest(AphrontRequest $request) { ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) ->setDescription($v_note); $xactions = array(); From 5885704800a97aadf47aee5654ff2647b40fb4ad Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 9 Jun 2017 22:17:36 +0000 Subject: [PATCH 109/865] (stable) Clean up spacing on diff-banner Summary: Adds spacing to the buttons, line-height for aligning text vertically. Test Plan: Leave comments on a diff. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18110 --- resources/celerity/map.php | 12 ++++++------ .../css/application/differential/changeset-view.css | 9 +++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1c0fb7fb3c..2209dffea7 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -12,7 +12,7 @@ 'core.pkg.css' => 'ab24402f', 'core.pkg.js' => '1475bd91', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '1ccbf3a9', + 'differential.pkg.css' => '4e99863c', 'differential.pkg.js' => 'b7504037', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', @@ -64,7 +64,7 @@ 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'c3f44655', + 'rsrc/css/application/differential/changeset-view.css' => 'c72dba88', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', @@ -562,7 +562,7 @@ 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'c3f44655', + 'differential-changeset-view-css' => 'c72dba88', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', @@ -1872,9 +1872,6 @@ 'javelin-dom', 'javelin-vector', ), - 'c3f44655' => array( - 'phui-inline-comment-view-css', - ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1896,6 +1893,9 @@ 'javelin-stratcom', 'javelin-util', ), + 'c72dba88' => array( + 'phui-inline-comment-view-css', + ), 'c7ccd872' => array( 'phui-fontkit-css', ), diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css index 33dcea1213..bfde532c44 100644 --- a/webroot/rsrc/css/application/differential/changeset-view.css +++ b/webroot/rsrc/css/application/differential/changeset-view.css @@ -392,13 +392,14 @@ tr.differential-inline-loading { top: 0; left: 0; right: 0; - background: rgba(255, 255, 255, 0.95); + background: #fff; box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); border-bottom: 1px solid {$lightgreyborder}; - padding: 12px 18px; + padding: 8px 18px; vertical-align: middle; font-weight: bold; font-size: {$biggerfontsize}; + line-height: 28px; } .diff-banner .phui-icon-view { @@ -409,6 +410,10 @@ tr.differential-inline-loading { color: {$greytext}; } +.diff-banner-buttons .button { + margin-left: 8px; +} + .diff-banner-has-unsaved, .diff-banner-has-unsubmitted { background: {$sh-yellowbackground}; From 314723d309cbea57051a257d75e301e7e26ec8f8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Jun 2017 16:54:56 -0700 Subject: [PATCH 110/865] (stable) Rebuild Celerity map on stable. --- resources/celerity/map.php | 116 ++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2209dffea7..0619fc34f8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,11 +9,11 @@ 'names' => array( 'conpherence.pkg.css' => 'ff161f2d', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'ab24402f', - 'core.pkg.js' => '1475bd91', + 'core.pkg.css' => '37dd219b', + 'core.pkg.js' => '5d80e0db', 'darkconsole.pkg.js' => '1f9a31bc', - 'differential.pkg.css' => '4e99863c', - 'differential.pkg.js' => 'b7504037', + 'differential.pkg.css' => '4ec4a37a', + 'differential.pkg.js' => 'd4ab0e81', 'diffusion.pkg.css' => 'b93d9b8c', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -64,14 +64,14 @@ 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', - 'rsrc/css/application/differential/changeset-view.css' => 'c72dba88', + 'rsrc/css/application/differential/changeset-view.css' => 'b5e6be7f', 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 'rsrc/css/application/differential/phui-inline-comment.css' => 'ffd1a542', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', - 'rsrc/css/application/diffusion/diffusion-history.css' => '6870e8c1', + 'rsrc/css/application/diffusion/diffusion-history.css' => '4540f568', 'rsrc/css/application/diffusion/diffusion-icons.css' => 'a6a1e2ba', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-source.css' => '750add59', @@ -115,7 +115,7 @@ 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => '23beb330', - 'rsrc/css/core/remarkup.css' => '509fb36e', + 'rsrc/css/core/remarkup.css' => 'eb37bd0d', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', @@ -166,7 +166,7 @@ 'rsrc/css/phui/phui-info-view.css' => '6e217679', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', - 'rsrc/css/phui/phui-list.css' => '12eb8ce6', + 'rsrc/css/phui/phui-list.css' => 'dcafb463', 'rsrc/css/phui/phui-object-box.css' => '9cff003c', 'rsrc/css/phui/phui-pager.css' => 'edcbc226', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', @@ -177,7 +177,7 @@ 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '93b084cf', 'rsrc/css/phui/phui-timeline-view.css' => '313c7f22', - 'rsrc/css/phui/phui-two-column-view.css' => 'ce9fa0b7', + 'rsrc/css/phui/phui-two-column-view.css' => '5b8cd553', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', 'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92', @@ -395,13 +395,13 @@ 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '408bf173', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', - 'rsrc/js/application/diff/DiffChangeset.js' => 'd498bddb', - 'rsrc/js/application/diff/DiffChangesetList.js' => '29bbc02c', - 'rsrc/js/application/diff/DiffInline.js' => '20553f71', + 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', + 'rsrc/js/application/diff/DiffChangesetList.js' => '79de07c6', + 'rsrc/js/application/diff/DiffInline.js' => '1bfa31c7', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', - 'rsrc/js/application/differential/behavior-populate.js' => '5e41c819', + 'rsrc/js/application/differential/behavior-populate.js' => '419998ab', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', @@ -526,7 +526,7 @@ 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', - 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', + 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'f6699267', 'rsrc/js/phuix/PHUIXButtonView.js' => 'a37126bd', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', @@ -562,14 +562,14 @@ 'conpherence-thread-manager' => '4d863052', 'conpherence-transaction-css' => '85129c68', 'd3' => 'a11a5ff2', - 'differential-changeset-view-css' => 'c72dba88', + 'differential-changeset-view-css' => 'b5e6be7f', 'differential-core-view-css' => '5b7b8ff4', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', - 'diffusion-history-css' => '6870e8c1', + 'diffusion-history-css' => '4540f568', 'diffusion-icons-css' => 'a6a1e2ba', 'diffusion-readme-css' => '419dd5b6', 'diffusion-source-css' => '750add59', @@ -616,7 +616,7 @@ 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', - 'javelin-behavior-differential-populate' => '5e41c819', + 'javelin-behavior-differential-populate' => '419998ab', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-browse-file' => '054a0f0b', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', @@ -774,9 +774,9 @@ 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', - 'phabricator-diff-changeset' => 'd498bddb', - 'phabricator-diff-changeset-list' => '29bbc02c', - 'phabricator-diff-inline' => '20553f71', + 'phabricator-diff-changeset' => '99abf4cd', + 'phabricator-diff-changeset-list' => '79de07c6', + 'phabricator-diff-inline' => '1bfa31c7', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', 'phabricator-fatal-config-template-css' => '8f18fa41', @@ -795,7 +795,7 @@ 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => 'c5af80a2', - 'phabricator-remarkup-css' => '509fb36e', + 'phabricator-remarkup-css' => 'eb37bd0d', 'phabricator-search-results-css' => '8f8e08ed', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-slowvote-css' => 'a94b7230', @@ -854,7 +854,7 @@ 'phui-inline-comment-view-css' => 'ffd1a542', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-lightbox-css' => '0a035e40', - 'phui-list-view-css' => '12eb8ce6', + 'phui-list-view-css' => 'dcafb463', 'phui-object-box-css' => '9cff003c', 'phui-oi-big-ui-css' => '19f9369b', 'phui-oi-color-css' => 'cd2b9b77', @@ -872,13 +872,13 @@ 'phui-tag-view-css' => '93b084cf', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => '313c7f22', - 'phui-two-column-view-css' => 'ce9fa0b7', + 'phui-two-column-view-css' => '5b8cd553', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', 'phui-workcard-view-css' => 'cca5fa92', 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', - 'phuix-action-view' => 'b3465b9b', + 'phuix-action-view' => '442efd08', 'phuix-autocomplete' => 'f6699267', 'phuix-button-view' => 'a37126bd', 'phuix-dropdown-menu' => '8018ee50', @@ -1016,6 +1016,9 @@ 'javelin-request', 'javelin-uri', ), + '1bfa31c7' => array( + 'javelin-dom', + ), '1e911d0f' => array( 'javelin-stratcom', 'javelin-request', @@ -1039,9 +1042,6 @@ 'javelin-install', 'javelin-dom', ), - '20553f71' => array( - 'javelin-dom', - ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1067,10 +1067,6 @@ 'javelin-install', 'javelin-util', ), - '29bbc02c' => array( - 'javelin-install', - 'phuix-button-view', - ), '2ae077e1' => array( 'javelin-behavior', 'javelin-dom', @@ -1164,6 +1160,14 @@ 'javelin-workflow', 'phabricator-draggable-list', ), + '419998ab' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'phabricator-tooltip', + 'phabricator-diff-changeset-list', + 'phabricator-diff-changeset', + ), 42126667 => array( 'javelin-behavior', 'javelin-dom', @@ -1178,6 +1182,11 @@ 'javelin-workflow', 'javelin-workboard-controller', ), + '442efd08' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-util', + ), '44959b73' => array( 'javelin-util', 'javelin-uri', @@ -1339,14 +1348,6 @@ 'phabricator-phtize', 'javelin-dom', ), - '5e41c819' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'phabricator-tooltip', - 'phabricator-diff-changeset-list', - 'phabricator-diff-changeset', - ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', @@ -1475,6 +1476,10 @@ 'javelin-behavior', 'javelin-quicksand', ), + '79de07c6' => array( + 'javelin-install', + 'phuix-button-view', + ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', @@ -1626,6 +1631,17 @@ 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), + '99abf4cd' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + 'phabricator-diff-inline', + ), '9a6dd75c' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1771,11 +1787,6 @@ 'javelin-uri', 'javelin-request', ), - 'b3465b9b' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-util', - ), 'b3a4b884' => array( 'javelin-behavior', 'phabricator-prefab', @@ -1800,6 +1811,9 @@ 'javelin-dom', 'javelin-util', ), + 'b5e6be7f' => array( + 'phui-inline-comment-view-css', + ), 'b6993408' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1893,9 +1907,6 @@ 'javelin-stratcom', 'javelin-util', ), - 'c72dba88' => array( - 'phui-inline-comment-view-css', - ), 'c7ccd872' => array( 'phui-fontkit-css', ), @@ -1986,17 +1997,6 @@ 'javelin-uri', 'javelin-util', ), - 'd498bddb' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - 'phabricator-diff-inline', - ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', From 7cb67dc7257e2a90be00b1e8cefd331a8402662a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 06:45:42 -0700 Subject: [PATCH 111/865] (stable) Restore "Land Revision" action to UI Summary: This was accidentally caught in the crossfire in D18150. This is stable enough to formalize instead of adding with an event hook. Test Plan: Looked at a candidate revision, saw "Land Revision" appear in UI again. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18154 --- .../DifferentialRevisionViewController.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index f19ff37360..ab03ee6d81 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -615,6 +615,29 @@ private function buildCurtain(DifferentialRevision $revision) { $curtain->addAction($relationship_submenu); } + $repository = $revision->getRepository(); + if ($repository && $repository->canPerformAutomation()) { + $revision_id = $revision->getID(); + + $op = new DrydockLandRepositoryOperation(); + $barrier = $op->getBarrierToLanding($viewer, $revision); + + if ($barrier) { + $can_land = false; + } else { + $can_land = true; + } + + $action = id(new PhabricatorActionView()) + ->setName(pht('Land Revision')) + ->setIcon('fa-fighter-jet') + ->setHref("/differential/revision/operation/{$revision_id}/") + ->setWorkflow(true) + ->setDisabled(!$can_land); + + $curtain->addAction($action); + } + return $curtain; } From 3480b3e7ef00ff8a42f78f2be31bda8aab318353 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 9 Jul 2017 06:40:51 -0700 Subject: [PATCH 112/865] (stable) Fix comparison check for SVN in browsing Diffusion Summary: Fixes T12905. Missed setting this variable. Test Plan: Browse an SVN repository. Reviewers: epriestley Subscribers: Korvin Maniphest Tasks: T12905 Differential Revision: https://secure.phabricator.com/D18190 --- .../diffusion/controller/DiffusionBrowseController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 3fa9e62948..06c8336f5c 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1667,6 +1667,7 @@ protected function getActions(DiffusionRequest $drequest) { $repository = $drequest->getRepository(); $history_uri = $drequest->generateURI(array('action' => 'history')); $behind_head = $drequest->getSymbolicCommit(); + $compare = null; $head_uri = $drequest->generateURI( array( 'commit' => '', @@ -1771,11 +1772,11 @@ private function buildOpenRevisions() { } $header = id(new PHUIHeaderView()) - ->setHeader(pht('Recently Open Revisions')) - ->setHeaderIcon('fa-gear'); + ->setHeader(pht('Recently Open Revisions')); $view = id(new DifferentialRevisionListView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setRevisions($revisions) ->setUser($viewer); From 57a584e270a703113431068e77ffcea122fdeb6c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 28 Jul 2017 10:37:53 -0700 Subject: [PATCH 113/865] (stable) Don't fatal when viewing tags pointing at commits we haven't imported/parsed yet Summary: In Diffusion, the "Tags" view may read commits which haven't imported or parsed yet, and thus don't have loadable objects. Most of this logic tests for `if ($commit)`, but the author part did not. Instead, don't render author information if `$commit` is not present. Test Plan: - Loaded tags view with commits present. - Faked `$commit = null;`, loaded tag view, got this instead of a fatal: {F5068432} Reviewers: chad, amckinley Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18290 --- .../diffusion/view/DiffusionTagListView.php | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index abab9003c3..0f8cbeae71 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -52,24 +52,12 @@ public function render() { 'commit' => $tag->getCommitIdentifier(), )); - $author = null; - if ($commit && $commit->getAuthorPHID()) { - $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); - } else if ($commit && $commit->getCommitData()) { - $author = self::renderName($commit->getCommitData()->getAuthorName()); + if ($commit) { + $author = $this->renderAuthor($tag, $commit); } else { - $author = self::renderName($tag->getAuthor()); + $author = null; } - $committed = phabricator_datetime($commit->getEpoch(), $viewer); - $author_name = phutil_tag( - 'strong', - array( - 'class' => 'diffusion-history-author-name', - ), - $author); - $authored = pht('%s on %s.', $author_name, $committed); - $description = null; if ($tag->getType() == 'git/tag') { // In Git, a tag may be a "real" tag, or just a reference to a commit. @@ -139,16 +127,43 @@ public function render() { ->setHref($tag_href) ->addAttribute(array($commit_tag)) ->addAttribute($description) - ->addAttribute($authored) ->setSideColumn(array( $build_view, $button_bar, )); + if ($author) { + $item->addAttribute($author); + } + $list->addItem($item); } return $list; } + private function renderAuthor( + DiffusionRepositoryTag $tag, + PhabricatorRepositoryCommit $commit) { + $viewer = $this->getViewer(); + + if ($commit->getAuthorPHID()) { + $author = $this->handles[$commit->getAuthorPHID()]->renderLink(); + } else if ($commit->getCommitData()) { + $author = self::renderName($commit->getCommitData()->getAuthorName()); + } else { + $author = self::renderName($tag->getAuthor()); + } + + $committed = phabricator_datetime($commit->getEpoch(), $viewer); + $author_name = phutil_tag( + 'strong', + array( + 'class' => 'diffusion-history-author-name', + ), + $author); + + return pht('%s on %s.', $author_name, $committed); + } + } From 77e4e6fdb44f7c4584fb5d6b638f1094bfe5315e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Aug 2017 17:04:11 -0700 Subject: [PATCH 114/865] (stable) Pass SSH wrappers to VCS commands unconditonally, not just if there's an SSH remote Summary: Ref T12961. In Mercurial, it's possible to have "subrepos" which may use a different protocol than the main repository. By putting an SSH repository inside an HTTP repository, an attacker can theoretically get us to execute `hg` without overriding `ui.ssh`, then execute code via the SSH hostname attack. As an immediate mitigation to this attack, specify `ui.ssh` unconditionally. Normally, this will have no effect (it will just be ignored). In the specific case of an SSH repo inside an HTTP repo, it will defuse the `ssh` protocol. For good measure and consistency, do the same for Subversion and Git. However, we don't normally maintain working copies for either Subversion or Git so it's unlikely that similar attacks exist there. Test Plan: - Put an SSH subrepo with an attack URI inside an HTTP outer repo in Mercurial. - Ran `hg up` with and without `ui.ssh` specified. - Got dangerous badness without `ui.ssh` and safe `ssh` subprocesses with `ui.ssh`. I'm not yet able to confirm that `hg pull -u -- ` can actually trigger this, but this can't hurt and our SSH wrapper is safer than the native behavior for all Subversion, Git and Mercurial versions released prior to today. Reviewers: chad Reviewed By: chad Subscribers: cspeckmim Maniphest Tasks: T12961 Differential Revision: https://secure.phabricator.com/D18389 --- .../protocol/DiffusionGitCommandEngine.php | 4 +--- .../protocol/DiffusionMercurialCommandEngine.php | 14 ++++++++------ .../protocol/DiffusionSubversionCommandEngine.php | 4 +--- .../__tests__/DiffusionCommandEngineTestCase.php | 4 ++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php index a16416ae1d..168b18caa5 100644 --- a/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionGitCommandEngine.php @@ -26,9 +26,7 @@ protected function newCustomEnvironment() { $env['HOME'] = PhabricatorEnv::getEmptyCWD(); - if ($this->isAnySSHProtocol()) { - $env['GIT_SSH'] = $this->getSSHWrapper(); - } + $env['GIT_SSH'] = $this->getSSHWrapper(); if ($this->isAnyHTTPProtocol()) { $uri = $this->getURI(); diff --git a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php index 49c618b085..9499b8f5f1 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php @@ -11,12 +11,14 @@ protected function canBuildForRepository( protected function newFormattedCommand($pattern, array $argv) { $args = array(); - if ($this->isAnySSHProtocol()) { - $pattern = "hg --config ui.ssh=%s {$pattern}"; - $args[] = $this->getSSHWrapper(); - } else { - $pattern = "hg {$pattern}"; - } + // NOTE: Here, and in Git and Subversion, we override the SSH command even + // if the repository does not use an SSH remote, since our SSH wrapper + // defuses an attack against older versions of Mercurial, Git and + // Subversion (see T12961) and it's possible to execute this attack + // in indirect ways, like by using an SSH subrepo inside an HTTP repo. + + $pattern = "hg --config ui.ssh=%s {$pattern}"; + $args[] = $this->getSSHWrapper(); return array($pattern, array_merge($args, $argv)); } diff --git a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php index d8e9e6ff17..75b8785a6e 100644 --- a/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionSubversionCommandEngine.php @@ -44,9 +44,7 @@ protected function newFormattedCommand($pattern, array $argv) { protected function newCustomEnvironment() { $env = array(); - if ($this->isAnySSHProtocol()) { - $env['SVN_SSH'] = $this->getSSHWrapper(); - } + $env['SVN_SSH'] = $this->getSSHWrapper(); return $env; } diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php index 76d9ad9170..1df3ea3522 100644 --- a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php +++ b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php @@ -26,7 +26,7 @@ public function testCommandEngine() { )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', @@ -102,7 +102,7 @@ public function testCommandEngine() { )); $this->assertCommandEngineFormat( - 'hg xyz', + (string)csprintf('hg --config ui.ssh=%s xyz', $ssh_wrapper), array( 'LANG' => 'en_US.UTF-8', 'HGPLAIN' => '1', From 41e823796ac1a80295c14a07faf331dd5e2aeb28 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Aug 2017 17:22:32 -0700 Subject: [PATCH 115/865] (stable) Stop populating or updating working copies in observed Mercurial repositories Summary: Ref T12961. Fixes T4416. Currently, for observed Mercurial repositories, we build a working copy with `pull -u` (for "update"). This should be unnecessary, and we don't do it for hosted Mercurial repositories. We also stopped doing it years ago for Git repositories. We also don't clone Mercurial repositories with a working copy. It's possible something has slipped through the cracks here so I'll hold this until after the release cut, but I believe there are no actual technical blockers here. Test Plan: - Observed a public Mercurial repository on Bitbucket. - Let it import. - Browsed commits, branches, file content, etc., without any apparent issues. Reviewers: chad Reviewed By: chad Subscribers: cspeckmim Maniphest Tasks: T12961, T4416 Differential Revision: https://secure.phabricator.com/D18390 --- .../repository/engine/PhabricatorRepositoryPullEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php index 32e99e619d..10eddd38d4 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -529,7 +529,7 @@ private function executeMercurialUpdate() { // This is a local command, but needs credentials. $remote = $repository->getRemoteURIEnvelope(); - $future = $repository->getRemoteCommandFuture('pull -u -- %P', $remote); + $future = $repository->getRemoteCommandFuture('pull -- %P', $remote); $future->setCWD($path); try { From ea4e33261e499b183558e7ef58837970d6de3b7a Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 11 Aug 2017 09:47:30 -0700 Subject: [PATCH 116/865] (stable) Fix an inverted condition for the "Reopen Revision" action Summary: Ref T2543. I converted this condition the wrong way, missing a `!`. I'll cherry-pick this to `stable`. Test Plan: No more "Reopen Revision" action available on open revisions. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18399 --- .../xaction/DifferentialRevisionReopenTransaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php index e2b217603a..7ba20772c7 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php @@ -39,7 +39,7 @@ public function applyInternalEffects($object, $value) { } protected function validateAction($object, PhabricatorUser $viewer) { - if ($object->isPublished()) { + if (!$object->isPublished()) { throw new Exception( pht( 'You can not reopen this revision because it is not closed. '. From 017125598631b394fd4bb05c52a0ad1180a42587 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Aug 2017 09:54:03 -0700 Subject: [PATCH 117/865] (stable) Fix an issue where "Close Revision" did not appear in the UI Summary: Ref T2543. When called from the UI to build the dropdown, there's no Editor, since we aren't actually in an edit flow. This logic worked for actually performing the edits, just not for getting the option into the dropdown. Test Plan: Used the dropdown to close an "Accepted" revision which I authored. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18490 --- .../xaction/DifferentialRevisionCloseTransaction.php | 10 ++++++---- .../storage/PhabricatorModularTransactionType.php | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index 8d01f48eff..d71b30950a 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -46,10 +46,12 @@ public function applyInternalEffects($object, $value) { } protected function validateAction($object, PhabricatorUser $viewer) { - if ($this->getEditor()->getIsCloseByCommit()) { - // If we're closing a revision because we discovered a commit, we don't - // care what state it was in. - return; + if ($this->hasEditor()) { + if ($this->getEditor()->getIsCloseByCommit()) { + // If we're closing a revision because we discovered a commit, we don't + // care what state it was in. + return; + } } if ($object->isClosed()) { diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 119bfedd32..f2b59c8a2b 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -134,6 +134,10 @@ final protected function getEditor() { return $this->editor; } + final protected function hasEditor() { + return (bool)$this->editor; + } + final protected function getAuthorPHID() { return $this->getStorage()->getAuthorPHID(); } From 70ebdd2694553f480cab6f4b3db86f5b82eba277 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 29 Aug 2017 12:42:58 -0700 Subject: [PATCH 118/865] (stable) Make legacy revision statuses from "differential.query" have type "string" again Summary: Ref T2543. The type on these got changed by accident, it should be "string" (crazy nonsense, compatible) not "int" (sensible, not compatible). (New API uses sensible strings like "accepted" only.) Test Plan: Called `differential.query` from web UI, saw `"2"` and similar statuses. Reviewers: chad, jmeador, lvital Reviewed By: jmeador, lvital Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18493 --- .../conduit/DifferentialQueryConduitAPIMethod.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php index 0b26db0655..eeef36ee69 100644 --- a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php @@ -218,7 +218,11 @@ protected function execute(ConduitAPIRequest $request) { 'dateCreated' => $revision->getDateCreated(), 'dateModified' => $revision->getDateModified(), 'authorPHID' => $revision->getAuthorPHID(), - 'status' => $revision->getLegacyRevisionStatus(), + + // NOTE: For backward compatibility this is explicitly a string, like + // "2", even though the value of the string is an integer. See PHI14. + 'status' => (string)$revision->getLegacyRevisionStatus(), + 'statusName' => $revision->getStatusDisplayName(), 'properties' => $revision->getProperties(), 'branch' => $diff->getBranch(), From 89a70bf56c93a86e9ead9b664ff95227cbb77644 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Sep 2017 11:33:22 -0700 Subject: [PATCH 119/865] (stable) Fix credential control logic for restricted credentials Summary: Fixes T12975. This logic didn't deal with PolicyException correctly. Test Plan: {F5167549} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12975 Differential Revision: https://secure.phabricator.com/D18537 --- .../view/PassphraseCredentialControl.php | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php index 1c189e30e7..9c9706fe3f 100644 --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -53,13 +53,22 @@ protected function renderInput() { if (strlen($current_phid) && empty($options_map[$current_phid])) { $viewer = $this->getViewer(); - $user_credential = id(new PassphraseCredentialQuery()) - ->setViewer($viewer) - ->withPHIDs(array($current_phid)) - ->executeOne(); - if (!$user_credential) { + $current_name = null; + try { + $user_credential = id(new PassphraseCredentialQuery()) + ->setViewer($viewer) + ->withPHIDs(array($current_phid)) + ->executeOne(); + + if ($user_credential) { + $current_name = pht( + '%s %s', + $user_credential->getMonogram(), + $user_credential->getName()); + } + } catch (PhabricatorPolicyException $policy_exception) { // Pull the credential with the ominipotent viewer so we can look up - // the ID and tell if it's restricted or invalid. + // the ID and provide the monogram. $omnipotent_credential = id(new PassphraseCredentialQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($current_phid)) @@ -68,16 +77,13 @@ protected function renderInput() { $current_name = pht( '%s (Restricted Credential)', $omnipotent_credential->getMonogram()); - } else { - $current_name = pht( - 'Invalid Credential ("%s")', - $current_phid); } - } else { + } + + if ($current_name === null) { $current_name = pht( - '%s %s', - $user_credential->getMonogram(), - $user_credential->getName()); + 'Invalid Credential ("%s")', + $current_phid); } $options_map = array( From 97f0103a80fe7a0190f2104e07be18007834f1e0 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 11:17:58 -0700 Subject: [PATCH 120/865] Update Spaces for new edit UI Summary: New edit ui Test Plan: create a space Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18558 --- .../controller/PhabricatorSpacesEditController.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/applications/spaces/controller/PhabricatorSpacesEditController.php b/src/applications/spaces/controller/PhabricatorSpacesEditController.php index faca39d634..cf3b6578b6 100644 --- a/src/applications/spaces/controller/PhabricatorSpacesEditController.php +++ b/src/applications/spaces/controller/PhabricatorSpacesEditController.php @@ -165,8 +165,8 @@ public function handleRequest(AphrontRequest $request) { ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Space')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setValidationException($validation_exception) ->appendChild($form); @@ -179,12 +179,7 @@ public function handleRequest(AphrontRequest $request) { $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($header_text) - ->setHeaderIcon('fa-pencil'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $box, )); From d3db3fff59de592768030171959b5439e6f69afa Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 11:12:45 -0700 Subject: [PATCH 121/865] Update file edit UI Summary: New white box Test Plan: /file/upload/ Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18557 --- .../files/controller/PhabricatorFileUploadController.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/applications/files/controller/PhabricatorFileUploadController.php b/src/applications/files/controller/PhabricatorFileUploadController.php index 3be0ca3968..c06e0e6d89 100644 --- a/src/applications/files/controller/PhabricatorFileUploadController.php +++ b/src/applications/files/controller/PhabricatorFileUploadController.php @@ -90,17 +90,12 @@ public function handleRequest(AphrontRequest $request) { ->setShowIfSupportedID($support_id); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('File')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-upload'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $form_box, $global_upload, From a5232e015afa6fe65086563806288d0df9f2bcb7 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 12:01:44 -0700 Subject: [PATCH 122/865] Update herald edit for new UI Summary: Updates herald update, create Test Plan: Create some rulez Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18563 --- .../herald/controller/HeraldNewController.php | 8 ++------ .../herald/controller/HeraldRuleController.php | 7 ++----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/applications/herald/controller/HeraldNewController.php b/src/applications/herald/controller/HeraldNewController.php index e9fa1e66bd..fbaf1aeb9e 100644 --- a/src/applications/herald/controller/HeraldNewController.php +++ b/src/applications/herald/controller/HeraldNewController.php @@ -194,8 +194,9 @@ public function handleRequest(AphrontRequest $request) { ->addCancelButton($cancel_uri, $cancel_text)); $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $crumbs = $this @@ -203,12 +204,7 @@ public function handleRequest(AphrontRequest $request) { ->addTextCrumb(pht('Create Rule')) ->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($form_box); return $this->newPage() diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index 0e7f6b0aa5..d338f6a57e 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -241,6 +241,8 @@ public function handleRequest(AphrontRequest $request) { $icon = $rule->getID() ? 'fa-pencil' : 'fa-plus-square'; $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setFormErrors($errors) ->setForm($form); @@ -249,12 +251,7 @@ public function handleRequest(AphrontRequest $request) { ->addTextCrumb($title) ->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($form_box); return $this->newPage() From d6bb0d1bfa83a9a25d01dec717f149ecdd8370bb Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 11:49:10 -0700 Subject: [PATCH 123/865] Update people edit pages UI Summary: Updates and clarifies UI Test Plan: New peoples, new bots, new mailing list Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18562 --- .../PhabricatorPeopleCreateController.php | 9 ++------- .../controller/PhabricatorPeopleNewController.php | 15 +++++---------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php index 04b2f4a8a4..c0b232645b 100644 --- a/src/applications/people/controller/PhabricatorPeopleCreateController.php +++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php @@ -92,13 +92,9 @@ public function handleRequest(AphrontRequest $request) { $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-user'); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('User')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setHeaderText($title) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $guidance_context = new PhabricatorPeopleCreateGuidanceContext(); @@ -109,7 +105,6 @@ public function handleRequest(AphrontRequest $request) { ->newInfoView(); $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter( array( $guidance, diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php index 60f9f47b5d..9f52cf567a 100644 --- a/src/applications/people/controller/PhabricatorPeopleNewController.php +++ b/src/applications/people/controller/PhabricatorPeopleNewController.php @@ -132,12 +132,15 @@ public function handleRequest(AphrontRequest $request) { ->setUser($admin); if ($is_bot) { + $title = pht('Create New Bot'); $form->appendRemarkupInstructions( pht('You are creating a new **bot** user account.')); } else if ($is_list) { + $title = pht('Create New Mailing List'); $form->appendRemarkupInstructions( pht('You are creating a new **mailing list** user account.')); } else { + $title = pht('Create New User'); $form->appendRemarkupInstructions( pht('You are creating a new **standard** user account.')); } @@ -205,25 +208,17 @@ public function handleRequest(AphrontRequest $request) { "`bot@yourcompany.com` if you prefer.")); } - - $title = pht('Create New User'); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('User')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-user'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($box); return $this->newPage() From 98185730df8dfa45256d63622d644626f53634ca Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 11:41:23 -0700 Subject: [PATCH 124/865] Update Phlux edit UI Summary: Updates Test Plan: Phlux edit page Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18561 --- .../phlux/controller/PhluxEditController.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/applications/phlux/controller/PhluxEditController.php b/src/applications/phlux/controller/PhluxEditController.php index f37795a754..e2bb7719f7 100644 --- a/src/applications/phlux/controller/PhluxEditController.php +++ b/src/applications/phlux/controller/PhluxEditController.php @@ -154,26 +154,19 @@ public function handleRequest(AphrontRequest $request) { if ($is_new) { $title = pht('Create Variable'); $crumbs->addTextCrumb($title, $request->getRequestURI()); - $header_icon = 'fa-plus-square'; } else { $title = pht('Edit Variable: %s', $key); - $header_icon = 'fa-pencil'; $crumbs->addTextCrumb($title, $request->getRequestURI()); } $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Variable')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $box, )); From 3720b28c6c27919005c22b160cdec899747260b8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 11:20:48 -0700 Subject: [PATCH 125/865] Update slowvote for new edit UI Summary: Updates UI Test Plan: open new poll Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18560 --- .../controller/PhabricatorSlowvoteEditController.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index ebf09ec929..44927fedea 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -269,17 +269,12 @@ public function handleRequest(AphrontRequest $request) { $crumbs->setBorder(true); $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Poll')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($form_box); return $this->newPage() From 8059db894d640946f580ef2af6c0933e2bfd7d9a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 08:33:20 -0700 Subject: [PATCH 126/865] Use the Ferret engine fulltext document table to drive auxiliary fulltext constraints Summary: Ref T12819. I started trying to get individual engines to drive these constraints (e.g., `ManiphestTaskQuery` can do most of the work) but this is a big pain, especially since most engines don't support "any owner" or "no owner", and not everything has an owner, and so on and so on. Going down this path would have meant a huge pile of stub functions everywhere, I think. Instead, drive these through the main engine using the fulltext document table, which already has everything we need to apply these constraints in a uniform way. Also tweak some parts of query construction and result ordering. Test Plan: Searched for documents by author, owner, unowned, any owner, tags, subscribers, fulltext in global search. Got sensible results without any application-specific code. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18550 --- .../DifferentialRevisionFerretEngine.php | 2 +- .../search/ManiphestTaskFerretEngine.php | 2 +- ...abricatorFerretFulltextEngineExtension.php | 32 ++++- .../ferret/PhabricatorFerretDocument.php | 12 ++ .../search/ferret/PhabricatorFerretEngine.php | 56 +-------- ...PhabricatorFerretFulltextStorageEngine.php | 30 +++-- ...PhabricatorCursorPagedPolicyAwareQuery.php | 114 ++++++++++++++++-- 7 files changed, 171 insertions(+), 77 deletions(-) diff --git a/src/applications/differential/search/DifferentialRevisionFerretEngine.php b/src/applications/differential/search/DifferentialRevisionFerretEngine.php index b51a486ef5..ebe59c2780 100644 --- a/src/applications/differential/search/DifferentialRevisionFerretEngine.php +++ b/src/applications/differential/search/DifferentialRevisionFerretEngine.php @@ -15,7 +15,7 @@ public function newFieldObject() { return new DifferentialRevisionFerretField(); } - protected function newSearchEngine() { + public function newSearchEngine() { return new DifferentialRevisionSearchEngine(); } diff --git a/src/applications/maniphest/search/ManiphestTaskFerretEngine.php b/src/applications/maniphest/search/ManiphestTaskFerretEngine.php index 2eab7db74c..7de368402a 100644 --- a/src/applications/maniphest/search/ManiphestTaskFerretEngine.php +++ b/src/applications/maniphest/search/ManiphestTaskFerretEngine.php @@ -15,7 +15,7 @@ public function newFieldObject() { return new ManiphestTaskFerretField(); } - protected function newSearchEngine() { + public function newSearchEngine() { return new ManiphestTaskSearchEngine(); } diff --git a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php index 8d1b6070e6..27694e3a01 100644 --- a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php @@ -23,11 +23,37 @@ public function indexFulltextObject( $phid = $document->getPHID(); $engine = $object->newFerretEngine(); + $is_closed = 0; + $author_phid = null; + $owner_phid = null; + foreach ($document->getRelationshipData() as $relationship) { + list($related_type, $related_phid) = $relationship; + switch ($related_type) { + case PhabricatorSearchRelationship::RELATIONSHIP_OPEN: + $is_closed = 0; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_CLOSED: + $is_closed = 1; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_OWNER: + $owner_phid = $related_phid; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED: + $owner_phid = null; + break; + case PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR: + $author_phid = $related_phid; + break; + } + } + $ferret_document = $engine->newDocumentObject() ->setObjectPHID($phid) - ->setIsClosed(0) - ->setEpochCreated(0) - ->setEpochModified(0); + ->setIsClosed($is_closed) + ->setEpochCreated($document->getDocumentCreated()) + ->setEpochModified($document->getDocumentModified()) + ->setAuthorPHID($author_phid) + ->setOwnerPHID($owner_phid); $stemmer = $engine->newStemmer(); diff --git a/src/applications/search/ferret/PhabricatorFerretDocument.php b/src/applications/search/ferret/PhabricatorFerretDocument.php index fa816c8d17..6f25eb93b6 100644 --- a/src/applications/search/ferret/PhabricatorFerretDocument.php +++ b/src/applications/search/ferret/PhabricatorFerretDocument.php @@ -27,6 +27,18 @@ protected function getConfiguration() { 'columns' => array('objectPHID'), 'unique' => true, ), + 'key_author' => array( + 'columns' => array('authorPHID'), + ), + 'key_owner' => array( + 'columns' => array('ownerPHID'), + ), + 'key_created' => array( + 'columns' => array('epochCreated'), + ), + 'key_modified' => array( + 'columns' => array('epochModified'), + ), ), ) + parent::getConfiguration(); } diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index 7e30189fb8..7b8520ac71 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -5,7 +5,7 @@ abstract class PhabricatorFerretEngine extends Phobject { abstract public function newNgramsObject(); abstract public function newDocumentObject(); abstract public function newFieldObject(); - abstract protected function newSearchEngine(); + abstract public function newSearchEngine(); public function getDefaultFunctionKey() { return 'all'; @@ -70,60 +70,6 @@ public function newStemmer() { return new PhutilSearchStemmer(); } - public function newConfiguredFulltextQuery( - $object, - PhabricatorSavedQuery $query, - PhabricatorUser $viewer) { - - $local_query = new PhabricatorSavedQuery(); - $local_query->setParameter('query', $query->getParameter('query')); - - // TODO: Modularize this piece. - $project_phids = $query->getParameter('projectPHIDs'); - if ($project_phids) { - $local_query->setParameter('projectPHIDs', $project_phids); - } - - $subscriber_phids = $query->getParameter('subscriberPHIDs'); - if ($subscriber_phids) { - $local_query->setParameter('subscriberPHIDs', $subscriber_phids); - } - - $author_phids = $query->getParameter('authorPHIDs'); - if ($author_phids) { - $local_query->setParameter('authorPHIDs', $author_phids); - } - - $owner_phids = $query->getParameter('ownerPHIDs'); - if ($owner_phids) { - $local_query->setParameter('ownerPHIDs', $owner_phids); - } - - $rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; - $rel_closed = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED; - - $statuses = $query->getParameter('statuses'); - if ($statuses) { - $statuses = array_fuse($statuses); - if (count($statuses) == 1) { - if (isset($statuses[$rel_open])) { - $local_query->setParameter('statuses', array('open()')); - } - if (isset($statuses[$rel_closed])) { - $local_query->setParameter('statuses', array('closed()')); - } - } - } - - $search_engine = $this->newSearchEngine() - ->setViewer($viewer); - - $engine_query = $search_engine->buildQueryFromSavedQuery($local_query) - ->setViewer($viewer); - - return $engine_query; - } - public function tokenizeString($value) { $value = trim($value, ' '); $value = preg_split('/ +/', $value); diff --git a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php index 0036ca928b..b67cd7d43c 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php @@ -47,6 +47,8 @@ public function executeSearch(PhabricatorSavedQuery $query) { $offset = (int)$query->getParameter('offset', 0); $limit = (int)$query->getParameter('limit', 25); + // NOTE: For now, it's okay to query with the omnipotent viewer here + // because we're just returning PHIDs which we'll filter later. $viewer = PhabricatorUser::getOmnipotentUser(); $type_results = array(); @@ -54,19 +56,31 @@ public function executeSearch(PhabricatorSavedQuery $query) { $engine = $spec['engine']; $object = $spec['object']; - // NOTE: For now, it's okay to query with the omnipotent viewer here - // because we're just returning PHIDs which we'll filter later. + $local_query = new PhabricatorSavedQuery(); + $local_query->setParameter('query', $query->getParameter('query')); - $type_query = $engine->newConfiguredFulltextQuery( - $object, - $query, - $viewer); + $project_phids = $query->getParameter('projectPHIDs'); + if ($project_phids) { + $local_query->setParameter('projectPHIDs', $project_phids); + } - $type_query + $subscriber_phids = $query->getParameter('subscriberPHIDs'); + if ($subscriber_phids) { + $local_query->setParameter('subscriberPHIDs', $subscriber_phids); + } + + $search_engine = $engine->newSearchEngine() + ->setViewer($viewer); + + $engine_query = $search_engine->buildQueryFromSavedQuery($local_query) + ->setViewer($viewer); + + $engine_query + ->withFerretQuery($engine, $query) ->setOrder('relevance') ->setLimit($offset + $limit); - $results = $type_query->execute(); + $results = $engine_query->execute(); $results = mpull($results, null, 'getPHID'); $type_results[$type] = $results; } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 1411095f36..569f79d824 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -28,8 +28,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery private $spaceIsArchived; private $ngrams = array(); private $ferretEngine; - private $ferretTokens; - private $ferretTables; + private $ferretTokens = array(); + private $ferretTables = array(); + private $ferretQuery; protected function getPageCursors(array $page) { return array( @@ -772,7 +773,7 @@ public function getBuiltinOrders() { if ($this->supportsFerretEngine()) { $orders['relevance'] = array( - 'vector' => array('rank', 'id'), + 'vector' => array('rank', 'fulltext-modified', 'id'), 'name' => pht('Relevence'), ); } @@ -975,6 +976,16 @@ public function getOrderableColumns() { 'column' => '_ft_rank', 'type' => 'int', ); + $columns['fulltext-created'] = array( + 'table' => 'ft_doc', + 'column' => 'epochCreated', + 'type' => 'int', + ); + $columns['fulltext-modified'] = array( + 'table' => 'ft_doc', + 'column' => 'epochModified', + 'type' => 'int', + ); } $cache->setKey($cache_key, $columns); @@ -1406,6 +1417,22 @@ public function supportsFerretEngine() { return ($object instanceof PhabricatorFerretInterface); } + public function withFerretQuery( + PhabricatorFerretEngine $engine, + PhabricatorSavedQuery $query) { + + if (!$this->supportsFerretEngine()) { + throw new Exception( + pht( + 'Query ("%s") does not support the Ferret fulltext engine.', + get_class($this))); + } + + $this->ferretEngine = $engine; + $this->ferretQuery = $query; + + return $this; + } public function withFerretConstraint( PhabricatorFerretEngine $engine, @@ -1538,10 +1565,10 @@ protected function buildFerretSelectClause(AphrontDatabaseConnection $conn) { $table_alias, $stem_value); } - - $parts[] = '0'; } + $parts[] = '0'; + $select[] = qsprintf( $conn, '%Q _ft_rank', @@ -1646,7 +1673,7 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { $joins = array(); $joins[] = qsprintf( $conn, - 'JOIN %T ftdoc ON ftdoc.objectPHID = %Q', + 'JOIN %T ft_doc ON ft_doc.objectPHID = %Q', $document_table->getTableName(), $phid_column); @@ -1655,11 +1682,11 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { $table = $spec['table']; $ngram = $spec['ngram']; - $alias = 'ft'.$idx++; + $alias = 'ftngram_'.$idx++; $joins[] = qsprintf( $conn, - 'JOIN %T %T ON %T.documentID = ftdoc.id AND %T.ngram = %s', + 'JOIN %T %T ON %T.documentID = ft_doc.id AND %T.ngram = %s', $table, $alias, $alias, @@ -1672,7 +1699,7 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { $joins[] = qsprintf( $conn, - 'JOIN %T %T ON ftdoc.id = %T.documentID + 'JOIN %T %T ON ft_doc.id = %T.documentID AND %T.fieldKey = %s', $field_table->getTableName(), $alias, @@ -1801,6 +1828,75 @@ protected function buildFerretWhereClause(AphrontDatabaseConnection $conn) { } } + if ($this->ferretQuery) { + $query = $this->ferretQuery; + + $author_phids = $query->getParameter('authorPHIDs'); + if ($author_phids) { + $where[] = qsprintf( + $conn, + 'ft_doc.authorPHID IN (%Ls)', + $author_phids); + } + + $with_unowned = $query->getParameter('withUnowned'); + $with_any = $query->getParameter('withAnyOwner'); + + if ($with_any && $with_unowned) { + throw new PhabricatorEmptyQueryException( + pht( + 'This query matches only unowned documents owned by anyone, '. + 'which is impossible.')); + } + + $owner_phids = $query->getParameter('ownerPHIDs'); + if ($owner_phids && !$with_any) { + if ($with_unowned) { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IN (%Ls) OR ft_doc.ownerPHID IS NULL', + $owner_phids); + } else { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IN (%Ls)', + $owner_phids); + } + } else if ($with_unowned) { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IS NULL'); + } + + if ($with_any) { + $where[] = qsprintf( + $conn, + 'ft_doc.ownerPHID IS NOT NULL'); + } + + $rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; + + $statuses = $query->getParameter('statuses'); + $is_closed = null; + if ($statuses) { + $statuses = array_fuse($statuses); + if (count($statuses) == 1) { + if (isset($statuses[$rel_open])) { + $is_closed = 0; + } else { + $is_closed = 1; + } + } + } + + if ($is_closed !== null) { + $where[] = qsprintf( + $conn, + 'ft_doc.isClosed = %d', + $is_closed); + } + } + return $where; } From a2a2b3f7f4b590ce211f8e02f8a76060313729cb Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 09:09:39 -0700 Subject: [PATCH 127/865] Sort global fulltext results by overall relevance Summary: Ref T12819. Currently, under the Ferret engine, we query each application's index separately and then aggregate the results. At the moment, results are aggregated by type first, then by actual rank. For example, all the revisions appear first, then all the tasks. Instead, surface the internal ranking data from the underlying query and sort by it. Test Plan: Searched for "A B" with a task named "A B" and a revision named "A". Saw task first. Broadly, saw mixed task and revision order in result sets. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18551 --- src/__phutil_library_map__.php | 2 + .../query/DifferentialRevisionQuery.php | 2 +- .../maniphest/query/ManiphestTaskQuery.php | 1 + .../ferret/PhabricatorFerretMetadata.php | 41 +++++++++++++++++++ ...PhabricatorFerretFulltextStorageEngine.php | 13 ++++++ ...PhabricatorCursorPagedPolicyAwareQuery.php | 35 ++++++++++++++++ 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/applications/search/ferret/PhabricatorFerretMetadata.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e81239f9c0..ce3a86b98c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2840,6 +2840,7 @@ 'PhabricatorFerretFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php', 'PhabricatorFerretFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php', 'PhabricatorFerretInterface' => 'applications/search/ferret/PhabricatorFerretInterface.php', + 'PhabricatorFerretMetadata' => 'applications/search/ferret/PhabricatorFerretMetadata.php', 'PhabricatorFerretNgrams' => 'applications/search/ferret/PhabricatorFerretNgrams.php', 'PhabricatorFerretSearchEngineExtension' => 'applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', @@ -8175,6 +8176,7 @@ 'PhabricatorFerretField' => 'PhabricatorSearchDAO', 'PhabricatorFerretFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorFerretFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', + 'PhabricatorFerretMetadata' => 'Phobject', 'PhabricatorFerretNgrams' => 'PhabricatorSearchDAO', 'PhabricatorFerretSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorFile' => array( diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 622eba564a..8fd91cbd7e 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -320,7 +320,7 @@ public function newResultObject() { */ protected function loadPage() { $data = $this->loadData(); - + $data = $this->didLoadRawRows($data); $table = $this->newResultObject(); return $table->loadAllFromArray($data); } diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index 704a67f548..f009e5c2ef 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -247,6 +247,7 @@ protected function loadPage() { break; } + $data = $this->didLoadRawRows($data); $tasks = $task_dao->loadAllFromArray($data); switch ($this->groupBy) { diff --git a/src/applications/search/ferret/PhabricatorFerretMetadata.php b/src/applications/search/ferret/PhabricatorFerretMetadata.php new file mode 100644 index 0000000000..8ab5e37e45 --- /dev/null +++ b/src/applications/search/ferret/PhabricatorFerretMetadata.php @@ -0,0 +1,41 @@ +engine = $engine; + return $this; + } + + public function getEngine() { + return $this->engine; + } + + public function setPHID($phid) { + $this->phid = $phid; + return $this; + } + + public function getPHID() { + return $this->phid; + } + + public function setRelevance($relevance) { + $this->relevance = $relevance; + return $this; + } + + public function getRelevance() { + return $this->relevance; + } + + public function getRelevanceSortVector() { + return id(new PhutilSortVector()) + ->addInt(-$this->getRelevance()); + } + +} diff --git a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php index b67cd7d43c..74e8771de7 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php @@ -52,6 +52,7 @@ public function executeSearch(PhabricatorSavedQuery $query) { $viewer = PhabricatorUser::getOmnipotentUser(); $type_results = array(); + $metadata = array(); foreach ($type_map as $type => $spec) { $engine = $spec['engine']; $object = $spec['object']; @@ -83,6 +84,8 @@ public function executeSearch(PhabricatorSavedQuery $query) { $results = $engine_query->execute(); $results = mpull($results, null, 'getPHID'); $type_results[$type] = $results; + + $metadata += $engine_query->getFerretMetadata(); } $list = array(); @@ -90,6 +93,16 @@ public function executeSearch(PhabricatorSavedQuery $query) { $list += $results; } + // Currently, the list is grouped by object type. For example, all the + // tasks might be first, then all the revisions, and so on. In each group, + // the results are ordered properly. + + // Reorder the results so that the highest-ranking results come first, + // no matter which object types they belong to. + + $metadata = msort($metadata, 'getRelevanceSortVector'); + $list = array_select_keys($list, array_keys($metadata)) + $list; + $result_slice = array_slice($list, $offset, $limit, true); return array_keys($result_slice); } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 569f79d824..42e6ed7b4e 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -31,6 +31,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery private $ferretTokens = array(); private $ferretTables = array(); private $ferretQuery; + private $ferretMetadata = array(); protected function getPageCursors(array $page) { return array( @@ -82,6 +83,18 @@ final protected function getBeforeID() { return $this->beforeID; } + final public function getFerretMetadata() { + if (!$this->supportsFerretEngine()) { + throw new Exception( + pht( + 'Unable to retrieve Ferret engine metadata, this class ("%s") does '. + 'not support the Ferret engine.', + get_class($this))); + } + + return $this->ferretMetadata; + } + protected function loadStandardPage(PhabricatorLiskDAO $table) { $rows = $this->loadStandardPageRows($table); return $table->loadAllFromArray($rows); @@ -111,6 +124,27 @@ protected function loadStandardPageRowsWithConnection( $this->buildOrderClause($conn), $this->buildLimitClause($conn)); + $rows = $this->didLoadRawRows($rows); + + return $rows; + } + + protected function didLoadRawRows(array $rows) { + if ($this->ferretEngine) { + foreach ($rows as $row) { + $phid = $row['phid']; + + $metadata = id(new PhabricatorFerretMetadata()) + ->setPHID($phid) + ->setEngine($this->ferretEngine) + ->setRelevance(idx($row, '_ft_rank')); + + $this->ferretMetadata[$phid] = $metadata; + + unset($row['_ft_rank']); + } + } + return $rows; } @@ -172,6 +206,7 @@ final protected function didLoadResults(array $results) { if ($this->beforeID) { $results = array_reverse($results, $preserve_keys = true); } + return $results; } From 3ff9d4a4cab39fcd84cb4706139486c0c85218be Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 09:38:19 -0700 Subject: [PATCH 128/865] Support Ferret engine for searching users Summary: Ref T12819. Adds support for indexing user accounts so they appear in global fulltext results. Also, always rank users ahead of other results. Test Plan: Indexed users. Searched for a user, got that user. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18552 --- .../20170907.ferret.01.user.doc.sql | 9 ++++++ .../20170907.ferret.02.user.field.sql | 8 +++++ .../20170907.ferret.03.user.ngrams.sql | 5 ++++ src/__phutil_library_map__.php | 9 ++++++ .../search/PhabricatorUserFerretEngine.php | 29 +++++++++++++++++++ .../people/storage/PhabricatorUser.php | 9 ++++++ .../storage/PhabricatorUserFerretDocument.php | 14 +++++++++ .../storage/PhabricatorUserFerretField.php | 14 +++++++++ .../storage/PhabricatorUserFerretNgrams.php | 14 +++++++++ .../search/ferret/PhabricatorFerretEngine.php | 4 +++ .../ferret/PhabricatorFerretMetadata.php | 3 ++ 11 files changed, 118 insertions(+) create mode 100644 resources/sql/autopatches/20170907.ferret.01.user.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.02.user.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.03.user.ngrams.sql create mode 100644 src/applications/people/search/PhabricatorUserFerretEngine.php create mode 100644 src/applications/people/storage/PhabricatorUserFerretDocument.php create mode 100644 src/applications/people/storage/PhabricatorUserFerretField.php create mode 100644 src/applications/people/storage/PhabricatorUserFerretNgrams.php diff --git a/resources/sql/autopatches/20170907.ferret.01.user.doc.sql b/resources/sql/autopatches/20170907.ferret.01.user.doc.sql new file mode 100644 index 0000000000..39496a0de0 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.01.user.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_user.user_user_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.02.user.field.sql b/resources/sql/autopatches/20170907.ferret.02.user.field.sql new file mode 100644 index 0000000000..3179e58e5b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.02.user.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_user.user_user_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.03.user.ngrams.sql b/resources/sql/autopatches/20170907.ferret.03.user.ngrams.sql new file mode 100644 index 0000000000..2105a7b7af --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.03.user.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_user.user_user_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ce3a86b98c..854a96366e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4257,6 +4257,10 @@ 'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php', 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', 'PhabricatorUserEmailTestCase' => 'applications/people/storage/__tests__/PhabricatorUserEmailTestCase.php', + 'PhabricatorUserFerretDocument' => 'applications/people/storage/PhabricatorUserFerretDocument.php', + 'PhabricatorUserFerretEngine' => 'applications/people/search/PhabricatorUserFerretEngine.php', + 'PhabricatorUserFerretField' => 'applications/people/storage/PhabricatorUserFerretField.php', + 'PhabricatorUserFerretNgrams' => 'applications/people/storage/PhabricatorUserFerretNgrams.php', 'PhabricatorUserFulltextEngine' => 'applications/people/search/PhabricatorUserFulltextEngine.php', 'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', @@ -9840,6 +9844,7 @@ 'PhabricatorFlaggableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', ), 'PhabricatorUserBadgesCacheType' => 'PhabricatorUserCacheType', @@ -9862,6 +9867,10 @@ 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase', + 'PhabricatorUserFerretDocument' => 'PhabricatorFerretDocument', + 'PhabricatorUserFerretEngine' => 'PhabricatorFerretEngine', + 'PhabricatorUserFerretField' => 'PhabricatorFerretField', + 'PhabricatorUserFerretNgrams' => 'PhabricatorFerretNgrams', 'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorUserIconField' => 'PhabricatorUserCustomField', 'PhabricatorUserLog' => array( diff --git a/src/applications/people/search/PhabricatorUserFerretEngine.php b/src/applications/people/search/PhabricatorUserFerretEngine.php new file mode 100644 index 0000000000..1f44bf4289 --- /dev/null +++ b/src/applications/people/search/PhabricatorUserFerretEngine.php @@ -0,0 +1,29 @@ +getEngine(); + return id(new PhutilSortVector()) + ->addInt($engine->getObjectTypeRelevance()) ->addInt(-$this->getRelevance()); } From 60deec36d8ed922938223f7771c21c62f5689d88 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 09:53:15 -0700 Subject: [PATCH 129/865] Lightly modernize FundInitiativeQuery Summary: Ref T12819. Prepares Fund to move to Ferret. Test Plan: Searched for initiatives in Fund. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18553 --- .../fund/query/FundInitiativeQuery.php | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/applications/fund/query/FundInitiativeQuery.php b/src/applications/fund/query/FundInitiativeQuery.php index 66f87a883c..9efbcfbf27 100644 --- a/src/applications/fund/query/FundInitiativeQuery.php +++ b/src/applications/fund/query/FundInitiativeQuery.php @@ -35,19 +35,12 @@ public function needProjectPHIDs($need) { return $this; } + public function newResultObject() { + return new FundInitiative(); + } + protected function loadPage() { - $table = new FundInitiative(); - $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); + return $this->loadStandardPage($this->newResultObject()); } protected function didFilterPage(array $initiatives) { @@ -73,40 +66,38 @@ protected function didFilterPage(array $initiatives) { return $initiatives; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); - - $where[] = $this->buildPagingClause($conn_r); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->ownerPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'ownerPHID IN (%Ls)', $this->ownerPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'status IN (%Ls)', $this->statuses); } - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { From cf0bc32e18a9aac85ccfdda74c36b040cbf4fe74 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 10:05:48 -0700 Subject: [PATCH 130/865] Lightly modernize FundInitiativeSearchEngine Summary: Ref T12819. Prepares for Ferret engine support. Test Plan: Queried for various initiatives. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18554 --- .../fund/query/FundInitiativeQuery.php | 30 ----- .../fund/query/FundInitiativeSearchEngine.php | 122 +++++++----------- 2 files changed, 50 insertions(+), 102 deletions(-) diff --git a/src/applications/fund/query/FundInitiativeQuery.php b/src/applications/fund/query/FundInitiativeQuery.php index 9efbcfbf27..aeeb0942b9 100644 --- a/src/applications/fund/query/FundInitiativeQuery.php +++ b/src/applications/fund/query/FundInitiativeQuery.php @@ -8,8 +8,6 @@ final class FundInitiativeQuery private $ownerPHIDs; private $statuses; - private $needProjectPHIDs; - public function withIDs(array $ids) { $this->ids = $ids; return $this; @@ -30,11 +28,6 @@ public function withStatuses(array $statuses) { return $this; } - public function needProjectPHIDs($need) { - $this->needProjectPHIDs = $need; - return $this; - } - public function newResultObject() { return new FundInitiative(); } @@ -43,29 +36,6 @@ protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } - protected function didFilterPage(array $initiatives) { - - if ($this->needProjectPHIDs) { - $edge_query = id(new PhabricatorEdgeQuery()) - ->withSourcePHIDs(mpull($initiatives, 'getPHID')) - ->withEdgeTypes( - array( - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, - )); - $edge_query->execute(); - - foreach ($initiatives as $initiative) { - $phids = $edge_query->getDestinationPHIDs( - array( - $initiative->getPHID(), - )); - $initiative->attachProjectPHIDs($phids); - } - } - - return $initiatives; - } - protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); diff --git a/src/applications/fund/query/FundInitiativeSearchEngine.php b/src/applications/fund/query/FundInitiativeSearchEngine.php index 3c339c1a64..e075fe5063 100644 --- a/src/applications/fund/query/FundInitiativeSearchEngine.php +++ b/src/applications/fund/query/FundInitiativeSearchEngine.php @@ -11,67 +11,37 @@ public function getApplicationClassName() { return 'PhabricatorFundApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'ownerPHIDs', - $this->readUsersFromRequest($request, 'owners')); - - $saved->setParameter( - 'statuses', - $this->readListFromRequest($request, 'statuses')); + public function newQuery() { + return new FundInitiativeQuery(); + } - return $saved; + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setKey('ownerPHIDs') + ->setAliases(array('owner', 'ownerPHID', 'owners')) + ->setLabel(pht('Owners')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Statuses')) + ->setOptions(FundInitiative::getStatusNameMap()), + ); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new FundInitiativeQuery()) - ->needProjectPHIDs(true); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $owner_phids = $saved->getParameter('ownerPHIDs'); - if ($owner_phids) { - $query->withOwnerPHIDs($owner_phids); + if ($map['ownerPHIDs']) { + $query->withOwnerPHIDs($map['ownerPHIDs']); } - $statuses = $saved->getParameter('statuses'); - if ($statuses) { - $query->withStatuses($statuses); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $statuses = $saved->getParameter('statuses', array()); - $statuses = array_fuse($statuses); - - $owner_phids = $saved->getParameter('ownerPHIDs', array()); - - $status_map = FundInitiative::getStatusNameMap(); - $status_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Statuses')); - foreach ($status_map as $status => $name) { - $status_control->addCheckbox( - 'statuses[]', - $status, - $name, - isset($statuses[$status])); - } - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Owners')) - ->setName('owners') - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setValue($owner_phids)) - ->appendChild($status_control); - } - protected function getURI($path) { return '/fund/'.$path; } @@ -112,21 +82,6 @@ public function buildSavedQueryFromBuiltin($query_key) { return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $initiatives, - PhabricatorSavedQuery $query) { - - $phids = array(); - foreach ($initiatives as $initiative) { - $phids[] = $initiative->getOwnerPHID(); - foreach ($initiative->getProjectPHIDs() as $project_phid) { - $phids[] = $project_phid; - } - } - - return $phids; - } - protected function renderResultList( array $initiatives, PhabricatorSavedQuery $query, @@ -135,7 +90,30 @@ protected function renderResultList( $viewer = $this->requireViewer(); - $list = id(new PHUIObjectItemListView()); + $load_phids = array(); + foreach ($initiatives as $initiative) { + $load_phids[] = $initiative->getOwnerPHID(); + } + + if ($initiatives) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($initiatives, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + + $edge_query->execute(); + + foreach ($edge_query->getDestinationPHIDs() as $phid) { + $load_phids[] = $phid; + } + } + + $handles = $viewer->loadHandles($load_phids); + $handles = iterator_to_array($handles); + + $list = new PHUIObjectItemListView(); foreach ($initiatives as $initiative) { $owner_handle = $handles[$initiative->getOwnerPHID()]; @@ -149,9 +127,12 @@ protected function renderResultList( $item->setDisabled(true); } - $project_handles = array_select_keys( - $handles, - $initiative->getProjectPHIDs()); + $project_phids = $edge_query->getDestinationPHIDs( + array( + $initiative->getPHID(), + )); + + $project_handles = array_select_keys($handles, $project_phids); if ($project_handles) { $item->addAttribute( id(new PHUIHandleTagListView()) @@ -168,9 +149,6 @@ protected function renderResultList( $result->setNoDataString(pht('No initiatives found.')); return $result; - - - return $list; } } From f23717b416fe08e579752ca361ee18b612822812 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 10:20:31 -0700 Subject: [PATCH 131/865] Support Ferret engine in Fund initiatives Summary: Ref T12819. Adds Ferret engine support to initiatives. Test Plan: Indexed and searched for initiatives. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18555 --- .../20170907.ferret.04.fund.doc.sql | 9 ++++++++ .../20170907.ferret.05.fund.field.sql | 8 +++++++ .../20170907.ferret.06.fund.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 9 ++++++++ .../fund/query/FundInitiativeQuery.php | 12 ++++++---- .../search/FundInitiativeFerretEngine.php | 22 +++++++++++++++++++ .../fund/storage/FundInitiative.php | 11 +++++++++- .../storage/FundInitiativeFerretDocument.php | 14 ++++++++++++ .../storage/FundInitiativeFerretField.php | 14 ++++++++++++ .../storage/FundInitiativeFerretNgrams.php | 14 ++++++++++++ 10 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 resources/sql/autopatches/20170907.ferret.04.fund.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.05.fund.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.06.fund.ngrams.sql create mode 100644 src/applications/fund/search/FundInitiativeFerretEngine.php create mode 100644 src/applications/fund/storage/FundInitiativeFerretDocument.php create mode 100644 src/applications/fund/storage/FundInitiativeFerretField.php create mode 100644 src/applications/fund/storage/FundInitiativeFerretNgrams.php diff --git a/resources/sql/autopatches/20170907.ferret.04.fund.doc.sql b/resources/sql/autopatches/20170907.ferret.04.fund.doc.sql new file mode 100644 index 0000000000..a7f8324594 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.04.fund.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiative_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.05.fund.field.sql b/resources/sql/autopatches/20170907.ferret.05.fund.field.sql new file mode 100644 index 0000000000..b8c544c2a7 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.05.fund.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiative_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.06.fund.ngrams.sql b/resources/sql/autopatches/20170907.ferret.06.fund.ngrams.sql new file mode 100644 index 0000000000..a509087bae --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.06.fund.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiative_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 854a96366e..2d415173bb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1153,6 +1153,10 @@ 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditEngine' => 'applications/fund/editor/FundInitiativeEditEngine.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', + 'FundInitiativeFerretDocument' => 'applications/fund/storage/FundInitiativeFerretDocument.php', + 'FundInitiativeFerretEngine' => 'applications/fund/search/FundInitiativeFerretEngine.php', + 'FundInitiativeFerretField' => 'applications/fund/storage/FundInitiativeFerretField.php', + 'FundInitiativeFerretNgrams' => 'applications/fund/storage/FundInitiativeFerretNgrams.php', 'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', 'FundInitiativeMerchantTransaction' => 'applications/fund/xaction/FundInitiativeMerchantTransaction.php', @@ -6231,6 +6235,7 @@ 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'FundInitiativeBackController' => 'FundController', 'FundInitiativeBackerTransaction' => 'FundInitiativeTransactionType', @@ -6239,6 +6244,10 @@ 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditEngine' => 'PhabricatorEditEngine', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', + 'FundInitiativeFerretDocument' => 'PhabricatorFerretDocument', + 'FundInitiativeFerretEngine' => 'PhabricatorFerretEngine', + 'FundInitiativeFerretField' => 'PhabricatorFerretField', + 'FundInitiativeFerretNgrams' => 'PhabricatorFerretNgrams', 'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine', 'FundInitiativeListController' => 'FundController', 'FundInitiativeMerchantTransaction' => 'FundInitiativeTransactionType', diff --git a/src/applications/fund/query/FundInitiativeQuery.php b/src/applications/fund/query/FundInitiativeQuery.php index aeeb0942b9..bbb4ef2746 100644 --- a/src/applications/fund/query/FundInitiativeQuery.php +++ b/src/applications/fund/query/FundInitiativeQuery.php @@ -42,28 +42,28 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'i.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'i.phid IN (%Ls)', $this->phids); } if ($this->ownerPHIDs !== null) { $where[] = qsprintf( $conn, - 'ownerPHID IN (%Ls)', + 'i.ownerPHID IN (%Ls)', $this->ownerPHIDs); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'i.status IN (%Ls)', $this->statuses); } @@ -74,4 +74,8 @@ public function getQueryApplicationClass() { return 'PhabricatorFundApplication'; } + protected function getPrimaryTableAlias() { + return 'i'; + } + } diff --git a/src/applications/fund/search/FundInitiativeFerretEngine.php b/src/applications/fund/search/FundInitiativeFerretEngine.php new file mode 100644 index 0000000000..fd9ce1313d --- /dev/null +++ b/src/applications/fund/search/FundInitiativeFerretEngine.php @@ -0,0 +1,22 @@ + Date: Thu, 7 Sep 2017 10:27:10 -0700 Subject: [PATCH 132/865] Support Ferret engine for Passphrase credentials Summary: Ref T12819. Adds Ferret support to Passphrase. Test Plan: Indexed credentials, searched for credentials. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18556 --- .../20170907.ferret.07.passphrase.doc.sql | 9 ++++++++ .../20170907.ferret.08.passphrase.field.sql | 8 +++++++ .../20170907.ferret.09.passphrase.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 9 ++++++++ .../query/PassphraseCredentialQuery.php | 18 +++++++++------ .../PassphraseCredentialFerretEngine.php | 22 +++++++++++++++++++ .../storage/PassphraseCredential.php | 11 +++++++++- .../PassphraseCredentialFerretDocument.php | 14 ++++++++++++ .../PassphraseCredentialFerretField.php | 14 ++++++++++++ .../PassphraseCredentialFerretNgrams.php | 14 ++++++++++++ 10 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 resources/sql/autopatches/20170907.ferret.07.passphrase.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.08.passphrase.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.09.passphrase.ngrams.sql create mode 100644 src/applications/passphrase/search/PassphraseCredentialFerretEngine.php create mode 100644 src/applications/passphrase/storage/PassphraseCredentialFerretDocument.php create mode 100644 src/applications/passphrase/storage/PassphraseCredentialFerretField.php create mode 100644 src/applications/passphrase/storage/PassphraseCredentialFerretNgrams.php diff --git a/resources/sql/autopatches/20170907.ferret.07.passphrase.doc.sql b/resources/sql/autopatches/20170907.ferret.07.passphrase.doc.sql new file mode 100644 index 0000000000..6787528d0e --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.07.passphrase.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.08.passphrase.field.sql b/resources/sql/autopatches/20170907.ferret.08.passphrase.field.sql new file mode 100644 index 0000000000..6dc62d477e --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.08.passphrase.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.09.passphrase.ngrams.sql b/resources/sql/autopatches/20170907.ferret.09.passphrase.ngrams.sql new file mode 100644 index 0000000000..2b64beb7ed --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.09.passphrase.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2d415173bb..3e498d6273 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1840,6 +1840,10 @@ 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', 'PassphraseCredentialDestroyTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', + 'PassphraseCredentialFerretDocument' => 'applications/passphrase/storage/PassphraseCredentialFerretDocument.php', + 'PassphraseCredentialFerretEngine' => 'applications/passphrase/search/PassphraseCredentialFerretEngine.php', + 'PassphraseCredentialFerretField' => 'applications/passphrase/storage/PassphraseCredentialFerretField.php', + 'PassphraseCredentialFerretNgrams' => 'applications/passphrase/storage/PassphraseCredentialFerretNgrams.php', 'PassphraseCredentialFulltextEngine' => 'applications/passphrase/search/PassphraseCredentialFulltextEngine.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php', @@ -7034,6 +7038,7 @@ 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PassphraseCredentialAuthorPolicyRule' => 'PhabricatorPolicyRule', 'PassphraseCredentialConduitController' => 'PassphraseController', @@ -7044,6 +7049,10 @@ 'PassphraseCredentialDestroyController' => 'PassphraseController', 'PassphraseCredentialDestroyTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialEditController' => 'PassphraseController', + 'PassphraseCredentialFerretDocument' => 'PhabricatorFerretDocument', + 'PassphraseCredentialFerretEngine' => 'PhabricatorFerretEngine', + 'PassphraseCredentialFerretField' => 'PhabricatorFerretField', + 'PassphraseCredentialFerretNgrams' => 'PhabricatorFerretNgrams', 'PassphraseCredentialFulltextEngine' => 'PhabricatorFulltextEngine', 'PassphraseCredentialListController' => 'PassphraseController', 'PassphraseCredentialLockController' => 'PassphraseController', diff --git a/src/applications/passphrase/query/PassphraseCredentialQuery.php b/src/applications/passphrase/query/PassphraseCredentialQuery.php index 3a78ac0d13..2518ad44e5 100644 --- a/src/applications/passphrase/query/PassphraseCredentialQuery.php +++ b/src/applications/passphrase/query/PassphraseCredentialQuery.php @@ -109,49 +109,49 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'c.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'c.phid IN (%Ls)', $this->phids); } if ($this->credentialTypes !== null) { $where[] = qsprintf( $conn, - 'credentialType in (%Ls)', + 'c.credentialType in (%Ls)', $this->credentialTypes); } if ($this->providesTypes !== null) { $where[] = qsprintf( $conn, - 'providesType IN (%Ls)', + 'c.providesType IN (%Ls)', $this->providesTypes); } if ($this->isDestroyed !== null) { $where[] = qsprintf( $conn, - 'isDestroyed = %d', + 'c.isDestroyed = %d', (int)$this->isDestroyed); } if ($this->allowConduit !== null) { $where[] = qsprintf( $conn, - 'allowConduit = %d', + 'c.allowConduit = %d', (int)$this->allowConduit); } if (strlen($this->nameContains)) { $where[] = qsprintf( $conn, - 'LOWER(name) LIKE %~', + 'LOWER(c.name) LIKE %~', phutil_utf8_strtolower($this->nameContains)); } @@ -162,4 +162,8 @@ public function getQueryApplicationClass() { return 'PhabricatorPassphraseApplication'; } + protected function getPrimaryTableAlias() { + return 'c'; + } + } diff --git a/src/applications/passphrase/search/PassphraseCredentialFerretEngine.php b/src/applications/passphrase/search/PassphraseCredentialFerretEngine.php new file mode 100644 index 0000000000..3caba16371 --- /dev/null +++ b/src/applications/passphrase/search/PassphraseCredentialFerretEngine.php @@ -0,0 +1,22 @@ + Date: Thu, 7 Sep 2017 11:03:39 -0700 Subject: [PATCH 133/865] Reduce the amount of boilerplate that implementing FerretInterface requires Summary: See brief discussion in D18554. All the index tables are the same for every application (and, at this point, seem unlikely to change) and we never actually pass these objects around (they're only used internally). In some other cases (like Transactions) not every application has the same tables (for example, Differential has extra field for inline comments), and/or we pass the objects around (lots of stuff uses `$xactions` directly). However, in this case, and in Edges, we don't interact with any representation of the database state directly in much of the code, and it doesn't change from application to application. Just automatically define document, field, and ngram tables for anything which implements `FerretInterface`. This makes the query and index logic a tiny bit messier but lets us delete a ton of boilerplate classes. Test Plan: Indexed objects, searched for objects. Same results as before with much less code. Ran `bin/storage upgrade`, got a clean bill of health. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18559 --- src/__phutil_library_map__.php | 36 ------ .../PhabricatorConfigCoreSchemaSpec.php | 8 ++ .../schema/PhabricatorConfigSchemaSpec.php | 20 ++++ .../DifferentialRevisionFerretEngine.php | 12 +- .../DifferentialRevisionFerretDocument.php | 14 --- .../DifferentialRevisionFerretField.php | 14 --- .../DifferentialRevisionFerretNgrams.php | 14 --- .../search/FundInitiativeFerretEngine.php | 12 +- .../storage/FundInitiativeFerretDocument.php | 14 --- .../storage/FundInitiativeFerretField.php | 14 --- .../storage/FundInitiativeFerretNgrams.php | 14 --- .../search/ManiphestTaskFerretEngine.php | 12 +- .../storage/ManiphestTaskFerretDocument.php | 14 --- .../storage/ManiphestTaskFerretField.php | 14 --- .../storage/ManiphestTaskFerretNgrams.php | 14 --- .../PassphraseCredentialFerretEngine.php | 12 +- .../PassphraseCredentialFerretDocument.php | 14 --- .../PassphraseCredentialFerretField.php | 14 --- .../PassphraseCredentialFerretNgrams.php | 14 --- .../search/PhabricatorUserFerretEngine.php | 12 +- .../storage/PhabricatorUserFerretDocument.php | 14 --- .../storage/PhabricatorUserFerretField.php | 14 --- .../storage/PhabricatorUserFerretNgrams.php | 14 --- ...abricatorFerretFulltextEngineExtension.php | 79 ++++++------ .../ferret/PhabricatorFerretDocument.php | 52 -------- .../search/ferret/PhabricatorFerretEngine.php | 112 +++++++++++++++++- .../search/ferret/PhabricatorFerretField.php | 39 ------ .../search/ferret/PhabricatorFerretNgrams.php | 35 ------ ...PhabricatorCursorPagedPolicyAwareQuery.php | 13 +- 29 files changed, 208 insertions(+), 456 deletions(-) delete mode 100644 src/applications/differential/storage/DifferentialRevisionFerretDocument.php delete mode 100644 src/applications/differential/storage/DifferentialRevisionFerretField.php delete mode 100644 src/applications/differential/storage/DifferentialRevisionFerretNgrams.php delete mode 100644 src/applications/fund/storage/FundInitiativeFerretDocument.php delete mode 100644 src/applications/fund/storage/FundInitiativeFerretField.php delete mode 100644 src/applications/fund/storage/FundInitiativeFerretNgrams.php delete mode 100644 src/applications/maniphest/storage/ManiphestTaskFerretDocument.php delete mode 100644 src/applications/maniphest/storage/ManiphestTaskFerretField.php delete mode 100644 src/applications/maniphest/storage/ManiphestTaskFerretNgrams.php delete mode 100644 src/applications/passphrase/storage/PassphraseCredentialFerretDocument.php delete mode 100644 src/applications/passphrase/storage/PassphraseCredentialFerretField.php delete mode 100644 src/applications/passphrase/storage/PassphraseCredentialFerretNgrams.php delete mode 100644 src/applications/people/storage/PhabricatorUserFerretDocument.php delete mode 100644 src/applications/people/storage/PhabricatorUserFerretField.php delete mode 100644 src/applications/people/storage/PhabricatorUserFerretNgrams.php delete mode 100644 src/applications/search/ferret/PhabricatorFerretDocument.php delete mode 100644 src/applications/search/ferret/PhabricatorFerretField.php delete mode 100644 src/applications/search/ferret/PhabricatorFerretNgrams.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3e498d6273..1ffe418b5c 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -532,10 +532,7 @@ 'DifferentialRevisionEditConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionEditConduitAPIMethod.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 'DifferentialRevisionEditEngine' => 'applications/differential/editor/DifferentialRevisionEditEngine.php', - 'DifferentialRevisionFerretDocument' => 'applications/differential/storage/DifferentialRevisionFerretDocument.php', 'DifferentialRevisionFerretEngine' => 'applications/differential/search/DifferentialRevisionFerretEngine.php', - 'DifferentialRevisionFerretField' => 'applications/differential/storage/DifferentialRevisionFerretField.php', - 'DifferentialRevisionFerretNgrams' => 'applications/differential/storage/DifferentialRevisionFerretNgrams.php', 'DifferentialRevisionFulltextEngine' => 'applications/differential/search/DifferentialRevisionFulltextEngine.php', 'DifferentialRevisionGraph' => 'infrastructure/graph/DifferentialRevisionGraph.php', 'DifferentialRevisionHasChildRelationship' => 'applications/differential/relationships/DifferentialRevisionHasChildRelationship.php', @@ -1153,10 +1150,7 @@ 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditEngine' => 'applications/fund/editor/FundInitiativeEditEngine.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', - 'FundInitiativeFerretDocument' => 'applications/fund/storage/FundInitiativeFerretDocument.php', 'FundInitiativeFerretEngine' => 'applications/fund/search/FundInitiativeFerretEngine.php', - 'FundInitiativeFerretField' => 'applications/fund/storage/FundInitiativeFerretField.php', - 'FundInitiativeFerretNgrams' => 'applications/fund/storage/FundInitiativeFerretNgrams.php', 'FundInitiativeFulltextEngine' => 'applications/fund/search/FundInitiativeFulltextEngine.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', 'FundInitiativeMerchantTransaction' => 'applications/fund/xaction/FundInitiativeMerchantTransaction.php', @@ -1539,10 +1533,7 @@ 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php', - 'ManiphestTaskFerretDocument' => 'applications/maniphest/storage/ManiphestTaskFerretDocument.php', 'ManiphestTaskFerretEngine' => 'applications/maniphest/search/ManiphestTaskFerretEngine.php', - 'ManiphestTaskFerretField' => 'applications/maniphest/storage/ManiphestTaskFerretField.php', - 'ManiphestTaskFerretNgrams' => 'applications/maniphest/storage/ManiphestTaskFerretNgrams.php', 'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php', 'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php', 'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php', @@ -1840,10 +1831,7 @@ 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', 'PassphraseCredentialDestroyTransaction' => 'applications/passphrase/xaction/PassphraseCredentialDestroyTransaction.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', - 'PassphraseCredentialFerretDocument' => 'applications/passphrase/storage/PassphraseCredentialFerretDocument.php', 'PassphraseCredentialFerretEngine' => 'applications/passphrase/search/PassphraseCredentialFerretEngine.php', - 'PassphraseCredentialFerretField' => 'applications/passphrase/storage/PassphraseCredentialFerretField.php', - 'PassphraseCredentialFerretNgrams' => 'applications/passphrase/storage/PassphraseCredentialFerretNgrams.php', 'PassphraseCredentialFulltextEngine' => 'applications/passphrase/search/PassphraseCredentialFulltextEngine.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php', @@ -2841,15 +2829,12 @@ 'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php', 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php', - 'PhabricatorFerretDocument' => 'applications/search/ferret/PhabricatorFerretDocument.php', 'PhabricatorFerretEngine' => 'applications/search/ferret/PhabricatorFerretEngine.php', 'PhabricatorFerretEngineTestCase' => 'applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php', - 'PhabricatorFerretField' => 'applications/search/ferret/PhabricatorFerretField.php', 'PhabricatorFerretFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php', 'PhabricatorFerretFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php', 'PhabricatorFerretInterface' => 'applications/search/ferret/PhabricatorFerretInterface.php', 'PhabricatorFerretMetadata' => 'applications/search/ferret/PhabricatorFerretMetadata.php', - 'PhabricatorFerretNgrams' => 'applications/search/ferret/PhabricatorFerretNgrams.php', 'PhabricatorFerretSearchEngineExtension' => 'applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', 'PhabricatorFileAES256StorageFormat' => 'applications/files/format/PhabricatorFileAES256StorageFormat.php', @@ -4265,10 +4250,7 @@ 'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php', 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', 'PhabricatorUserEmailTestCase' => 'applications/people/storage/__tests__/PhabricatorUserEmailTestCase.php', - 'PhabricatorUserFerretDocument' => 'applications/people/storage/PhabricatorUserFerretDocument.php', 'PhabricatorUserFerretEngine' => 'applications/people/search/PhabricatorUserFerretEngine.php', - 'PhabricatorUserFerretField' => 'applications/people/storage/PhabricatorUserFerretField.php', - 'PhabricatorUserFerretNgrams' => 'applications/people/storage/PhabricatorUserFerretNgrams.php', 'PhabricatorUserFulltextEngine' => 'applications/people/search/PhabricatorUserFulltextEngine.php', 'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', @@ -5564,10 +5546,7 @@ 'DifferentialRevisionEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionEditEngine' => 'PhabricatorEditEngine', - 'DifferentialRevisionFerretDocument' => 'PhabricatorFerretDocument', 'DifferentialRevisionFerretEngine' => 'PhabricatorFerretEngine', - 'DifferentialRevisionFerretField' => 'PhabricatorFerretField', - 'DifferentialRevisionFerretNgrams' => 'PhabricatorFerretNgrams', 'DifferentialRevisionFulltextEngine' => 'PhabricatorFulltextEngine', 'DifferentialRevisionGraph' => 'PhabricatorObjectGraph', 'DifferentialRevisionHasChildRelationship' => 'DifferentialRevisionRelationship', @@ -6248,10 +6227,7 @@ 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditEngine' => 'PhabricatorEditEngine', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', - 'FundInitiativeFerretDocument' => 'PhabricatorFerretDocument', 'FundInitiativeFerretEngine' => 'PhabricatorFerretEngine', - 'FundInitiativeFerretField' => 'PhabricatorFerretField', - 'FundInitiativeFerretNgrams' => 'PhabricatorFerretNgrams', 'FundInitiativeFulltextEngine' => 'PhabricatorFulltextEngine', 'FundInitiativeListController' => 'FundController', 'FundInitiativeMerchantTransaction' => 'FundInitiativeTransactionType', @@ -6719,10 +6695,7 @@ 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock', - 'ManiphestTaskFerretDocument' => 'PhabricatorFerretDocument', 'ManiphestTaskFerretEngine' => 'PhabricatorFerretEngine', - 'ManiphestTaskFerretField' => 'PhabricatorFerretField', - 'ManiphestTaskFerretNgrams' => 'PhabricatorFerretNgrams', 'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine', 'ManiphestTaskGraph' => 'PhabricatorObjectGraph', 'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType', @@ -7049,10 +7022,7 @@ 'PassphraseCredentialDestroyController' => 'PassphraseController', 'PassphraseCredentialDestroyTransaction' => 'PassphraseCredentialTransactionType', 'PassphraseCredentialEditController' => 'PassphraseController', - 'PassphraseCredentialFerretDocument' => 'PhabricatorFerretDocument', 'PassphraseCredentialFerretEngine' => 'PhabricatorFerretEngine', - 'PassphraseCredentialFerretField' => 'PhabricatorFerretField', - 'PassphraseCredentialFerretNgrams' => 'PhabricatorFerretNgrams', 'PassphraseCredentialFulltextEngine' => 'PhabricatorFulltextEngine', 'PassphraseCredentialListController' => 'PassphraseController', 'PassphraseCredentialLockController' => 'PassphraseController', @@ -8192,14 +8162,11 @@ 'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryPublisher' => 'Phobject', 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', - 'PhabricatorFerretDocument' => 'PhabricatorSearchDAO', 'PhabricatorFerretEngine' => 'Phobject', 'PhabricatorFerretEngineTestCase' => 'PhabricatorTestCase', - 'PhabricatorFerretField' => 'PhabricatorSearchDAO', 'PhabricatorFerretFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorFerretFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', 'PhabricatorFerretMetadata' => 'Phobject', - 'PhabricatorFerretNgrams' => 'PhabricatorSearchDAO', 'PhabricatorFerretSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorFile' => array( 'PhabricatorFileDAO', @@ -9885,10 +9852,7 @@ 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase', - 'PhabricatorUserFerretDocument' => 'PhabricatorFerretDocument', 'PhabricatorUserFerretEngine' => 'PhabricatorFerretEngine', - 'PhabricatorUserFerretField' => 'PhabricatorFerretField', - 'PhabricatorUserFerretNgrams' => 'PhabricatorFerretNgrams', 'PhabricatorUserFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorUserIconField' => 'PhabricatorUserCustomField', 'PhabricatorUserLog' => array( diff --git a/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php index 4219ad2cb4..0d8d743649 100644 --- a/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigCoreSchemaSpec.php @@ -42,5 +42,13 @@ public function buildSchemata() { )); } + $ferret_objects = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorFerretInterface') + ->execute(); + + foreach ($ferret_objects as $ferret_object) { + $engine = $ferret_object->newFerretEngine(); + $this->buildFerretIndexSchema($engine); + } } } diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index 49a99ab03a..b24adf27cd 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -55,6 +55,26 @@ protected function buildLiskObjectSchema(PhabricatorLiskDAO $object) { $object->getSchemaKeys()); } + protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) { + $this->buildRawSchema( + $engine->getApplicationName(), + $engine->getDocumentTableName(), + $engine->getDocumentSchemaColumns(), + $engine->getDocumentSchemaKeys()); + + $this->buildRawSchema( + $engine->getApplicationName(), + $engine->getFieldTableName(), + $engine->getFieldSchemaColumns(), + $engine->getFieldSchemaKeys()); + + $this->buildRawSchema( + $engine->getApplicationName(), + $engine->getNgramsTableName(), + $engine->getNgramsSchemaColumns(), + $engine->getNgramsSchemaKeys()); + } + protected function buildRawSchema( $database_name, $table_name, diff --git a/src/applications/differential/search/DifferentialRevisionFerretEngine.php b/src/applications/differential/search/DifferentialRevisionFerretEngine.php index ebe59c2780..2fc7d5905e 100644 --- a/src/applications/differential/search/DifferentialRevisionFerretEngine.php +++ b/src/applications/differential/search/DifferentialRevisionFerretEngine.php @@ -3,16 +3,12 @@ final class DifferentialRevisionFerretEngine extends PhabricatorFerretEngine { - public function newNgramsObject() { - return new DifferentialRevisionFerretNgrams(); + public function getApplicationName() { + return 'differential'; } - public function newDocumentObject() { - return new DifferentialRevisionFerretDocument(); - } - - public function newFieldObject() { - return new DifferentialRevisionFerretField(); + public function getScopeName() { + return 'revision'; } public function newSearchEngine() { diff --git a/src/applications/differential/storage/DifferentialRevisionFerretDocument.php b/src/applications/differential/storage/DifferentialRevisionFerretDocument.php deleted file mode 100644 index ae8571f625..0000000000 --- a/src/applications/differential/storage/DifferentialRevisionFerretDocument.php +++ /dev/null @@ -1,14 +0,0 @@ -newDocumentObject() - ->setObjectPHID($phid) - ->setIsClosed($is_closed) - ->setEpochCreated($document->getDocumentCreated()) - ->setEpochModified($document->getDocumentModified()) - ->setAuthorPHID($author_phid) - ->setOwnerPHID($owner_phid); - $stemmer = $engine->newStemmer(); // Copy all of the "title" and "body" fields to create new "core" fields. @@ -133,33 +125,49 @@ public function indexFulltextObject( $ngrams_source[] = $term_corpus; } - $ferret_fields[] = $engine->newFieldObject() - ->setFieldKey($key) - ->setRawCorpus($raw_corpus) - ->setTermCorpus($term_corpus) - ->setNormalCorpus($normal_corpus); + $ferret_fields[] = array( + 'fieldKey' => $key, + 'rawCorpus' => $raw_corpus, + 'termCorpus' => $term_corpus, + 'normalCorpus' => $normal_corpus, + ); } $ngrams_source = implode("\n", $ngrams_source); $ngrams = $engine->getTermNgramsFromString($ngrams_source); - $ferret_document->openTransaction(); + $object->openTransaction(); try { + $conn = $object->establishConnection('w'); $this->deleteOldDocument($engine, $object, $document); - $ferret_document->save(); - - $document_id = $ferret_document->getID(); + queryfx( + $conn, + 'INSERT INTO %T (objectPHID, isClosed, epochCreated, epochModified, + authorPHID, ownerPHID) VALUES (%s, %d, %d, %d, %ns, %ns)', + $engine->getDocumentTableName(), + $object->getPHID(), + $is_closed, + $document->getDocumentCreated(), + $document->getDocumentModified(), + $author_phid, + $owner_phid); + + $document_id = $conn->getInsertID(); foreach ($ferret_fields as $ferret_field) { - $ferret_field - ->setDocumentID($document_id) - ->save(); + queryfx( + $conn, + 'INSERT INTO %T (documentID, fieldKey, rawCorpus, termCorpus, + normalCorpus) VALUES (%d, %s, %s, %s, %s)', + $engine->getFieldTableName(), + $document_id, + $ferret_field['fieldKey'], + $ferret_field['rawCorpus'], + $ferret_field['termCorpus'], + $ferret_field['normalCorpus']); } - $ferret_ngrams = $engine->newNgramsObject(); - $conn = $ferret_ngrams->establishConnection('w'); - $sql = array(); foreach ($ngrams as $ngram) { $sql[] = qsprintf( @@ -173,15 +181,15 @@ public function indexFulltextObject( queryfx( $conn, 'INSERT INTO %T (documentID, ngram) VALUES %Q', - $ferret_ngrams->getTableName(), + $engine->getNgramsTableName(), $chunk); } } catch (Exception $ex) { - $ferret_document->killTransaction(); + $object->killTransaction(); throw $ex; } - $ferret_document->saveTransaction(); + $object->saveTransaction(); } @@ -190,32 +198,35 @@ private function deleteOldDocument( $object, PhabricatorSearchAbstractDocument $document) { - $old_document = $engine->newDocumentObject()->loadOneWhere( - 'objectPHID = %s', - $document->getPHID()); + $conn = $object->establishConnection('w'); + + $old_document = queryfx_one( + $conn, + 'SELECT * FROM %T WHERE objectPHID = %s', + $engine->getDocumentTableName(), + $object->getPHID()); if (!$old_document) { return; } - $conn = $old_document->establishConnection('w'); - $old_id = $old_document->getID(); + $old_id = $old_document['id']; queryfx( $conn, 'DELETE FROM %T WHERE id = %d', - $engine->newDocumentObject()->getTableName(), + $engine->getDocumentTableName(), $old_id); queryfx( $conn, 'DELETE FROM %T WHERE documentID = %d', - $engine->newFieldObject()->getTableName(), + $engine->getFieldTableName(), $old_id); queryfx( $conn, 'DELETE FROM %T WHERE documentID = %d', - $engine->newNgramsObject()->getTableName(), + $engine->getNgramsTableName(), $old_id); } diff --git a/src/applications/search/ferret/PhabricatorFerretDocument.php b/src/applications/search/ferret/PhabricatorFerretDocument.php deleted file mode 100644 index 6f25eb93b6..0000000000 --- a/src/applications/search/ferret/PhabricatorFerretDocument.php +++ /dev/null @@ -1,52 +0,0 @@ - false, - self::CONFIG_COLUMN_SCHEMA => array( - 'isClosed' => 'bool', - 'authorPHID' => 'phid?', - 'ownerPHID' => 'phid?', - 'epochCreated' => 'epoch', - 'epochModified' => 'epoch', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_object' => array( - 'columns' => array('objectPHID'), - 'unique' => true, - ), - 'key_author' => array( - 'columns' => array('authorPHID'), - ), - 'key_owner' => array( - 'columns' => array('ownerPHID'), - ), - 'key_created' => array( - 'columns' => array('epochCreated'), - ), - 'key_modified' => array( - 'columns' => array('epochModified'), - ), - ), - ) + parent::getConfiguration(); - } - - public function getTableName() { - $application = $this->getApplicationName(); - $key = $this->getIndexKey(); - return "{$application}_{$key}_fdocument"; - } - -} diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index eb40b70e58..219130c02c 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -2,9 +2,8 @@ abstract class PhabricatorFerretEngine extends Phobject { - abstract public function newNgramsObject(); - abstract public function newDocumentObject(); - abstract public function newFieldObject(); + abstract public function getApplicationName(); + abstract public function getScopeName(); abstract public function newSearchEngine(); public function getDefaultFunctionKey() { @@ -168,4 +167,111 @@ public function newTermsCorpus($raw_corpus) { return $term_corpus; } +/* -( Schema )------------------------------------------------------------- */ + + public function getDocumentTableName() { + $application = $this->getApplicationName(); + $scope = $this->getScopeName(); + + return "{$application}_{$scope}_fdocument"; + } + + public function getDocumentSchemaColumns() { + return array( + 'id' => 'auto', + 'objectPHID' => 'phid', + 'isClosed' => 'bool', + 'authorPHID' => 'phid?', + 'ownerPHID' => 'phid?', + 'epochCreated' => 'epoch', + 'epochModified' => 'epoch', + ); + } + + public function getDocumentSchemaKeys() { + return array( + 'PRIMARY' => array( + 'columns' => array('id'), + 'unique' => true, + ), + 'key_object' => array( + 'columns' => array('objectPHID'), + 'unique' => true, + ), + 'key_author' => array( + 'columns' => array('authorPHID'), + ), + 'key_owner' => array( + 'columns' => array('ownerPHID'), + ), + 'key_created' => array( + 'columns' => array('epochCreated'), + ), + 'key_modified' => array( + 'columns' => array('epochModified'), + ), + ); + } + + public function getFieldTableName() { + $application = $this->getApplicationName(); + $scope = $this->getScopeName(); + + return "{$application}_{$scope}_ffield"; + } + + public function getFieldSchemaColumns() { + return array( + 'id' => 'auto', + 'documentID' => 'uint32', + 'fieldKey' => 'text4', + 'rawCorpus' => 'sort', + 'termCorpus' => 'sort', + 'normalCorpus' => 'sort', + ); + } + + public function getFieldSchemaKeys() { + return array( + 'PRIMARY' => array( + 'columns' => array('id'), + 'unique' => true, + ), + 'key_documentfield' => array( + 'columns' => array('documentID', 'fieldKey'), + 'unique' => true, + ), + ); + } + + public function getNgramsTableName() { + $application = $this->getApplicationName(); + $scope = $this->getScopeName(); + + return "{$application}_{$scope}_fngrams"; + } + + public function getNgramsSchemaColumns() { + return array( + 'id' => 'auto', + 'documentID' => 'uint32', + 'ngram' => 'char3', + ); + } + + public function getNgramsSchemaKeys() { + return array( + 'PRIMARY' => array( + 'columns' => array('id'), + 'unique' => true, + ), + 'key_ngram' => array( + 'columns' => array('ngram', 'documentID'), + ), + 'key_object' => array( + 'columns' => array('documentID'), + ), + ); + } + } diff --git a/src/applications/search/ferret/PhabricatorFerretField.php b/src/applications/search/ferret/PhabricatorFerretField.php deleted file mode 100644 index be39e745ed..0000000000 --- a/src/applications/search/ferret/PhabricatorFerretField.php +++ /dev/null @@ -1,39 +0,0 @@ - false, - self::CONFIG_COLUMN_SCHEMA => array( - 'documentID' => 'uint32', - 'fieldKey' => 'text4', - 'rawCorpus' => 'sort', - 'termCorpus' => 'sort', - 'normalCorpus' => 'sort', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_documentfield' => array( - 'columns' => array('documentID', 'fieldKey'), - 'unique' => true, - ), - ), - ) + parent::getConfiguration(); - } - - public function getTableName() { - $application = $this->getApplicationName(); - $key = $this->getIndexKey(); - return "{$application}_{$key}_ffield"; - } - -} diff --git a/src/applications/search/ferret/PhabricatorFerretNgrams.php b/src/applications/search/ferret/PhabricatorFerretNgrams.php deleted file mode 100644 index 9e28f96cb6..0000000000 --- a/src/applications/search/ferret/PhabricatorFerretNgrams.php +++ /dev/null @@ -1,35 +0,0 @@ - false, - self::CONFIG_COLUMN_SCHEMA => array( - 'documentID' => 'uint32', - 'ngram' => 'char3', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_ngram' => array( - 'columns' => array('ngram', 'documentID'), - ), - 'key_object' => array( - 'columns' => array('documentID'), - ), - ), - ) + parent::getConfiguration(); - } - - public function getTableName() { - $application = $this->getApplicationName(); - $key = $this->getIndexKey(); - return "{$application}_{$key}_fngrams"; - } - -} diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 42e6ed7b4e..18e860ebb9 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1623,8 +1623,7 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { $engine = $this->ferretEngine; $stemmer = $engine->newStemmer(); - $ngram_table = $engine->newNgramsObject(); - $ngram_table_name = $ngram_table->getTableName(); + $ngram_table = $engine->getNgramsTableName(); $flat = array(); foreach ($this->ferretTokens as $fulltext_token) { @@ -1680,7 +1679,7 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { foreach ($ngrams as $ngram) { $flat[] = array( - 'table' => $ngram_table_name, + 'table' => $ngram_table, 'ngram' => $ngram, ); } @@ -1702,14 +1701,14 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { $phid_column = qsprintf($conn, '%T', 'phid'); } - $document_table = $engine->newDocumentObject(); - $field_table = $engine->newFieldObject(); + $document_table = $engine->getDocumentTableName(); + $field_table = $engine->getFieldTableName(); $joins = array(); $joins[] = qsprintf( $conn, 'JOIN %T ft_doc ON ft_doc.objectPHID = %Q', - $document_table->getTableName(), + $document_table, $phid_column); $idx = 1; @@ -1736,7 +1735,7 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { $conn, 'JOIN %T %T ON ft_doc.id = %T.documentID AND %T.fieldKey = %s', - $field_table->getTableName(), + $field_table, $alias, $alias, $alias, From c9152b586b64a2e9f552f6f5348126b64a9adce8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 12:02:53 -0700 Subject: [PATCH 134/865] Support Ferret engine in Owners Summary: Ref T12819. Same deal as before, but smaller diffs after D18559. Test Plan: Indexed and searched for packages. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18564 --- .../20170907.ferret.10.owners.doc.sql | 9 +++++++++ .../20170907.ferret.11.owners.field.sql | 8 ++++++++ .../20170907.ferret.12.owners.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 5 ++++- .../PhabricatorOwnersPackageFerretEngine.php | 18 ++++++++++++++++++ .../PhabricatorOwnersPackageFulltextEngine.php | 0 .../storage/PhabricatorOwnersPackage.php | 9 +++++++++ 7 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20170907.ferret.10.owners.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.11.owners.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.12.owners.ngrams.sql create mode 100644 src/applications/owners/search/PhabricatorOwnersPackageFerretEngine.php rename src/applications/owners/{query => search}/PhabricatorOwnersPackageFulltextEngine.php (100%) diff --git a/resources/sql/autopatches/20170907.ferret.10.owners.doc.sql b/resources/sql/autopatches/20170907.ferret.10.owners.doc.sql new file mode 100644 index 0000000000..aaaa36623b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.10.owners.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_package_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.11.owners.field.sql b/resources/sql/autopatches/20170907.ferret.11.owners.field.sql new file mode 100644 index 0000000000..ebd72806f4 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.11.owners.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_package_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.12.owners.ngrams.sql b/resources/sql/autopatches/20170907.ferret.12.owners.ngrams.sql new file mode 100644 index 0000000000..0f8c6865bf --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.12.owners.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_package_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1ffe418b5c..266993bfb7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3317,7 +3317,8 @@ 'PhabricatorOwnersPackageDescriptionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDescriptionTransaction.php', 'PhabricatorOwnersPackageDominionTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageDominionTransaction.php', 'PhabricatorOwnersPackageEditEngine' => 'applications/owners/editor/PhabricatorOwnersPackageEditEngine.php', - 'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/query/PhabricatorOwnersPackageFulltextEngine.php', + 'PhabricatorOwnersPackageFerretEngine' => 'applications/owners/search/PhabricatorOwnersPackageFerretEngine.php', + 'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/search/PhabricatorOwnersPackageFulltextEngine.php', 'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php', 'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php', 'PhabricatorOwnersPackageNameTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php', @@ -8714,6 +8715,7 @@ 'PhabricatorDestructibleInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorNgramsInterface', ), 'PhabricatorOwnersPackageAuditingTransaction' => 'PhabricatorOwnersPackageTransactionType', @@ -8723,6 +8725,7 @@ 'PhabricatorOwnersPackageDescriptionTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageDominionTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorOwnersPackageFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams', diff --git a/src/applications/owners/search/PhabricatorOwnersPackageFerretEngine.php b/src/applications/owners/search/PhabricatorOwnersPackageFerretEngine.php new file mode 100644 index 0000000000..684fe10969 --- /dev/null +++ b/src/applications/owners/search/PhabricatorOwnersPackageFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 7 Sep 2017 12:14:05 -0700 Subject: [PATCH 135/865] Support Ferret engine in Phame Summary: Ref T12819. Mostly straightforward, with a couple of minor query modernization things. Test Plan: Indexed and searched for posts and blogs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18565 --- .../20170907.ferret.13.blog.doc.sql | 9 +++++++ .../20170907.ferret.14.blog.field.sql | 8 ++++++ .../20170907.ferret.15.blog.ngrams.sql | 5 ++++ .../20170907.ferret.16.post.doc.sql | 9 +++++++ .../20170907.ferret.17.post.field.sql | 8 ++++++ .../20170907.ferret.18.post.ngrams.sql | 5 ++++ src/__phutil_library_map__.php | 6 +++++ .../phame/query/PhameBlogQuery.php | 12 ++++++--- .../phame/query/PhamePostQuery.php | 25 +++++++++++-------- .../phame/search/PhameBlogFerretEngine.php | 18 +++++++++++++ .../phame/search/PhamePostFerretEngine.php | 18 +++++++++++++ src/applications/phame/storage/PhameBlog.php | 11 +++++++- src/applications/phame/storage/PhamePost.php | 11 +++++++- 13 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 resources/sql/autopatches/20170907.ferret.13.blog.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.14.blog.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.15.blog.ngrams.sql create mode 100644 resources/sql/autopatches/20170907.ferret.16.post.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.17.post.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.18.post.ngrams.sql create mode 100644 src/applications/phame/search/PhameBlogFerretEngine.php create mode 100644 src/applications/phame/search/PhamePostFerretEngine.php diff --git a/resources/sql/autopatches/20170907.ferret.13.blog.doc.sql b/resources/sql/autopatches/20170907.ferret.13.blog.doc.sql new file mode 100644 index 0000000000..d75232fae1 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.13.blog.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_blog_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.14.blog.field.sql b/resources/sql/autopatches/20170907.ferret.14.blog.field.sql new file mode 100644 index 0000000000..9982914229 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.14.blog.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_blog_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.15.blog.ngrams.sql b/resources/sql/autopatches/20170907.ferret.15.blog.ngrams.sql new file mode 100644 index 0000000000..b20bb8fcbb --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.15.blog.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_blog_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.16.post.doc.sql b/resources/sql/autopatches/20170907.ferret.16.post.doc.sql new file mode 100644 index 0000000000..9f9155aa49 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.16.post.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_post_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.17.post.field.sql b/resources/sql/autopatches/20170907.ferret.17.post.field.sql new file mode 100644 index 0000000000..26d729d05d --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.17.post.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_post_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.18.post.ngrams.sql b/resources/sql/autopatches/20170907.ferret.18.post.ngrams.sql new file mode 100644 index 0000000000..18e534e948 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.18.post.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_post_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 266993bfb7..7a26123482 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4371,6 +4371,7 @@ 'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php', 'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', 'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php', + 'PhameBlogFerretEngine' => 'applications/phame/search/PhameBlogFerretEngine.php', 'PhameBlogFullDomainTransaction' => 'applications/phame/xaction/PhameBlogFullDomainTransaction.php', 'PhameBlogFulltextEngine' => 'applications/phame/search/PhameBlogFulltextEngine.php', 'PhameBlogHeaderImageTransaction' => 'applications/phame/xaction/PhameBlogHeaderImageTransaction.php', @@ -4411,6 +4412,7 @@ 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', + 'PhamePostFerretEngine' => 'applications/phame/search/PhamePostFerretEngine.php', 'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php', 'PhamePostHeaderImageTransaction' => 'applications/phame/xaction/PhamePostHeaderImageTransaction.php', 'PhamePostHeaderPictureController' => 'applications/phame/controller/post/PhamePostHeaderPictureController.php', @@ -9995,6 +9997,7 @@ 'PhabricatorApplicationTransactionInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PhameBlog404Controller' => 'PhameLiveController', 'PhameBlogArchiveController' => 'PhameBlogController', @@ -10007,6 +10010,7 @@ 'PhameBlogEditEngine' => 'PhabricatorEditEngine', 'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', 'PhameBlogFeedController' => 'PhameBlogController', + 'PhameBlogFerretEngine' => 'PhabricatorFerretEngine', 'PhameBlogFullDomainTransaction' => 'PhameBlogTransactionType', 'PhameBlogFulltextEngine' => 'PhabricatorFulltextEngine', 'PhameBlogHeaderImageTransaction' => 'PhameBlogTransactionType', @@ -10050,6 +10054,7 @@ 'PhabricatorTokenReceiverInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PhamePostArchiveController' => 'PhamePostController', 'PhamePostBlogTransaction' => 'PhamePostTransactionType', @@ -10059,6 +10064,7 @@ 'PhamePostEditController' => 'PhamePostController', 'PhamePostEditEngine' => 'PhabricatorEditEngine', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhamePostFerretEngine' => 'PhabricatorFerretEngine', 'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine', 'PhamePostHeaderImageTransaction' => 'PhamePostTransactionType', 'PhamePostHeaderPictureController' => 'PhamePostController', diff --git a/src/applications/phame/query/PhameBlogQuery.php b/src/applications/phame/query/PhameBlogQuery.php index bd34255bd0..b4018c78eb 100644 --- a/src/applications/phame/query/PhameBlogQuery.php +++ b/src/applications/phame/query/PhameBlogQuery.php @@ -55,28 +55,28 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'status IN (%Ls)', + 'b.status IN (%Ls)', $this->statuses); } if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ls)', + 'b.id IN (%Ls)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'b.phid IN (%Ls)', $this->phids); } if ($this->domain !== null) { $where[] = qsprintf( $conn, - 'domain = %s', + 'b.domain = %s', $this->domain); } @@ -143,4 +143,8 @@ public function getQueryApplicationClass() { return null; } + protected function getPrimaryTableAlias() { + return 'b'; + } + } diff --git a/src/applications/phame/query/PhamePostQuery.php b/src/applications/phame/query/PhamePostQuery.php index 357418cfb3..85ef470cea 100644 --- a/src/applications/phame/query/PhamePostQuery.php +++ b/src/applications/phame/query/PhamePostQuery.php @@ -106,45 +106,45 @@ protected function willFilterPage(array $posts) { protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'p.id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'p.phid IN (%Ls)', $this->phids); } - if ($this->bloggerPHIDs) { + if ($this->bloggerPHIDs !== null) { $where[] = qsprintf( $conn, - 'bloggerPHID IN (%Ls)', + 'p.bloggerPHID IN (%Ls)', $this->bloggerPHIDs); } - if ($this->visibility) { + if ($this->visibility !== null) { $where[] = qsprintf( $conn, - 'visibility IN (%Ld)', + 'p.visibility IN (%Ld)', $this->visibility); } if ($this->publishedAfter !== null) { $where[] = qsprintf( $conn, - 'datePublished > %d', + 'p.datePublished > %d', $this->publishedAfter); } if ($this->blogPHIDs !== null) { $where[] = qsprintf( $conn, - 'blogPHID in (%Ls)', + 'p.blogPHID in (%Ls)', $this->blogPHIDs); } @@ -163,6 +163,7 @@ public function getBuiltinOrders() { public function getOrderableColumns() { return parent::getOrderableColumns() + array( 'datePublished' => array( + 'table' => $this->getPrimaryTableAlias(), 'column' => 'datePublished', 'type' => 'int', 'reverse' => false, @@ -186,4 +187,8 @@ public function getQueryApplicationClass() { return null; } + protected function getPrimaryTableAlias() { + return 'p'; + } + } diff --git a/src/applications/phame/search/PhameBlogFerretEngine.php b/src/applications/phame/search/PhameBlogFerretEngine.php new file mode 100644 index 0000000000..76901f915c --- /dev/null +++ b/src/applications/phame/search/PhameBlogFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 7 Sep 2017 12:20:47 -0700 Subject: [PATCH 136/865] Support Ferret engine in Projects Summary: Ref T12819. Adds support for projects. Test Plan: Indexed and searched for projects. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18566 --- .../20170907.ferret.19.project.doc.sql | 9 +++++++++ .../20170907.ferret.20.project.field.sql | 8 ++++++++ .../20170907.ferret.21.project.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 3 +++ .../search/PhabricatorProjectFerretEngine.php | 18 ++++++++++++++++++ .../project/storage/PhabricatorProject.php | 9 +++++++++ 6 files changed, 52 insertions(+) create mode 100644 resources/sql/autopatches/20170907.ferret.19.project.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.20.project.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.21.project.ngrams.sql create mode 100644 src/applications/project/search/PhabricatorProjectFerretEngine.php diff --git a/resources/sql/autopatches/20170907.ferret.19.project.doc.sql b/resources/sql/autopatches/20170907.ferret.19.project.doc.sql new file mode 100644 index 0000000000..26272439cf --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.19.project.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_project.project_project_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.20.project.field.sql b/resources/sql/autopatches/20170907.ferret.20.project.field.sql new file mode 100644 index 0000000000..36514eb55d --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.20.project.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_project.project_project_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.21.project.ngrams.sql b/resources/sql/autopatches/20170907.ferret.21.project.ngrams.sql new file mode 100644 index 0000000000..dec12b0e56 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.21.project.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_project.project_project_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7a26123482..2513480fa3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3666,6 +3666,7 @@ 'PhabricatorProjectEditController' => 'applications/project/controller/PhabricatorProjectEditController.php', 'PhabricatorProjectEditEngine' => 'applications/project/engine/PhabricatorProjectEditEngine.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', + 'PhabricatorProjectFerretEngine' => 'applications/project/search/PhabricatorProjectFerretEngine.php', 'PhabricatorProjectFilterTransaction' => 'applications/project/xaction/PhabricatorProjectFilterTransaction.php', 'PhabricatorProjectFulltextEngine' => 'applications/project/search/PhabricatorProjectFulltextEngine.php', 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', @@ -9105,6 +9106,7 @@ 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', 'PhabricatorColumnProxyInterface', ), @@ -9164,6 +9166,7 @@ 'PhabricatorProjectEditController' => 'PhabricatorProjectController', 'PhabricatorProjectEditEngine' => 'PhabricatorEditEngine', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', + 'PhabricatorProjectFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorProjectFilterTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorProjectHeraldAction' => 'HeraldAction', diff --git a/src/applications/project/search/PhabricatorProjectFerretEngine.php b/src/applications/project/search/PhabricatorProjectFerretEngine.php new file mode 100644 index 0000000000..459fc24026 --- /dev/null +++ b/src/applications/project/search/PhabricatorProjectFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 7 Sep 2017 12:38:11 -0700 Subject: [PATCH 137/865] Support Ferret engine in Phriction Summary: Ref T12819. Adds Ferret engine support. Test Plan: Indexed and searched for documents. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18567 --- .../20170907.ferret.22.phriction.doc.sql | 9 +++++++++ .../20170907.ferret.23.phriction.field.sql | 8 ++++++++ .../20170907.ferret.24.phriction.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 3 +++ .../search/PhrictionDocumentFerretEngine.php | 18 ++++++++++++++++++ .../phriction/storage/PhrictionDocument.php | 9 +++++++++ 6 files changed, 52 insertions(+) create mode 100644 resources/sql/autopatches/20170907.ferret.22.phriction.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.23.phriction.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.24.phriction.ngrams.sql create mode 100644 src/applications/phriction/search/PhrictionDocumentFerretEngine.php diff --git a/resources/sql/autopatches/20170907.ferret.22.phriction.doc.sql b/resources/sql/autopatches/20170907.ferret.22.phriction.doc.sql new file mode 100644 index 0000000000..9de7124255 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.22.phriction.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_document_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.23.phriction.field.sql b/resources/sql/autopatches/20170907.ferret.23.phriction.field.sql new file mode 100644 index 0000000000..0fc5b959d1 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.23.phriction.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_document_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.24.phriction.ngrams.sql b/resources/sql/autopatches/20170907.ferret.24.phriction.ngrams.sql new file mode 100644 index 0000000000..abbb90a1e4 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.24.phriction.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_document_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2513480fa3..c22e4314c0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4694,6 +4694,7 @@ 'PhrictionDocumentContentTransaction' => 'applications/phriction/xaction/PhrictionDocumentContentTransaction.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', + 'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.php', 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', 'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php', @@ -10420,6 +10421,7 @@ 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', ), @@ -10428,6 +10430,7 @@ 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentController' => 'PhrictionController', 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', + 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', diff --git a/src/applications/phriction/search/PhrictionDocumentFerretEngine.php b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php new file mode 100644 index 0000000000..76802391e7 --- /dev/null +++ b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 7 Sep 2017 13:01:26 -0700 Subject: [PATCH 138/865] Support Ferret engine in Calendar Summary: Ref T12819. Adds ferret engine support for Calendar events. Test Plan: Indexed and queried calendar events. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18568 --- .../20170907.ferret.25.event.doc.sql | 9 +++++++++ .../20170907.ferret.26.event.field.sql | 8 ++++++++ .../20170907.ferret.27.event.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 3 +++ .../PhabricatorCalendarEventFerretEngine.php | 18 ++++++++++++++++++ .../storage/PhabricatorCalendarEvent.php | 9 +++++++++ 6 files changed, 52 insertions(+) create mode 100644 resources/sql/autopatches/20170907.ferret.25.event.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.26.event.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.27.event.ngrams.sql create mode 100644 src/applications/calendar/search/PhabricatorCalendarEventFerretEngine.php diff --git a/resources/sql/autopatches/20170907.ferret.25.event.doc.sql b/resources/sql/autopatches/20170907.ferret.25.event.doc.sql new file mode 100644 index 0000000000..d7298fad31 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.25.event.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_event_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.26.event.field.sql b/resources/sql/autopatches/20170907.ferret.26.event.field.sql new file mode 100644 index 0000000000..2ec76c3511 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.26.event.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_event_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.27.event.ngrams.sql b/resources/sql/autopatches/20170907.ferret.27.event.ngrams.sql new file mode 100644 index 0000000000..e802e2d97e --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.27.event.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_event_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c22e4314c0..67bb9b73f3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2208,6 +2208,7 @@ 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', 'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php', 'PhabricatorCalendarEventExportController' => 'applications/calendar/controller/PhabricatorCalendarEventExportController.php', + 'PhabricatorCalendarEventFerretEngine' => 'applications/calendar/search/PhabricatorCalendarEventFerretEngine.php', 'PhabricatorCalendarEventForkTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php', 'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php', 'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php', @@ -7446,6 +7447,7 @@ 'PhabricatorFlaggableInterface', 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', ), 'PhabricatorCalendarEventAcceptTransaction' => 'PhabricatorCalendarEventReplyTransaction', @@ -7466,6 +7468,7 @@ 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction', 'PhabricatorCalendarEventExportController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorCalendarEventForkTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType', 'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine', diff --git a/src/applications/calendar/search/PhabricatorCalendarEventFerretEngine.php b/src/applications/calendar/search/PhabricatorCalendarEventFerretEngine.php new file mode 100644 index 0000000000..70e456e034 --- /dev/null +++ b/src/applications/calendar/search/PhabricatorCalendarEventFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 7 Sep 2017 13:05:24 -0700 Subject: [PATCH 139/865] Support Ferret engine in Pholio Summary: Ref T12819. Support for Pholio. Test Plan: Indexed and searched mocks. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18569 --- .../20170907.ferret.28.mock.doc.sql | 9 +++++++++ .../20170907.ferret.29.mock.field.sql | 8 ++++++++ .../20170907.ferret.30.mock.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 3 +++ .../pholio/search/PholioMockFerretEngine.php | 18 ++++++++++++++++++ src/applications/pholio/storage/PholioMock.php | 10 +++++++++- 6 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20170907.ferret.28.mock.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.29.mock.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.30.mock.ngrams.sql create mode 100644 src/applications/pholio/search/PholioMockFerretEngine.php diff --git a/resources/sql/autopatches/20170907.ferret.28.mock.doc.sql b/resources/sql/autopatches/20170907.ferret.28.mock.doc.sql new file mode 100644 index 0000000000..eb80ef3937 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.28.mock.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_pholio.pholio_mock_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.29.mock.field.sql b/resources/sql/autopatches/20170907.ferret.29.mock.field.sql new file mode 100644 index 0000000000..0cb0e97d05 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.29.mock.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_pholio.pholio_mock_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.30.mock.ngrams.sql b/resources/sql/autopatches/20170907.ferret.30.mock.ngrams.sql new file mode 100644 index 0000000000..e343ccf83b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.30.mock.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_pholio.pholio_mock_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 67bb9b73f3..f42f815f1f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4475,6 +4475,7 @@ 'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php', 'PholioMockEditor' => 'applications/pholio/editor/PholioMockEditor.php', 'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php', + 'PholioMockFerretEngine' => 'applications/pholio/search/PholioMockFerretEngine.php', 'PholioMockFulltextEngine' => 'applications/pholio/search/PholioMockFulltextEngine.php', 'PholioMockHasTaskEdgeType' => 'applications/pholio/edge/PholioMockHasTaskEdgeType.php', 'PholioMockHasTaskRelationship' => 'applications/pholio/relationships/PholioMockHasTaskRelationship.php', @@ -10145,6 +10146,7 @@ 'PhabricatorSpacesInterface', 'PhabricatorMentionableInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PholioMockArchiveController' => 'PholioController', 'PholioMockAuthorHeraldField' => 'PholioMockHeraldField', @@ -10154,6 +10156,7 @@ 'PholioMockEditController' => 'PholioController', 'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor', 'PholioMockEmbedView' => 'AphrontView', + 'PholioMockFerretEngine' => 'PhabricatorFerretEngine', 'PholioMockFulltextEngine' => 'PhabricatorFulltextEngine', 'PholioMockHasTaskEdgeType' => 'PhabricatorEdgeType', 'PholioMockHasTaskRelationship' => 'PholioMockRelationship', diff --git a/src/applications/pholio/search/PholioMockFerretEngine.php b/src/applications/pholio/search/PholioMockFerretEngine.php new file mode 100644 index 0000000000..f4f9b4610f --- /dev/null +++ b/src/applications/pholio/search/PholioMockFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 7 Sep 2017 13:11:34 -0700 Subject: [PATCH 140/865] Support the Ferret engine in Diffusion Summary: Ref T12819. More ferret engine support. Test Plan: Indexed and searched commits and repositories. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18572 --- .../20170907.ferret.31.repo.doc.sql | 9 +++++++++ .../20170907.ferret.32.repo.field.sql | 8 ++++++++ .../20170907.ferret.33.repo.ngrams.sql | 5 +++++ .../20170907.ferret.34.commit.doc.sql | 9 +++++++++ .../20170907.ferret.35.commit.field.sql | 8 ++++++++ .../20170907.ferret.36.commit.ngrams.sql | 5 +++++ src/__phutil_library_map__.php | 6 ++++++ .../search/DiffusionCommitFerretEngine.php | 18 ++++++++++++++++++ .../PhabricatorRepositoryFerretEngine.php | 18 ++++++++++++++++++ .../storage/PhabricatorRepository.php | 11 ++++++++++- .../storage/PhabricatorRepositoryCommit.php | 9 +++++++++ 11 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20170907.ferret.31.repo.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.32.repo.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.33.repo.ngrams.sql create mode 100644 resources/sql/autopatches/20170907.ferret.34.commit.doc.sql create mode 100644 resources/sql/autopatches/20170907.ferret.35.commit.field.sql create mode 100644 resources/sql/autopatches/20170907.ferret.36.commit.ngrams.sql create mode 100644 src/applications/repository/search/DiffusionCommitFerretEngine.php create mode 100644 src/applications/repository/search/PhabricatorRepositoryFerretEngine.php diff --git a/resources/sql/autopatches/20170907.ferret.31.repo.doc.sql b/resources/sql/autopatches/20170907.ferret.31.repo.doc.sql new file mode 100644 index 0000000000..4f37de60be --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.31.repo.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_repository_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.32.repo.field.sql b/resources/sql/autopatches/20170907.ferret.32.repo.field.sql new file mode 100644 index 0000000000..c7d75eb29d --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.32.repo.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_repository_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.33.repo.ngrams.sql b/resources/sql/autopatches/20170907.ferret.33.repo.ngrams.sql new file mode 100644 index 0000000000..db7ad4f3a0 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.33.repo.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_repository_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.34.commit.doc.sql b/resources/sql/autopatches/20170907.ferret.34.commit.doc.sql new file mode 100644 index 0000000000..9c275b09b7 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.34.commit.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commit_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.35.commit.field.sql b/resources/sql/autopatches/20170907.ferret.35.commit.field.sql new file mode 100644 index 0000000000..c2520b693b --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.35.commit.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commit_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20170907.ferret.36.commit.ngrams.sql b/resources/sql/autopatches/20170907.ferret.36.commit.ngrams.sql new file mode 100644 index 0000000000..32ed2275c3 --- /dev/null +++ b/resources/sql/autopatches/20170907.ferret.36.commit.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commit_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f42f815f1f..6c18759cb9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -652,6 +652,7 @@ 'DiffusionCommitEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitEditConduitAPIMethod.php', 'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php', 'DiffusionCommitEditEngine' => 'applications/diffusion/editor/DiffusionCommitEditEngine.php', + 'DiffusionCommitFerretEngine' => 'applications/repository/search/DiffusionCommitFerretEngine.php', 'DiffusionCommitFulltextEngine' => 'applications/repository/search/DiffusionCommitFulltextEngine.php', 'DiffusionCommitHasPackageEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasPackageEdgeType.php', 'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php', @@ -3807,6 +3808,7 @@ 'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php', 'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php', 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', + 'PhabricatorRepositoryFerretEngine' => 'applications/repository/search/PhabricatorRepositoryFerretEngine.php', 'PhabricatorRepositoryFulltextEngine' => 'applications/repository/search/PhabricatorRepositoryFulltextEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', @@ -5673,6 +5675,7 @@ 'DiffusionCommitEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DiffusionCommitEditController' => 'DiffusionController', 'DiffusionCommitEditEngine' => 'PhabricatorEditEngine', + 'DiffusionCommitFerretEngine' => 'PhabricatorFerretEngine', 'DiffusionCommitFulltextEngine' => 'PhabricatorFulltextEngine', 'DiffusionCommitHasPackageEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType', @@ -9308,6 +9311,7 @@ 'PhabricatorSpacesInterface', 'PhabricatorConduitResultInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PhabricatorRepositoryAuditRequest' => array( 'PhabricatorRepositoryDAO', @@ -9328,6 +9332,7 @@ 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', 'PhabricatorConduitResultInterface', 'PhabricatorDraftInterface', ), @@ -9350,6 +9355,7 @@ 'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorRepositoryEngine' => 'Phobject', + 'PhabricatorRepositoryFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorRepositoryFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', diff --git a/src/applications/repository/search/DiffusionCommitFerretEngine.php b/src/applications/repository/search/DiffusionCommitFerretEngine.php new file mode 100644 index 0000000000..a6c5ce42c8 --- /dev/null +++ b/src/applications/repository/search/DiffusionCommitFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 7 Sep 2017 13:15:07 -0700 Subject: [PATCH 141/865] Fix info view error layout in config boxes Summary: Adds some side margin here. Test Plan: error out a form field in a white box Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18571 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-info-view.css | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index aef701f7bc..2093e3e70e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'b2235af0', + 'core.pkg.css' => 'e9473020', 'core.pkg.js' => '6c085267', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -162,7 +162,7 @@ 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '5c4a5de6', 'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c', - 'rsrc/css/phui/phui-info-view.css' => '04e071d7', + 'rsrc/css/phui/phui-info-view.css' => 'e929f98c', 'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0', 'rsrc/css/phui/phui-left-right.css' => '75227a4d', 'rsrc/css/phui/phui-lightbox.css' => '0a035e40', @@ -849,7 +849,7 @@ 'phui-icon-set-selector-css' => '87db8fee', 'phui-icon-view-css' => '5c4a5de6', 'phui-image-mask-css' => 'a8498f9c', - 'phui-info-view-css' => '04e071d7', + 'phui-info-view-css' => 'e929f98c', 'phui-inline-comment-view-css' => '65ae3bc2', 'phui-invisible-character-view-css' => '6993d9f0', 'phui-left-right-css' => '75227a4d', diff --git a/webroot/rsrc/css/phui/phui-info-view.css b/webroot/rsrc/css/phui/phui-info-view.css index 7b20c63ccf..55400956e4 100644 --- a/webroot/rsrc/css/phui/phui-info-view.css +++ b/webroot/rsrc/css/phui/phui-info-view.css @@ -139,3 +139,7 @@ h1.phui-info-view-head { div.phui-object-box .phui-header-shell + .phui-info-view { margin: 16px 0 8px; } + +div.phui-object-box.phui-box-white-config .phui-header-shell + .phui-info-view { + margin: 20px 16px 8px; +} From f1193afa94bfeeebe8491b0e89a2b68d286c7f81 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 13:08:10 -0700 Subject: [PATCH 142/865] Update passphrase edit UI Summary: Updates creating credentials Test Plan: create some notes Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18570 --- .../herald/controller/HeraldRuleController.php | 2 -- .../PassphraseCredentialCreateController.php | 9 ++------- .../PassphraseCredentialEditController.php | 17 +++-------------- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index d338f6a57e..eaf352a0a7 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -238,8 +238,6 @@ public function handleRequest(AphrontRequest $request) { ? pht('Edit Herald Rule: %s', $rule->getName()) : pht('Create Herald Rule: %s', idx($content_type_map, $content_type)); - $icon = $rule->getID() ? 'fa-pencil' : 'fa-plus-square'; - $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) diff --git a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php index 87afe22cc6..b237ef378b 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialCreateController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialCreateController.php @@ -52,17 +52,12 @@ public function handleRequest(AphrontRequest $request) { $crumbs->setBorder(true); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Credential')) + ->setHeaderText($title) ->setFormErrors($errors) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon('fa-plus-square'); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter($box); return $this->newPage() diff --git a/src/applications/passphrase/controller/PassphraseCredentialEditController.php b/src/applications/passphrase/controller/PassphraseCredentialEditController.php index a35bd3479e..91c8d93883 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialEditController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialEditController.php @@ -249,10 +249,6 @@ public function handleRequest(AphrontRequest $request) { ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel(pht('Credential Type')) - ->setValue($type->getCredentialTypeName())) ->appendChild( id(new AphrontFormDividerControl())) ->appendControl( @@ -322,10 +318,9 @@ public function handleRequest(AphrontRequest $request) { $crumbs->setBorder(true); if ($is_new) { - $title = pht('Create New Credential'); + $title = pht('New Credential: %s', $type->getCredentialTypeName()); $crumbs->addTextCrumb(pht('Create')); $cancel_uri = $this->getApplicationURI(); - $header_icon = 'fa-plus-square'; } else { $title = pht('Edit Credential: %s', $credential->getName()); $crumbs->addTextCrumb( @@ -333,7 +328,6 @@ public function handleRequest(AphrontRequest $request) { '/K'.$credential->getID()); $crumbs->addTextCrumb(pht('Edit')); $cancel_uri = '/K'.$credential->getID(); - $header_icon = 'fa-pencil'; } if ($request->isAjax()) { @@ -356,18 +350,13 @@ public function handleRequest(AphrontRequest $request) { ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Credential')) + ->setHeaderText($title) ->setFormErrors($errors) ->setValidationException($validation_exception) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form); - $header = id(new PHUIHeaderView()) - ->setHeader($title) - ->setHeaderIcon($header_icon); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) ->setFooter(array( $box, )); From 8e9f0496266a3d328507ad79ee4e34402ac171dd Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 13:37:41 -0700 Subject: [PATCH 143/865] Provide "bin/storage analyze" and make "bin/storage upgrade" run analysis automatically Summary: Ref T12819. Normallly "ANALYZE TABLE" is like sprinkling magic pixie dust on the database and hoping it will make "good vibes" that cause it to go faster, but in at least some concrete cases with the ngrams tables there really was a key cardinality issue which ANALYZE TABLE corrected, fixing bogus query plans. Add `bin/storage analyze` to analyze all tables, and make `bin/storage upgrade` run it after adjustment if `--no-adjust` is not specified, and make `bin/storage adjust` run it always. This runs in a couple seconds and should never hurt anything, so it should be fine to sprinkle lots of pixie dust into the `bin/storage` workflow. Test Plan: Ran `bin/storage analyze`. Ran `bin/storage upgrade`, saw analyze run. Totally felt great vibes and really aligned chakras on the database. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18573 --- src/__phutil_library_map__.php | 2 + ...icatorStorageManagementAnalyzeWorkflow.php | 19 ++++++ .../PhabricatorStorageManagementWorkflow.php | 58 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6c18759cb9..83714d4e13 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4088,6 +4088,7 @@ 'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php', 'PhabricatorStorageManagementAPI' => 'infrastructure/storage/management/PhabricatorStorageManagementAPI.php', 'PhabricatorStorageManagementAdjustWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php', + 'PhabricatorStorageManagementAnalyzeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php', 'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php', 'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php', 'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php', @@ -9679,6 +9680,7 @@ 'PhabricatorStorageFixtureScopeGuard' => 'Phobject', 'PhabricatorStorageManagementAPI' => 'Phobject', 'PhabricatorStorageManagementAdjustWorkflow' => 'PhabricatorStorageManagementWorkflow', + 'PhabricatorStorageManagementAnalyzeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow', diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php new file mode 100644 index 0000000000..54a4bf3d36 --- /dev/null +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php @@ -0,0 +1,19 @@ +setName('analyze') + ->setExamples('**analyze**') + ->setSynopsis( + pht('Run "ANALYZE TABLE" on tables to improve performance.')); + } + + public function didExecute(PhutilArgumentParser $args) { + $this->analyzeTables(); + return 0; + } + +} diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index 48d753781d..85d0c09c73 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -137,6 +137,15 @@ final protected function adjustSchemata( try { $err = $this->doAdjustSchemata($api, $unsafe); + + // Analyze tables if we're not doing a dry run and adjustments are either + // all clear or have minor errors like surplus tables. + if (!$this->dryRun) { + $should_analyze = (($err == 0) || ($err == 2)); + if ($should_analyze) { + $this->analyzeTables(); + } + } } catch (Exception $ex) { $lock->unlock(); throw $ex; @@ -1163,4 +1172,53 @@ final protected function lock(PhabricatorStorageManagementAPI $api) { ->lock(); } + final protected function analyzeTables() { + // Analyzing tables can sometimes have a significant effect on query + // performance, particularly for the fulltext ngrams tables. See T12819 + // for some specific examples. + + $api = $this->getSingleAPI(); + $conn = $api->getConn(null); + + $patches = $this->getPatches(); + $databases = $api->getDatabaseList($patches, true); + + $this->logInfo( + pht('ANALYZE'), + pht('Analyzing tables...')); + + $targets = array(); + foreach ($databases as $database) { + queryfx($conn, 'USE %C', $database); + $tables = queryfx_all($conn, 'SHOW TABLE STATUS'); + foreach ($tables as $table) { + $table_name = $table['Name']; + + $targets[] = array( + 'database' => $database, + 'table' => $table_name, + ); + } + } + + $bar = id(new PhutilConsoleProgressBar()) + ->setTotal(count($targets)); + foreach ($targets as $target) { + queryfx( + $conn, + 'ANALYZE TABLE %T.%T', + $target['database'], + $target['table']); + + $bar->update(1); + } + $bar->done(); + + $this->logOkay( + pht('ANALYZED'), + pht( + 'Analyzed %d table(s).', + count($targets))); + } + } From a46a9ff165bd0254b31a7ede38b474e9f7897531 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Sep 2017 15:38:33 -0700 Subject: [PATCH 144/865] Update VCS password UI Summary: Miss this with earlier pass, updates the VCS password page. Test Plan: Try to set a vcs password Reviewers: epriestley Reviewed By: epriestley Spies: Korvin Differential Revision: https://secure.phabricator.com/D18574 --- .../diffusion/panel/DiffusionSetPasswordSettingsPanel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php index a41f322a3d..ccfa0df4a4 100644 --- a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php +++ b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php @@ -233,7 +233,7 @@ public function processRequest(AphrontRequest $request) { $object_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($form) ->setFormErrors($errors); @@ -259,7 +259,7 @@ public function processRequest(AphrontRequest $request) { $remove_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Remove VCS Password')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) ->setForm($remove_form); $saved = null; From 4cae4a3b767fb505c32efd7ab7d8c6c185c59b96 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Sep 2017 14:52:38 -0700 Subject: [PATCH 145/865] Correct `bin/storage analyze` internal API for cluster environments Summary: Ref T12819. This worked right in a non-cluster environment, but `bin/storage upgrade` iterates over each master in a partitioned cluster environment. Tweak the API so `bin/storage analyze` targets a single host but `bin/storage upgrade` can hit all the masters. Test Plan: Will run `bin/storage upgrade` in production again. Ran `upgrade` and `analyze` locally, still work fine. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18576 --- .../PhabricatorStorageManagementAnalyzeWorkflow.php | 3 ++- .../workflow/PhabricatorStorageManagementWorkflow.php | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php index 54a4bf3d36..6fe3e60926 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAnalyzeWorkflow.php @@ -12,7 +12,8 @@ protected function didConstruct() { } public function didExecute(PhutilArgumentParser $args) { - $this->analyzeTables(); + $api = $this->getSingleAPI(); + $this->analyzeTables($api); return 0; } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index 85d0c09c73..a03ef41c4d 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -143,7 +143,7 @@ final protected function adjustSchemata( if (!$this->dryRun) { $should_analyze = (($err == 0) || ($err == 2)); if ($should_analyze) { - $this->analyzeTables(); + $this->analyzeTables($api); } } } catch (Exception $ex) { @@ -1172,12 +1172,13 @@ final protected function lock(PhabricatorStorageManagementAPI $api) { ->lock(); } - final protected function analyzeTables() { + final protected function analyzeTables( + PhabricatorStorageManagementAPI $api) { + // Analyzing tables can sometimes have a significant effect on query // performance, particularly for the fulltext ngrams tables. See T12819 // for some specific examples. - $api = $this->getSingleAPI(); $conn = $api->getConn(null); $patches = $this->getPatches(); From 7ea6de6e9c9da3ce818ac65c78da8065b73e6d67 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Sep 2017 08:06:16 -0700 Subject: [PATCH 146/865] Split Ferret engine strings for tokenization on any sequence of whitespace Summary: Ref T12819. Currently, strings are split only on spaces, but newlines (and, if they exist, tabs) should also split strings. Without this, we can fail to get the proper term boundary tokens for words which begin at the start of a line or end at the end of a line. Test Plan: Reindexed a document with "xyz\nabc", saw `"yz "` and `" ab"` term boundary tokens generate properly. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18579 --- src/applications/search/ferret/PhabricatorFerretEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index 219130c02c..3c8098c54f 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -75,7 +75,7 @@ public function newStemmer() { public function tokenizeString($value) { $value = trim($value, ' '); - $value = preg_split('/ +/', $value); + $value = preg_split('/\s+/u', $value); return $value; } From d67cc8e5c59c435bca027ac3e5f377aeab23412d Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Sep 2017 08:13:08 -0700 Subject: [PATCH 147/865] Remove some redundant information from the Ferret engine index Summary: Ref T12819. The "full" field has all other fields, and the "core" field has "title" and "body". Due to the way the "full" and "core" fields were being built, the "core" field also got included in the "full" field, so the "full" field has two copies of the title, two copies of the body, and then one copy of everything else. Put only one copy of each distinct thing in each "full" and "core". Also, simplify the logic a little bit so we build these virtual fields in a more consistent way. Test Plan: Ran `bin/search index` and looked at the fields in the database, saw less redundant information. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18580 --- .../PhabricatorFerretFulltextEngineExtension.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php index 4344fe1623..6903072345 100644 --- a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php @@ -66,9 +66,12 @@ public function indexFulltextObject( ); break; } - } - $key_all = PhabricatorSearchDocumentFieldType::FIELD_ALL; + $virtual_fields[] = array( + PhabricatorSearchDocumentFieldType::FIELD_ALL, + $raw_corpus, + ); + } $empty_template = array( 'raw' => array(), @@ -76,9 +79,7 @@ public function indexFulltextObject( 'normal' => array(), ); - $ferret_corpus_map = array( - $key_all => $empty_template, - ); + $ferret_corpus_map = array(); foreach ($virtual_fields as $field) { list($key, $raw_corpus) = $field; @@ -98,10 +99,6 @@ public function indexFulltextObject( $ferret_corpus_map[$key]['raw'][] = $raw_corpus; $ferret_corpus_map[$key]['term'][] = $term_corpus; $ferret_corpus_map[$key]['normal'][] = $normal_corpus; - - $ferret_corpus_map[$key_all]['raw'][] = $raw_corpus; - $ferret_corpus_map[$key_all]['term'][] = $term_corpus; - $ferret_corpus_map[$key_all]['normal'][] = $normal_corpus; } $ferret_fields = array(); From c662dda0f17d886fb864ecfde0e8b37b4fe090f8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Sep 2017 09:03:32 -0700 Subject: [PATCH 148/865] When selecting Ferret ngrams, select term ngrams (not raw ngrams) for term search Summary: Ref T12819. For queries like `v0.2`, we would incorrectly search for ngrams including `0.2`, but this is only a substring ngram: the term corpus splits this into `v0` and `2`, so `0.2` is not in the ngrams table. When executing term queries, search for term ngrams instead. This makes "v0.2" work properly again. Test Plan: Searched for "v0.2", found a task with "v0.2" in the title. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18581 --- .../query/policy/PhabricatorCursorPagedPolicyAwareQuery.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 18e860ebb9..0488ba6133 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1666,12 +1666,13 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { if ($is_substring) { $ngrams = $engine->getSubstringNgramsFromString($value); } else { - $ngrams = $engine->getTermNgramsFromString($value); + $terms_value = $engine->newTermsCorpus($value); + $ngrams = $engine->getTermNgramsFromString($terms_value); // If this is a stemmed term, only look for ngrams present in both the // unstemmed and stemmed variations. if ($is_stemmed) { - $stem_value = $stemmer->stemToken($value); + $stem_value = $stemmer->stemToken($terms_value); $stem_ngrams = $engine->getTermNgramsFromString($stem_value); $ngrams = array_intersect($ngrams, $stem_ngrams); } From d15fb20fe6a9108f36970194b2f4b54803bde3f5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Sep 2017 08:08:19 -0700 Subject: [PATCH 149/865] Support storage of Differential hunk data in Files Summary: Ref T12932. For long-lived installs, one of the largest tables tends to be the hunk data table. Although it doesn't grow tremendously fast, it's also well suited to storage in Files instead of the database (infrequent access, relatively large blobs of data, mostly one-at-a-time access), and earlier work anticipated eventually adding support for Files storage. Make Files storage work, and provide `bin/differential migrate-hunk` to manually test/migrate hunks. This is currently the only way hunks get moved to file storage, but I expect to add a GC step which moves them to File storage after 30 days shortly. The immediate motivation for this is to relieve storage pressure on db001/db002 so we have more headroom for deploying the Ferret engine and its larger indexes (see also T12819). Test Plan: - Used `bin/differential migrate-hunk` to move a hunk to and from file storage, verified it survived intact. - Downloaded the actual stored file, sanity-checked it. Verified permissions. - Destroyed a diff with `bin/remove destroy`, saw the hunk and file storage destroyed. - Verified that going from file -> text destroys the old file properly with `migrate-hunk --trace ...`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12932 Differential Revision: https://secure.phabricator.com/D18584 --- src/__phutil_library_map__.php | 4 + ...ricatorDifferentialMigrateHunkWorkflow.php | 86 ++++++++++++ .../storage/DifferentialChangeset.php | 28 +++- .../differential/storage/DifferentialDiff.php | 2 +- .../differential/storage/DifferentialHunk.php | 17 ++- .../storage/DifferentialModernHunk.php | 125 ++++++++++++++++++ 6 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 83714d4e13..925dfc66c6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2667,6 +2667,7 @@ 'PhabricatorDifferentialConfigOptions' => 'applications/differential/config/PhabricatorDifferentialConfigOptions.php', 'PhabricatorDifferentialExtractWorkflow' => 'applications/differential/management/PhabricatorDifferentialExtractWorkflow.php', 'PhabricatorDifferentialManagementWorkflow' => 'applications/differential/management/PhabricatorDifferentialManagementWorkflow.php', + 'PhabricatorDifferentialMigrateHunkWorkflow' => 'applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php', 'PhabricatorDiffusionApplication' => 'applications/diffusion/application/PhabricatorDiffusionApplication.php', 'PhabricatorDiffusionBlameSetting' => 'applications/settings/setting/PhabricatorDiffusionBlameSetting.php', @@ -5367,6 +5368,7 @@ 'DifferentialChangeset' => array( 'DifferentialDAO', 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', ), 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetFileTreeSideNavBuilder' => 'Phobject', @@ -5461,6 +5463,7 @@ 'DifferentialHunk' => array( 'DifferentialDAO', 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', ), 'DifferentialHunkParser' => 'Phobject', 'DifferentialHunkParserTestCase' => 'PhabricatorTestCase', @@ -7999,6 +8002,7 @@ 'PhabricatorDifferentialConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorDifferentialExtractWorkflow' => 'PhabricatorDifferentialManagementWorkflow', 'PhabricatorDifferentialManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorDifferentialMigrateHunkWorkflow' => 'PhabricatorDifferentialManagementWorkflow', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorDiffusionApplication' => 'PhabricatorApplication', 'PhabricatorDiffusionBlameSetting' => 'PhabricatorInternalSetting', diff --git a/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php new file mode 100644 index 0000000000..9c0e0071e4 --- /dev/null +++ b/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php @@ -0,0 +1,86 @@ +setName('migrate-hunk') + ->setExamples('**migrate-hunk** --id __hunk__ --to __storage__') + ->setSynopsis(pht('Migrate storage engines for a hunk.')) + ->setArguments( + array( + array( + 'name' => 'id', + 'param' => 'id', + 'help' => pht('Hunk ID to migrate.'), + ), + array( + 'name' => 'to', + 'param' => 'storage', + 'help' => pht('Storage engine to migrate to.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $id = $args->getArg('id'); + if (!$id) { + throw new PhutilArgumentUsageException( + pht('Specify a hunk to migrate with --id.')); + } + + $storage = $args->getArg('to'); + switch ($storage) { + case DifferentialModernHunk::DATATYPE_TEXT: + case DifferentialModernHunk::DATATYPE_FILE: + break; + default: + throw new PhutilArgumentUsageException( + pht('Specify a hunk storage engine with --to.')); + } + + $hunk = $this->loadHunk($id); + $old_data = $hunk->getChanges(); + + switch ($storage) { + case DifferentialModernHunk::DATATYPE_TEXT: + $hunk->saveAsText(); + $this->logOkay( + pht('TEXT'), + pht('Convereted hunk to text storage.')); + break; + case DifferentialModernHunk::DATATYPE_FILE: + $hunk->saveAsFile(); + $this->logOkay( + pht('FILE'), + pht('Convereted hunk to file storage.')); + break; + } + + $hunk = $this->loadHunk($id); + $new_data = $hunk->getChanges(); + + if ($old_data !== $new_data) { + throw new Exception( + pht( + 'Integrity check failed: new file data differs fom old data!')); + } + + return 0; + } + + private function loadHunk($id) { + $hunk = id(new DifferentialModernHunk())->load($id); + if (!$hunk) { + throw new PhutilArgumentUsageException( + pht( + 'No hunk exists with ID "%s".', + $id)); + } + + return $hunk; + } + + +} diff --git a/src/applications/differential/storage/DifferentialChangeset.php b/src/applications/differential/storage/DifferentialChangeset.php index 9c718a8765..3656fe6507 100644 --- a/src/applications/differential/storage/DifferentialChangeset.php +++ b/src/applications/differential/storage/DifferentialChangeset.php @@ -1,7 +1,10 @@ getDiff()->hasAutomaticCapability($capability, $viewer); } + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->openTransaction(); + + $hunks = id(new DifferentialModernHunk())->loadAllWhere( + 'changesetID = %d', + $this->getID()); + foreach ($hunks as $hunk) { + $engine->destroyObject($hunk); + } + + $this->delete(); + + $this->saveTransaction(); + } + + } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 8976fdd8f0..f7e85cd835 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -727,7 +727,7 @@ public function destroyObjectPermanently( $this->delete(); foreach ($this->loadChangesets() as $changeset) { - $changeset->delete(); + $engine->destroyObject($changeset); } $properties = id(new DifferentialDiffProperty())->loadAllWhere( diff --git a/src/applications/differential/storage/DifferentialHunk.php b/src/applications/differential/storage/DifferentialHunk.php index fcc7926590..a57d0035eb 100644 --- a/src/applications/differential/storage/DifferentialHunk.php +++ b/src/applications/differential/storage/DifferentialHunk.php @@ -1,7 +1,10 @@ getChangeset()->hasAutomaticCapability($capability, $viewer); } + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); + } + + } diff --git a/src/applications/differential/storage/DifferentialModernHunk.php b/src/applications/differential/storage/DifferentialModernHunk.php index 9f0242ce5f..6f63f680a1 100644 --- a/src/applications/differential/storage/DifferentialModernHunk.php +++ b/src/applications/differential/storage/DifferentialModernHunk.php @@ -15,6 +15,7 @@ final class DifferentialModernHunk extends DifferentialHunk { private $rawData; private $forcedEncoding; + private $fileData; public function getTableName() { return 'differential_hunk_modern'; @@ -87,6 +88,57 @@ public function save() { return parent::save(); } + public function saveAsText() { + $old_type = $this->getDataType(); + $old_data = $this->getData(); + + if ($old_type == self::DATATYPE_TEXT) { + return $this; + } + + $raw_data = $this->getRawData(); + + $this->setDataType(self::DATATYPE_TEXT); + $this->setData($raw_data); + + $result = $this->save(); + + $this->destroyData($old_type, $old_data); + + return $result; + } + + public function saveAsFile() { + $old_type = $this->getDataType(); + $old_data = $this->getData(); + + if ($old_type == self::DATATYPE_FILE) { + return $this; + } + + $raw_data = $this->getRawData(); + + $file = PhabricatorFile::newFromFileData( + $raw_data, + array( + 'name' => 'differential-hunk', + 'mime-type' => 'application/octet-stream', + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + + $this->setDataType(self::DATATYPE_FILE); + $this->setData($file->getPHID()); + + // NOTE: Because hunks don't have a PHID and we just load hunk data with + // the ominipotent viewer, we do not need to attach the file to anything. + + $result = $this->save(); + + $this->destroyData($old_type, $old_data); + + return $result; + } + private function getRawData() { if ($this->rawData === null) { $type = $this->getDataType(); @@ -98,6 +150,8 @@ private function getRawData() { $data = $data; break; case self::DATATYPE_FILE: + $data = $this->loadFileData(); + break; default: throw new Exception( pht('Hunk has unsupported data type "%s"!', $type)); @@ -123,4 +177,75 @@ private function getRawData() { return $this->rawData; } + private function loadFileData() { + if ($this->fileData === null) { + $type = $this->getDataType(); + if ($type !== self::DATATYPE_FILE) { + throw new Exception( + pht( + 'Unable to load file data for hunk with wrong data type ("%s").', + $type)); + } + + $file_phid = $this->getData(); + + $file = $this->loadRawFile($file_phid); + $data = $file->loadFileData(); + + $this->fileData = $data; + } + + return $this->fileData; + } + + private function loadRawFile($file_phid) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->execute(); + if (!$files) { + throw new Exception( + pht( + 'Failed to load file ("%s") with hunk data.', + $file_phid)); + } + + $file = head($files); + + return $file; + } + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $type = $this->getDataType(); + $data = $this->getData(); + + $this->destroyData($type, $data, $engine); + + return parent::destroyObjectPermanently($engine); + } + + + private function destroyData( + $type, + $data, + PhabricatorDestructionEngine $engine = null) { + + if (!$engine) { + $engine = new PhabricatorDestructionEngine(); + } + + switch ($type) { + case self::DATATYPE_FILE: + $file = $this->loadRawFile($data); + $engine->destroyObject($file); + break; + } + } + } From 495ab7363b5bfbf92da3e66acd4edb580bdbb714 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Sep 2017 16:14:10 -0700 Subject: [PATCH 150/865] Remove "Contains Words" constraint from Maniphest Summary: Ref T12819. Obsoleted by the Ferret engine, which is unprototyping shortly. This breaks compatibility in two ways: - `maniphest.query` no longer supports "fullText" (now throws an explicit exception). - Existing saved searches with a "Contains Words" constraint will no longer have that constraint. It seems unlikely (?) that either of these are seeing too much use, and they should be easy to fix. I'll note them in the changelog. Test Plan: Viewed Maniphest, no more "Contains Words" field. Called `maniphest.query` with "fullText", got explicit exception. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18586 --- .../ManiphestQueryConduitAPIMethod.php | 5 ++- .../maniphest/query/ManiphestTaskQuery.php | 38 ------------------- .../query/ManiphestTaskSearchEngine.php | 8 ---- 3 files changed, 4 insertions(+), 47 deletions(-) diff --git a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php index 45373d2587..7ddf3b03eb 100644 --- a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php @@ -102,7 +102,10 @@ protected function execute(ConduitAPIRequest $request) { $full_text = $request->getValue('fullText'); if ($full_text) { - $query->withFullTextSearch($full_text); + throw new Exception( + pht( + 'Parameter "fullText" is no longer supported. Use method '. + '"maniphest.search" with the "query" constraint instead.')); } $status = $request->getValue('status'); diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index f009e5c2ef..cfc69722d8 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -24,8 +24,6 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $subtaskIDs; private $subtypes; - private $fullTextSearch = ''; - private $status = 'status-any'; const STATUS_ANY = 'status-any'; const STATUS_OPEN = 'status-open'; @@ -115,11 +113,6 @@ public function withSubscribers(array $subscribers) { return $this; } - public function withFullTextSearch($fulltext_search) { - $this->fullTextSearch = $fulltext_search; - return $this; - } - public function setGroupBy($group) { $this->groupBy = $group; @@ -329,7 +322,6 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where[] = $this->buildStatusWhereClause($conn); $where[] = $this->buildOwnerWhereClause($conn); - $where[] = $this->buildFullTextWhereClause($conn); if ($this->taskIDs !== null) { $where[] = qsprintf( @@ -481,36 +473,6 @@ private function buildOwnerWhereClause(AphrontDatabaseConnection $conn) { return '('.implode(') OR (', $subclause).')'; } - private function buildFullTextWhereClause(AphrontDatabaseConnection $conn) { - if (!strlen($this->fullTextSearch)) { - return null; - } - - // In doing a fulltext search, we first find all the PHIDs that match the - // fulltext search, and then use that to limit the rest of the search - $fulltext_query = id(new PhabricatorSavedQuery()) - ->setEngineClassName('PhabricatorSearchApplicationSearchEngine') - ->setParameter('query', $this->fullTextSearch); - - // NOTE: Setting this to something larger than 10,000 will raise errors in - // Elasticsearch, and billions of results won't fit in memory anyway. - $fulltext_query->setParameter('limit', 10000); - $fulltext_query->setParameter('types', - array(ManiphestTaskPHIDType::TYPECONST)); - - $fulltext_results = PhabricatorSearchService::executeSearch( - $fulltext_query); - - if (empty($fulltext_results)) { - $fulltext_results = array(null); - } - - return qsprintf( - $conn, - 'task.phid IN (%Ls)', - $fulltext_results); - } - protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $open_statuses = ManiphestTaskStatus::getOpenStatusConstants(); $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 150ec81def..ec1956bd8e 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -86,9 +86,6 @@ protected function buildCustomSearchFields() { pht('Search for tasks with given subtypes.')) ->setDatasource(new ManiphestTaskSubtypeDatasource()) ->setIsHidden($hide_subtypes), - id(new PhabricatorSearchTextField()) - ->setLabel(pht('Contains Words')) - ->setKey('fulltext'), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Open Parents')) ->setKey('hasParents') @@ -144,7 +141,6 @@ protected function getDefaultFieldOrder() { 'statuses', 'priorities', 'subtypes', - 'fulltext', 'hasParents', 'hasSubtasks', 'parentIDs', @@ -220,10 +216,6 @@ protected function buildQueryFromParameters(array $map) { $query->withOpenSubtasks($map['hasSubtasks']); } - if (strlen($map['fulltext'])) { - $query->withFullTextSearch($map['fulltext']); - } - if ($map['parentIDs']) { $query->withParentTaskIDs($map['parentIDs']); } From 6edf98eb3b8a30f9805f50775394d1eb57733dae Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Sep 2017 16:22:39 -0700 Subject: [PATCH 151/865] Unprototype the Ferret UI fields Summary: Ref T12819. Show the new Ferret engine fields (and enable the indexer) unconditionally. Also pull them to the top since they're fairly general-purpose and appear more broadly now, and also they actually work correctly (WOW). Some redundant fields (like "Name Contains" in Repositories and Owners) could probably be removed now, I may clean those up in a followup. Test Plan: Browsed around, saw Ferret fields in UI without "(Prototype)" suffix. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18587 --- .../search/engine/PhabricatorApplicationSearchEngine.php | 3 +++ .../PhabricatorFerretSearchEngineExtension.php | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 6122e66e23..f4c460ce34 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -324,6 +324,9 @@ private function adjustFieldsForDisplay(array $field_map) { $result = $head + $body + $tail; + // Force the fulltext "query" field to the top unconditionally. + $result = array_select_keys($result, array('query')) + $result; + foreach ($this->getHiddenFields() as $hidden_key) { unset($result[$hidden_key]); } diff --git a/src/applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php index 02aadf7336..65822b0a32 100644 --- a/src/applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorFerretSearchEngineExtension.php @@ -6,7 +6,7 @@ final class PhabricatorFerretSearchEngineExtension const EXTENSIONKEY = 'ferret'; public function isExtensionEnabled() { - return PhabricatorEnv::getEnvConfig('phabricator.show-prototypes'); + return true; } public function getExtensionName() { @@ -56,7 +56,7 @@ public function getSearchFields($object) { $fields[] = id(new PhabricatorSearchTextField()) ->setKey('query') - ->setLabel(pht('Query (Prototype)')) + ->setLabel(pht('Query')) ->setDescription(pht('Fulltext search.')); return $fields; From dd3f05ec2503cd92deb005639209b4b55c927b10 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Sep 2017 16:30:35 -0700 Subject: [PATCH 152/865] Remove "Name Contains" query constraint from Diffusion for Repositories Summary: Ref T12819. Obsoleted by the Ferret engine "Query" field. This is a compatibility break, I'll note it in the changelog. Test Plan: Searched for repositories by name with "Query" instead of "Name Contains". Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18588 --- .../repository/query/PhabricatorRepositoryQuery.php | 13 ------------- .../query/PhabricatorRepositorySearchEngine.php | 7 ------- 2 files changed, 20 deletions(-) diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 85b2ea0f0d..ffa9fa1b59 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -8,7 +8,6 @@ final class PhabricatorRepositoryQuery private $callsigns; private $types; private $uuids; - private $nameContains; private $uris; private $datasourceQuery; private $slugs; @@ -116,11 +115,6 @@ public function withUUIDs(array $uuids) { return $this; } - public function withNameContains($contains) { - $this->nameContains = $contains; - return $this; - } - public function withURIs(array $uris) { $this->uris = $uris; return $this; @@ -661,13 +655,6 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $this->uuids); } - if (strlen($this->nameContains)) { - $where[] = qsprintf( - $conn, - 'r.name LIKE %~', - $this->nameContains); - } - if (strlen($this->datasourceQuery)) { // This handles having "rP" match callsigns starting with "P...". $query = trim($this->datasourceQuery); diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index f321a112d9..5cd88183e1 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -24,9 +24,6 @@ protected function buildCustomSearchFields() { id(new PhabricatorSearchStringListField()) ->setLabel(pht('Callsigns')) ->setKey('callsigns'), - id(new PhabricatorSearchTextField()) - ->setLabel(pht('Name Contains')) - ->setKey('name'), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Status')) ->setKey('status') @@ -72,10 +69,6 @@ protected function buildQueryFromParameters(array $map) { $query->withTypes($map['types']); } - if (strlen($map['name'])) { - $query->withNameContains($map['name']); - } - if ($map['uris']) { $query->withURIs($map['uris']); } From 39b74572e6f73cee600c1af71f104aec90577a0c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Sep 2017 16:35:09 -0700 Subject: [PATCH 153/865] Return fulltext tokens from the Ferret fulltext engine Summary: Ref T12819. These render the little "Searched For: X, Y, U V" hint about how something was parsed. (This might get a "substring" color or "title only" color or something in the future.) Test Plan: {F5178807} Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18589 --- .../PhabricatorFerretFulltextStorageEngine.php | 4 ++++ .../policy/PhabricatorCursorPagedPolicyAwareQuery.php | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php index 74e8771de7..b3618db5ff 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php @@ -86,6 +86,10 @@ public function executeSearch(PhabricatorSavedQuery $query) { $type_results[$type] = $results; $metadata += $engine_query->getFerretMetadata(); + + if (!$this->fulltextTokens) { + $this->fulltextTokens = $engine_query->getFerretTokens(); + } } $list = array(); diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 0488ba6133..5cacd66d06 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1469,6 +1469,17 @@ public function withFerretQuery( return $this; } + public function getFerretTokens() { + if (!$this->supportsFerretEngine()) { + throw new Exception( + pht( + 'Query ("%s") does not support the Ferret fulltext engine.', + get_class($this))); + } + + return $this->ferretTokens; + } + public function withFerretConstraint( PhabricatorFerretEngine $engine, array $fulltext_tokens) { From da0a08a7e13e01d5e34bb0a12706a7b4f9e348d6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Sep 2017 16:40:52 -0700 Subject: [PATCH 154/865] Make "mysql" mean "Ferret engine" in Fulltext search Summary: Ref T12819. Swaps constants so existing configurations that use a "mysql" engine now use the Ferret engine, not an InnoDB/MyISAM FULLTEXT engine. Test Plan: Swapped my local config back to "mysql" (the default), saw Ferret engine results in the UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18590 --- src/__phutil_library_map__.php | 2 - ...PhabricatorFerretFulltextStorageEngine.php | 2 +- .../PhabricatorMySQLFulltextStorageEngine.php | 504 ------------------ 3 files changed, 1 insertion(+), 507 deletions(-) delete mode 100644 src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 925dfc66c6..97f5b480a4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3197,7 +3197,6 @@ 'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php', 'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php', 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php', - 'PhabricatorMySQLFulltextStorageEngine' => 'applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php', 'PhabricatorMySQLSearchHost' => 'infrastructure/cluster/search/PhabricatorMySQLSearchHost.php', 'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php', 'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php', @@ -8588,7 +8587,6 @@ 'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController', 'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', - 'PhabricatorMySQLFulltextStorageEngine' => 'PhabricatorFulltextStorageEngine', 'PhabricatorMySQLSearchHost' => 'PhabricatorSearchHost', 'PhabricatorMySQLSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorNamedQuery' => array( diff --git a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php index b3618db5ff..a7d9570c57 100644 --- a/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorFerretFulltextStorageEngine.php @@ -7,7 +7,7 @@ final class PhabricatorFerretFulltextStorageEngine private $engineLimits; public function getEngineIdentifier() { - return 'ferret'; + return 'mysql'; } public function getHostType() { diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php deleted file mode 100644 index c2e38d2db7..0000000000 --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ /dev/null @@ -1,504 +0,0 @@ -getPHID(); - if (!$phid) { - throw new Exception(pht('Document has no PHID!')); - } - - $store = new PhabricatorSearchDocument(); - $store->setPHID($doc->getPHID()); - $store->setDocumentType($doc->getDocumentType()); - $store->setDocumentTitle($doc->getDocumentTitle()); - $store->setDocumentCreated($doc->getDocumentCreated()); - $store->setDocumentModified($doc->getDocumentModified()); - $store->replace(); - - $conn_w = $store->establishConnection('w'); - - $stemmer = new PhutilSearchStemmer(); - - $field_dao = new PhabricatorSearchDocumentField(); - queryfx( - $conn_w, - 'DELETE FROM %T WHERE phid = %s', - $field_dao->getTableName(), - $phid); - foreach ($doc->getFieldData() as $field) { - list($ftype, $corpus, $aux_phid) = $field; - - $stemmed_corpus = $stemmer->stemCorpus($corpus); - - queryfx( - $conn_w, - 'INSERT INTO %T - (phid, phidType, field, auxPHID, corpus, stemmedCorpus) '. - 'VALUES (%s, %s, %s, %ns, %s, %s)', - $field_dao->getTableName(), - $phid, - $doc->getDocumentType(), - $ftype, - $aux_phid, - $corpus, - $stemmed_corpus); - } - - - $sql = array(); - foreach ($doc->getRelationshipData() as $relationship) { - list($rtype, $to_phid, $to_type, $time) = $relationship; - $sql[] = qsprintf( - $conn_w, - '(%s, %s, %s, %s, %d)', - $phid, - $to_phid, - $rtype, - $to_type, - $time); - } - - $rship_dao = new PhabricatorSearchDocumentRelationship(); - queryfx( - $conn_w, - 'DELETE FROM %T WHERE phid = %s', - $rship_dao->getTableName(), - $phid); - if ($sql) { - queryfx( - $conn_w, - 'INSERT INTO %T '. - '(phid, relatedPHID, relation, relatedType, relatedTime) '. - 'VALUES %Q', - $rship_dao->getTableName(), - implode(', ', $sql)); - } - - } - - public function executeSearch(PhabricatorSavedQuery $query) { - $table = new PhabricatorSearchDocument(); - $document_table = $table->getTableName(); - $conn = $table->establishConnection('r'); - - $subquery = $this->newFulltextSubquery($query, $conn); - - $offset = (int)$query->getParameter('offset', 0); - $limit = (int)$query->getParameter('limit', 25); - - // NOTE: We must JOIN the subquery in order to apply a limit. - $results = queryfx_all( - $conn, - 'SELECT - documentPHID, - MAX(fieldScore) AS documentScore - FROM (%Q) query - JOIN %T root ON query.documentPHID = root.phid - GROUP BY documentPHID - ORDER BY documentScore DESC - LIMIT %d, %d', - $subquery, - $document_table, - $offset, - $limit); - - return ipull($results, 'documentPHID'); - } - - private function newFulltextSubquery( - PhabricatorSavedQuery $query, - AphrontDatabaseConnection $conn) { - - $field = new PhabricatorSearchDocumentField(); - $field_table = $field->getTableName(); - - $document = new PhabricatorSearchDocument(); - $document_table = $document->getTableName(); - - $select = array(); - $select[] = 'document.phid AS documentPHID'; - - $join = array(); - $where = array(); - - $title_field = PhabricatorSearchDocumentFieldType::FIELD_TITLE; - $title_boost = 1024; - - $stemmer = new PhutilSearchStemmer(); - - $raw_query = $query->getParameter('query'); - $raw_query = trim($raw_query); - if (strlen($raw_query)) { - $compiler = PhabricatorSearchDocument::newQueryCompiler() - ->setStemmer($stemmer); - - $tokens = $compiler->newTokens($raw_query); - - list($min_length, $stopword_list) = $this->getEngineLimits($conn); - - // Process all the parts of the user's query so we can show them which - // parts we searched for and which ones we ignored. - $fulltext_tokens = array(); - foreach ($tokens as $key => $token) { - $fulltext_token = id(new PhabricatorFulltextToken()) - ->setToken($token); - - $fulltext_tokens[$key] = $fulltext_token; - - $value = $token->getValue(); - - // If the value is unquoted, we'll stem it in the query, so stem it - // here before performing filtering tests. See T12596. - if (!$token->isQuoted()) { - $value = $stemmer->stemToken($value); - } - - if ($this->isShortToken($value, $min_length)) { - $fulltext_token->setIsShort(true); - continue; - } - - if (isset($stopword_list[phutil_utf8_strtolower($value)])) { - $fulltext_token->setIsStopword(true); - continue; - } - } - $this->fulltextTokens = $fulltext_tokens; - - // Remove tokens which aren't queryable from the query. This is mostly - // a workaround for the peculiar behaviors described in T12137. - foreach ($this->fulltextTokens as $key => $fulltext_token) { - if (!$fulltext_token->isQueryable()) { - unset($tokens[$key]); - } - } - - if (!$tokens) { - throw new PhutilSearchQueryCompilerSyntaxException( - pht( - 'All of your search terms are too short or too common to '. - 'appear in the search index. Search for longer or more '. - 'distinctive terms.')); - } - - $queries = array(); - $queries[] = $compiler->compileLiteralQuery($tokens); - $queries[] = $compiler->compileStemmedQuery($tokens); - $compiled_query = implode(' ', array_filter($queries)); - } else { - $compiled_query = null; - } - - if (strlen($compiled_query)) { - $select[] = qsprintf( - $conn, - 'IF(field.field = %s, %d, 0) + - MATCH(corpus, stemmedCorpus) AGAINST (%s IN BOOLEAN MODE) - AS fieldScore', - $title_field, - $title_boost, - $compiled_query); - - $join[] = qsprintf( - $conn, - '%T field ON field.phid = document.phid', - $field_table); - - $where[] = qsprintf( - $conn, - 'MATCH(corpus, stemmedCorpus) AGAINST (%s IN BOOLEAN MODE)', - $compiled_query); - - if ($query->getParameter('field')) { - $where[] = qsprintf( - $conn, - 'field.field = %s', - $field); - } - } else { - $select[] = qsprintf( - $conn, - 'document.documentCreated AS fieldScore'); - } - - $exclude = $query->getParameter('exclude'); - if ($exclude) { - $where[] = qsprintf( - $conn, - 'document.phid != %s', - $exclude); - } - - $types = $query->getParameter('types'); - if ($types) { - if (strlen($compiled_query)) { - $where[] = qsprintf( - $conn, - 'field.phidType IN (%Ls)', - $types); - } - - $where[] = qsprintf( - $conn, - 'document.documentType IN (%Ls)', - $types); - } - - $join[] = $this->joinRelationship( - $conn, - $query, - 'authorPHIDs', - PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR); - - $statuses = $query->getParameter('statuses', array()); - $statuses = array_fuse($statuses); - $open_rel = PhabricatorSearchRelationship::RELATIONSHIP_OPEN; - $closed_rel = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED; - $include_open = !empty($statuses[$open_rel]); - $include_closed = !empty($statuses[$closed_rel]); - - if ($include_open && !$include_closed) { - $join[] = $this->joinRelationship( - $conn, - $query, - 'statuses', - $open_rel, - true); - } else if ($include_closed && !$include_open) { - $join[] = $this->joinRelationship( - $conn, - $query, - 'statuses', - $closed_rel, - true); - } - - if ($query->getParameter('withAnyOwner')) { - $join[] = $this->joinRelationship( - $conn, - $query, - 'withAnyOwner', - PhabricatorSearchRelationship::RELATIONSHIP_OWNER, - true); - } else if ($query->getParameter('withUnowned')) { - $join[] = $this->joinRelationship( - $conn, - $query, - 'withUnowned', - PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED, - true); - } else { - $join[] = $this->joinRelationship( - $conn, - $query, - 'ownerPHIDs', - PhabricatorSearchRelationship::RELATIONSHIP_OWNER); - } - - $join[] = $this->joinRelationship( - $conn, - $query, - 'subscriberPHIDs', - PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER); - - $join[] = $this->joinRelationship( - $conn, - $query, - 'projectPHIDs', - PhabricatorSearchRelationship::RELATIONSHIP_PROJECT); - - $join[] = $this->joinRelationship( - $conn, - $query, - 'repository', - PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY); - - $select = implode(', ', $select); - - $join = array_filter($join); - foreach ($join as $key => $clause) { - $join[$key] = ' JOIN '.$clause; - } - $join = implode(' ', $join); - - if ($where) { - $where = 'WHERE '.implode(' AND ', $where); - } else { - $where = ''; - } - - if (strlen($compiled_query)) { - $order = ''; - } else { - // When not executing a query, order by document creation date. This - // is the default view in object browser dialogs, like "Close Duplicate". - $order = qsprintf( - $conn, - 'ORDER BY document.documentCreated DESC'); - } - - return qsprintf( - $conn, - 'SELECT %Q FROM %T document %Q %Q %Q LIMIT 1000', - $select, - $document_table, - $join, - $where, - $order); - } - - protected function joinRelationship( - AphrontDatabaseConnection $conn, - PhabricatorSavedQuery $query, - $field, - $type, - $is_existence = false) { - - $sql = qsprintf( - $conn, - '%T AS %C ON %C.phid = document.phid AND %C.relation = %s', - id(new PhabricatorSearchDocumentRelationship())->getTableName(), - $field, - $field, - $field, - $type); - - if (!$is_existence) { - $phids = $query->getParameter($field, array()); - if (!$phids) { - return null; - } - $sql .= qsprintf( - $conn, - ' AND %C.relatedPHID in (%Ls)', - $field, - $phids); - } - - return $sql; - } - - public function indexExists() { - return true; - } - - public function getIndexStats() { - return false; - } - - public function getFulltextTokens() { - return $this->fulltextTokens; - } - - private function getEngineLimits(AphrontDatabaseConnection $conn) { - if ($this->engineLimits === null) { - $this->engineLimits = $this->newEngineLimits($conn); - } - return $this->engineLimits; - } - - private function newEngineLimits(AphrontDatabaseConnection $conn) { - // First, try InnoDB. Some database may not have both table engines, so - // selecting variables from missing table engines can fail and throw. - - try { - $result = queryfx_one( - $conn, - 'SELECT @@innodb_ft_min_token_size innodb_max, - @@innodb_ft_server_stopword_table innodb_stopword_config'); - } catch (AphrontQueryException $ex) { - $result = null; - } - - if ($result) { - $min_len = $result['innodb_max']; - - $stopword_config = $result['innodb_stopword_config']; - if (preg_match('(/)', $stopword_config)) { - // If the setting is nonempty and contains a slash, query the - // table the user has configured. - $parts = explode('/', $stopword_config); - list($stopword_database, $stopword_table) = $parts; - } else { - // Otherwise, query the InnoDB default stopword table. - $stopword_database = 'INFORMATION_SCHEMA'; - $stopword_table = 'INNODB_FT_DEFAULT_STOPWORD'; - } - - $stopwords = queryfx_all( - $conn, - 'SELECT * FROM %T.%T', - $stopword_database, - $stopword_table); - $stopwords = ipull($stopwords, 'value'); - $stopwords = array_fuse($stopwords); - - return array($min_len, $stopwords); - } - - // If InnoDB fails, try MyISAM. - $result = queryfx_one( - $conn, - 'SELECT - @@ft_min_word_len myisam_max, - @@ft_stopword_file myisam_stopwords'); - - $min_len = $result['myisam_max']; - - $file = $result['myisam_stopwords']; - if (preg_match('(/resources/sql/stopwords\.txt\z)', $file)) { - // If this is set to something that looks like the Phabricator - // stopword file, read that. - $file = 'stopwords.txt'; - } else { - // Otherwise, just use the default stopwords. This might be wrong - // but we can't read the actual value dynamically and reading - // whatever file the variable is set to could be a big headache - // to get right from a security perspective. - $file = 'stopwords_myisam.txt'; - } - - $root = dirname(phutil_get_library_root('phabricator')); - $data = Filesystem::readFile($root.'/resources/sql/'.$file); - $stopwords = explode("\n", $data); - $stopwords = array_filter($stopwords); - $stopwords = array_fuse($stopwords); - - return array($min_len, $stopwords); - } - - private function isShortToken($value, $min_length) { - // NOTE: The engine tokenizes internally on periods, so terms in the form - // "ab.cd", where short substrings are separated by periods, do not produce - // any queryable tokens. These terms are meaningful if at least one - // substring is longer than the minimum length, like "example.py". See - // T12928. This also applies to words with intermediate apostrophes, like - // "to's". - - $parts = preg_split('/[.\']+/', $value); - - foreach ($parts as $part) { - if (phutil_utf8_strlen($part) >= $min_length) { - return false; - } - } - - return true; - } - -} From e6f0f865187a4429eca5deafebe198203e9a6fcc Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Sep 2017 07:35:15 -0700 Subject: [PATCH 155/865] Document Ferret engine fulltext search features Summary: Ref T12819. Adds some documentation for `-term`, `~term`, `title:term`, etc. Test Plan: Read documentation. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18592 --- src/docs/user/userguide/search.diviner | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/docs/user/userguide/search.diviner b/src/docs/user/userguide/search.diviner index f2d564bc75..875edc6d0f 100644 --- a/src/docs/user/userguide/search.diviner +++ b/src/docs/user/userguide/search.diviner @@ -123,3 +123,53 @@ Another useful function is the `viewer()` function, which works as though you'd typed your own username when you run the query. However, if you send the query to someone else, it will show results for //their// username when they run it. This can be particularly useful when creating dashboard panels. + + +Fulltext Search +=============== + +Global search and some applications provide **fulltext search**. In +applications, this is a field called {nav Query}. + +Fulltext search allows you to search the text content of objects and supports +some special syntax. These features are supported: + + - Substring search with `~platypus`. + - Field search with `title:platypus`. + - Filtering out matches with `-platypus`. + - Quoted terms with `"platypus attorney"`. + - Combining features with `title:~"platypus attorney"`. + +See below for more detail. + +**Substrings**: Normally, query terms are searched for as words, so searching +for `read` won't find documents which only contain the word `threaded`, even +though "read" is a substring of "threaded". With the substring operator, `~`, +you can search for substrings instead: the query `~read` will match documents +which contain that text anywhere, even in the middle of a word. + +**Quoted Terms**: When you search for multiple terms, documents which match +each term will be returned, even if the terms are not adjacent in the document. +For example, the query `void star` will match a document titled `A star in the +void`, because it matches both `void` and `star`. To search for an exact +sequence of terms, quote them: `"void star"`. This query will only match +documents which use those terms as written. + +**Stemming**: Searching for a term like `rearming` will find documents which +contain variations of the word, like `rearm`, `rearms`, and `rearmed`. To +search for an an exact word, quote the term: `"rearming"`. + +**Field Search**: By default, query terms are searched for in the title, body, +and comments. If you only want to search for a term in titles, use `title:`. +For example, `title:platypus` only finds documents with that term in the +title. This can be combined with other operators, for example `title:~platypus` +or `title:"platypus attorney"`. These scopes are also supported: + + - `title:...` searches titles. + - `body:...` searches bodies (descriptions or summaries). + - `core:...` searches titles and bodies, but not comments. + - `comments:...` searches only comments. + +**Filtering Matches**: You can remove documents which match certain terms from +the result set with `-`. For example: `platypus -mammal`. Documents which match +negated terms will be filtered out of the result set. From fdc0d8c2f6f3543de6a68fe26bdeda2bf9c530ed Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Sep 2017 07:44:29 -0700 Subject: [PATCH 156/865] Fix an issue with selecting the right stemmed ngrams with Ferret engine queries Summary: Ref T12819. In D18581, I corrected one bug (ngram selection for terms) but introduced a minor new bug. We now pass `' query '` (term corpus with boundary spaces) to the stemmer, but it bails out on this since English words don't start with spaces. Trim these extra boundary spaces off before invoking the stemmer. The practical effect of this is that searching for non-stem variations of a word ("detection") now finds stemmed variations again ("detect"). Prior to fixing this bug, the stem could find longer variations but not the other way around. Test Plan: Searched for "detection", found results matching "detect" after patch (and saw same results for "detect" and "detection"). Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18593 --- .../query/policy/PhabricatorCursorPagedPolicyAwareQuery.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 5cacd66d06..cd4ccf30b5 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1683,6 +1683,9 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { // If this is a stemmed term, only look for ngrams present in both the // unstemmed and stemmed variations. if ($is_stemmed) { + // Trim the boundary space characters so the stemmer recognizes this + // is (or, at least, may be) a normal word and activates. + $terms_value = trim($terms_value, ' '); $stem_value = $stemmer->stemToken($terms_value); $stem_ngrams = $engine->getTermNgramsFromString($stem_value); $ngrams = array_intersect($ngrams, $stem_ngrams); From 124e580f6ec776fcb03d74883f27f998430bad09 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Sep 2017 08:16:28 -0700 Subject: [PATCH 157/865] Issue upgrade guidance to rebuild indexes for the Ferret engine Summary: Ref T12819. This is shipping, so issue upgrade guidance to instruct installs to rebuild the index. Also generate a new `quickstart.sql` since we haven't regenerated in a bit and there's been a large amount of table churn fairly recently. Test Plan: Ran `bin/storage upgrade`, saw guidance notification in UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12819 Differential Revision: https://secure.phabricator.com/D18594 --- .../20170912.ferret.01.activity.php | 19 + resources/sql/quickstart.sql | 552 +++++++++++++++++- 2 files changed, 549 insertions(+), 22 deletions(-) create mode 100644 resources/sql/autopatches/20170912.ferret.01.activity.php diff --git a/resources/sql/autopatches/20170912.ferret.01.activity.php b/resources/sql/autopatches/20170912.ferret.01.activity.php new file mode 100644 index 0000000000..cafd60c928 --- /dev/null +++ b/resources/sql/autopatches/20170912.ferret.01.activity.php @@ -0,0 +1,19 @@ +loadAllWhere('1 = 1 LIMIT 1'); +if (!$users) { + return; +} + +try { + id(new PhabricatorConfigManualActivity()) + ->setActivityType(PhabricatorConfigManualActivity::TYPE_REINDEX) + ->save(); +} catch (AphrontDuplicateKeyQueryException $ex) { + // If we've already noted that this activity is required, just move on. +} diff --git a/resources/sql/quickstart.sql b/resources/sql/quickstart.sql index b992c03522..e9ae4465a4 100644 --- a/resources/sql/quickstart.sql +++ b/resources/sql/quickstart.sql @@ -95,6 +95,42 @@ CREATE TABLE `calendar_event` ( KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `calendar_event_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `calendar_event_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `calendar_event_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `calendar_eventinvitee` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `eventPHID` varbinary(64) NOT NULL, @@ -452,7 +488,7 @@ CREATE TABLE `daemon_log` ( PRIMARY KEY (`id`), UNIQUE KEY `key_daemonID` (`daemonID`), KEY `status` (`status`), - KEY `dateCreated` (`dateCreated`) + KEY `key_modified` (`dateModified`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `daemon_logevent` ( @@ -602,17 +638,6 @@ CREATE TABLE `differential_difftransaction` ( KEY `key_object` (`objectPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; -CREATE TABLE `differential_draft` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `objectPHID` varbinary(64) NOT NULL, - `authorPHID` varbinary(64) NOT NULL, - `draftKey` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, - `dateCreated` int(10) unsigned NOT NULL, - `dateModified` int(10) unsigned NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `key_unique` (`objectPHID`,`authorPHID`,`draftKey`) -) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; - CREATE TABLE `differential_hiddencomment` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `userPHID` varbinary(64) NOT NULL, @@ -697,6 +722,42 @@ CREATE TABLE `differential_revision` ( KEY `key_status` (`status`,`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `differential_revision_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `differential_revision_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `differential_revision_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `differential_revisionhash` ( `revisionID` int(10) unsigned NOT NULL, `type` binary(4) NOT NULL, @@ -1767,7 +1828,7 @@ CREATE TABLE `maniphest_task` ( `phid` varbinary(64) NOT NULL, `authorPHID` varbinary(64) NOT NULL, `ownerPHID` varbinary(64) DEFAULT NULL, - `status` varchar(12) COLLATE {$COLLATE_TEXT} NOT NULL, + `status` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, `priority` int(10) unsigned NOT NULL, `title` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, `originalTitle` longtext COLLATE {$COLLATE_TEXT} NOT NULL, @@ -1801,6 +1862,42 @@ CREATE TABLE `maniphest_task` ( KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `maniphest_task_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `maniphest_task_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `maniphest_task_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `maniphest_transaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -1857,7 +1954,7 @@ CREATE TABLE `patch_status` ( PRIMARY KEY (`patch`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; -INSERT INTO `patch_status` VALUES ('phabricator:000.project.sql',1492953699,NULL),('phabricator:0000.legacy.sql',1492953699,NULL),('phabricator:001.maniphest_projects.sql',1492953699,NULL),('phabricator:002.oauth.sql',1492953699,NULL),('phabricator:003.more_oauth.sql',1492953699,NULL),('phabricator:004.daemonrepos.sql',1492953699,NULL),('phabricator:005.workers.sql',1492953699,NULL),('phabricator:006.repository.sql',1492953699,NULL),('phabricator:007.daemonlog.sql',1492953699,NULL),('phabricator:008.repoopt.sql',1492953699,NULL),('phabricator:009.repo_summary.sql',1492953699,NULL),('phabricator:010.herald.sql',1492953699,NULL),('phabricator:011.badcommit.sql',1492953699,NULL),('phabricator:012.dropphidtype.sql',1492953699,NULL),('phabricator:013.commitdetail.sql',1492953699,NULL),('phabricator:014.shortcuts.sql',1492953699,NULL),('phabricator:015.preferences.sql',1492953699,NULL),('phabricator:016.userrealnameindex.sql',1492953699,NULL),('phabricator:017.sessionkeys.sql',1492953700,NULL),('phabricator:018.owners.sql',1492953700,NULL),('phabricator:019.arcprojects.sql',1492953700,NULL),('phabricator:020.pathcapital.sql',1492953700,NULL),('phabricator:021.xhpastview.sql',1492953700,NULL),('phabricator:022.differentialcommit.sql',1492953700,NULL),('phabricator:023.dxkeys.sql',1492953700,NULL),('phabricator:024.mlistkeys.sql',1492953700,NULL),('phabricator:025.commentopt.sql',1492953700,NULL),('phabricator:026.diffpropkey.sql',1492953700,NULL),('phabricator:027.metamtakeys.sql',1492953700,NULL),('phabricator:028.systemagent.sql',1492953700,NULL),('phabricator:029.cursors.sql',1492953700,NULL),('phabricator:030.imagemacro.sql',1492953700,NULL),('phabricator:031.workerrace.sql',1492953700,NULL),('phabricator:032.viewtime.sql',1492953700,NULL),('phabricator:033.privtest.sql',1492953700,NULL),('phabricator:034.savedheader.sql',1492953700,NULL),('phabricator:035.proxyimage.sql',1492953700,NULL),('phabricator:036.mailkey.sql',1492953700,NULL),('phabricator:037.setuptest.sql',1492953700,NULL),('phabricator:038.admin.sql',1492953700,NULL),('phabricator:039.userlog.sql',1492953700,NULL),('phabricator:040.transform.sql',1492953700,NULL),('phabricator:041.heraldrepetition.sql',1492953700,NULL),('phabricator:042.commentmetadata.sql',1492953700,NULL),('phabricator:043.pastebin.sql',1492953700,NULL),('phabricator:044.countdown.sql',1492953700,NULL),('phabricator:045.timezone.sql',1492953700,NULL),('phabricator:046.conduittoken.sql',1492953700,NULL),('phabricator:047.projectstatus.sql',1492953700,NULL),('phabricator:048.relationshipkeys.sql',1492953700,NULL),('phabricator:049.projectowner.sql',1492953700,NULL),('phabricator:050.taskdenormal.sql',1492953700,NULL),('phabricator:051.projectfilter.sql',1492953700,NULL),('phabricator:052.pastelanguage.sql',1492953700,NULL),('phabricator:053.feed.sql',1492953700,NULL),('phabricator:054.subscribers.sql',1492953700,NULL),('phabricator:055.add_author_to_files.sql',1492953700,NULL),('phabricator:056.slowvote.sql',1492953700,NULL),('phabricator:057.parsecache.sql',1492953701,NULL),('phabricator:058.missingkeys.sql',1492953701,NULL),('phabricator:059.engines.php',1492953701,NULL),('phabricator:060.phriction.sql',1492953701,NULL),('phabricator:061.phrictioncontent.sql',1492953701,NULL),('phabricator:062.phrictionmenu.sql',1492953701,NULL),('phabricator:063.pasteforks.sql',1492953701,NULL),('phabricator:064.subprojects.sql',1492953701,NULL),('phabricator:065.sshkeys.sql',1492953701,NULL),('phabricator:066.phrictioncontent.sql',1492953701,NULL),('phabricator:067.preferences.sql',1492953701,NULL),('phabricator:068.maniphestauxiliarystorage.sql',1492953701,NULL),('phabricator:069.heraldxscript.sql',1492953701,NULL),('phabricator:070.differentialaux.sql',1492953701,NULL),('phabricator:071.contentsource.sql',1492953701,NULL),('phabricator:072.blamerevert.sql',1492953701,NULL),('phabricator:073.reposymbols.sql',1492953701,NULL),('phabricator:074.affectedpath.sql',1492953701,NULL),('phabricator:075.revisionhash.sql',1492953701,NULL),('phabricator:076.indexedlanguages.sql',1492953701,NULL),('phabricator:077.originalemail.sql',1492953701,NULL),('phabricator:078.nametoken.sql',1492953701,NULL),('phabricator:079.nametokenindex.php',1492953701,NULL),('phabricator:080.filekeys.sql',1492953701,NULL),('phabricator:081.filekeys.php',1492953701,NULL),('phabricator:082.xactionkey.sql',1492953701,NULL),('phabricator:083.dxviewtime.sql',1492953701,NULL),('phabricator:084.pasteauthorkey.sql',1492953701,NULL),('phabricator:085.packagecommitrelationship.sql',1492953701,NULL),('phabricator:086.formeraffil.sql',1492953701,NULL),('phabricator:087.phrictiondelete.sql',1492953701,NULL),('phabricator:088.audit.sql',1492953701,NULL),('phabricator:089.projectwiki.sql',1492953701,NULL),('phabricator:090.forceuniqueprojectnames.php',1492953701,NULL),('phabricator:091.uniqueslugkey.sql',1492953701,NULL),('phabricator:092.dropgithubnotification.sql',1492953701,NULL),('phabricator:093.gitremotes.php',1492953701,NULL),('phabricator:094.phrictioncolumn.sql',1492953701,NULL),('phabricator:095.directory.sql',1492953701,NULL),('phabricator:096.filename.sql',1492953701,NULL),('phabricator:097.heraldruletypes.sql',1492953701,NULL),('phabricator:098.heraldruletypemigration.php',1492953701,NULL),('phabricator:099.drydock.sql',1492953701,NULL),('phabricator:100.projectxaction.sql',1492953701,NULL),('phabricator:101.heraldruleapplied.sql',1492953701,NULL),('phabricator:102.heraldcleanup.php',1492953701,NULL),('phabricator:103.heraldedithistory.sql',1492953701,NULL),('phabricator:104.searchkey.sql',1492953702,NULL),('phabricator:105.mimetype.sql',1492953702,NULL),('phabricator:106.chatlog.sql',1492953702,NULL),('phabricator:107.oauthserver.sql',1492953702,NULL),('phabricator:108.oauthscope.sql',1492953702,NULL),('phabricator:109.oauthclientphidkey.sql',1492953702,NULL),('phabricator:110.commitaudit.sql',1492953702,NULL),('phabricator:111.commitauditmigration.php',1492953702,NULL),('phabricator:112.oauthaccesscoderedirecturi.sql',1492953702,NULL),('phabricator:113.lastreviewer.sql',1492953702,NULL),('phabricator:114.auditrequest.sql',1492953702,NULL),('phabricator:115.prepareutf8.sql',1492953702,NULL),('phabricator:116.utf8-backup-first-expect-wait.sql',1492953704,NULL),('phabricator:117.repositorydescription.php',1492953704,NULL),('phabricator:118.auditinline.sql',1492953704,NULL),('phabricator:119.filehash.sql',1492953704,NULL),('phabricator:120.noop.sql',1492953704,NULL),('phabricator:121.drydocklog.sql',1492953704,NULL),('phabricator:122.flag.sql',1492953704,NULL),('phabricator:123.heraldrulelog.sql',1492953704,NULL),('phabricator:124.subpriority.sql',1492953704,NULL),('phabricator:125.ipv6.sql',1492953704,NULL),('phabricator:126.edges.sql',1492953704,NULL),('phabricator:127.userkeybody.sql',1492953704,NULL),('phabricator:128.phabricatorcom.sql',1492953704,NULL),('phabricator:129.savedquery.sql',1492953704,NULL),('phabricator:130.denormalrevisionquery.sql',1492953704,NULL),('phabricator:131.migraterevisionquery.php',1492953704,NULL),('phabricator:132.phame.sql',1492953704,NULL),('phabricator:133.imagemacro.sql',1492953704,NULL),('phabricator:134.emptysearch.sql',1492953704,NULL),('phabricator:135.datecommitted.sql',1492953704,NULL),('phabricator:136.sex.sql',1492953704,NULL),('phabricator:137.auditmetadata.sql',1492953704,NULL),('phabricator:138.notification.sql',1492953704,NULL),('phabricator:20121209.pholioxactions.sql',1492953705,NULL),('phabricator:20121209.xmacroadd.sql',1492953705,NULL),('phabricator:20121209.xmacromigrate.php',1492953705,NULL),('phabricator:20121209.xmacromigratekey.sql',1492953705,NULL),('phabricator:20121220.generalcache.sql',1492953705,NULL),('phabricator:20121226.config.sql',1492953705,NULL),('phabricator:20130101.confxaction.sql',1492953705,NULL),('phabricator:20130102.metamtareceivedmailmessageidhash.sql',1492953705,NULL),('phabricator:20130103.filemetadata.sql',1492953705,NULL),('phabricator:20130111.conpherence.sql',1492953705,NULL),('phabricator:20130127.altheraldtranscript.sql',1492953705,NULL),('phabricator:20130131.conpherencepics.sql',1492953705,NULL),('phabricator:20130201.revisionunsubscribed.php',1492953705,NULL),('phabricator:20130201.revisionunsubscribed.sql',1492953705,NULL),('phabricator:20130214.chatlogchannel.sql',1492953705,NULL),('phabricator:20130214.chatlogchannelid.sql',1492953705,NULL),('phabricator:20130214.token.sql',1492953705,NULL),('phabricator:20130215.phabricatorfileaddttl.sql',1492953705,NULL),('phabricator:20130217.cachettl.sql',1492953705,NULL),('phabricator:20130218.longdaemon.sql',1492953705,NULL),('phabricator:20130218.updatechannelid.php',1492953705,NULL),('phabricator:20130219.commitsummary.sql',1492953705,NULL),('phabricator:20130219.commitsummarymig.php',1492953705,NULL),('phabricator:20130222.dropchannel.sql',1492953705,NULL),('phabricator:20130226.commitkey.sql',1492953705,NULL),('phabricator:20130304.lintauthor.sql',1492953705,NULL),('phabricator:20130310.xactionmeta.sql',1492953705,NULL),('phabricator:20130317.phrictionedge.sql',1492953705,NULL),('phabricator:20130319.conpherence.sql',1492953705,NULL),('phabricator:20130319.phabricatorfileexplicitupload.sql',1492953705,NULL),('phabricator:20130320.phlux.sql',1492953705,NULL),('phabricator:20130321.token.sql',1492953705,NULL),('phabricator:20130322.phortune.sql',1492953705,NULL),('phabricator:20130323.phortunepayment.sql',1492953705,NULL),('phabricator:20130324.phortuneproduct.sql',1492953705,NULL),('phabricator:20130330.phrequent.sql',1492953705,NULL),('phabricator:20130403.conpherencecache.sql',1492953705,NULL),('phabricator:20130403.conpherencecachemig.php',1492953705,NULL),('phabricator:20130409.commitdrev.php',1492953705,NULL),('phabricator:20130417.externalaccount.sql',1492953705,NULL),('phabricator:20130423.conpherenceindices.sql',1492953706,NULL),('phabricator:20130423.phortunepaymentrevised.sql',1492953706,NULL),('phabricator:20130423.updateexternalaccount.sql',1492953705,NULL),('phabricator:20130426.search_savedquery.sql',1492953706,NULL),('phabricator:20130502.countdownrevamp1.sql',1492953706,NULL),('phabricator:20130502.countdownrevamp2.php',1492953706,NULL),('phabricator:20130502.countdownrevamp3.sql',1492953706,NULL),('phabricator:20130507.releephrqmailkey.sql',1492953706,NULL),('phabricator:20130507.releephrqmailkeypop.php',1492953706,NULL),('phabricator:20130507.releephrqsimplifycols.sql',1492953706,NULL),('phabricator:20130508.releephtransactions.sql',1492953706,NULL),('phabricator:20130508.releephtransactionsmig.php',1492953706,NULL),('phabricator:20130508.search_namedquery.sql',1492953706,NULL),('phabricator:20130513.receviedmailstatus.sql',1492953706,NULL),('phabricator:20130519.diviner.sql',1492953706,NULL),('phabricator:20130521.dropconphimages.sql',1492953706,NULL),('phabricator:20130523.maniphest_owners.sql',1492953706,NULL),('phabricator:20130524.repoxactions.sql',1492953706,NULL),('phabricator:20130529.macroauthor.sql',1492953706,NULL),('phabricator:20130529.macroauthormig.php',1492953706,NULL),('phabricator:20130530.macrodatekey.sql',1492953706,NULL),('phabricator:20130530.pastekeys.sql',1492953706,NULL),('phabricator:20130530.sessionhash.php',1492953706,NULL),('phabricator:20130531.filekeys.sql',1492953706,NULL),('phabricator:20130602.morediviner.sql',1492953706,NULL),('phabricator:20130602.namedqueries.sql',1492953706,NULL),('phabricator:20130606.userxactions.sql',1492953706,NULL),('phabricator:20130607.xaccount.sql',1492953706,NULL),('phabricator:20130611.migrateoauth.php',1492953706,NULL),('phabricator:20130611.nukeldap.php',1492953706,NULL),('phabricator:20130613.authdb.sql',1492953706,NULL),('phabricator:20130619.authconf.php',1492953706,NULL),('phabricator:20130620.diffxactions.sql',1492953706,NULL),('phabricator:20130621.diffcommentphid.sql',1492953706,NULL),('phabricator:20130621.diffcommentphidmig.php',1492953706,NULL),('phabricator:20130621.diffcommentunphid.sql',1492953706,NULL),('phabricator:20130622.doorkeeper.sql',1492953706,NULL),('phabricator:20130628.legalpadv0.sql',1492953706,NULL),('phabricator:20130701.conduitlog.sql',1492953706,NULL),('phabricator:20130703.legalpaddocdenorm.php',1492953707,NULL),('phabricator:20130703.legalpaddocdenorm.sql',1492953707,NULL),('phabricator:20130709.droptimeline.sql',1492953707,NULL),('phabricator:20130709.legalpadsignature.sql',1492953707,NULL),('phabricator:20130711.pholioimageobsolete.php',1492953707,NULL),('phabricator:20130711.pholioimageobsolete.sql',1492953707,NULL),('phabricator:20130711.pholioimageobsolete2.sql',1492953707,NULL),('phabricator:20130711.trimrealnames.php',1492953707,NULL),('phabricator:20130714.votexactions.sql',1492953707,NULL),('phabricator:20130715.votecomments.php',1492953707,NULL),('phabricator:20130715.voteedges.sql',1492953707,NULL),('phabricator:20130716.archivememberlessprojects.php',1492953707,NULL),('phabricator:20130722.pholioreplace.sql',1492953707,NULL),('phabricator:20130723.taskstarttime.sql',1492953707,NULL),('phabricator:20130726.ponderxactions.sql',1492953707,NULL),('phabricator:20130727.ponderquestionstatus.sql',1492953707,NULL),('phabricator:20130728.ponderunique.php',1492953707,NULL),('phabricator:20130728.ponderuniquekey.sql',1492953707,NULL),('phabricator:20130728.ponderxcomment.php',1492953707,NULL),('phabricator:20130731.releephcutpointidentifier.sql',1492953707,NULL),('phabricator:20130731.releephproject.sql',1492953707,NULL),('phabricator:20130731.releephrepoid.sql',1492953707,NULL),('phabricator:20130801.pastexactions.php',1492953707,NULL),('phabricator:20130801.pastexactions.sql',1492953707,NULL),('phabricator:20130802.heraldphid.sql',1492953707,NULL),('phabricator:20130802.heraldphids.php',1492953707,NULL),('phabricator:20130802.heraldphidukey.sql',1492953707,NULL),('phabricator:20130802.heraldxactions.sql',1492953707,NULL),('phabricator:20130805.pasteedges.sql',1492953707,NULL),('phabricator:20130805.pastemailkey.sql',1492953707,NULL),('phabricator:20130805.pastemailkeypop.php',1492953707,NULL),('phabricator:20130814.usercustom.sql',1492953707,NULL),('phabricator:20130820.file-mailkey-populate.php',1492953707,NULL),('phabricator:20130820.filemailkey.sql',1492953707,NULL),('phabricator:20130820.filexactions.sql',1492953707,NULL),('phabricator:20130820.releephxactions.sql',1492953707,NULL),('phabricator:20130826.divinernode.sql',1492953707,NULL),('phabricator:20130912.maniphest.1.touch.sql',1492953707,NULL),('phabricator:20130912.maniphest.2.created.sql',1492953707,NULL),('phabricator:20130912.maniphest.3.nameindex.sql',1492953707,NULL),('phabricator:20130912.maniphest.4.fillindex.php',1492953707,NULL),('phabricator:20130913.maniphest.1.migratesearch.php',1492953707,NULL),('phabricator:20130914.usercustom.sql',1492953707,NULL),('phabricator:20130915.maniphestcustom.sql',1492953707,NULL),('phabricator:20130915.maniphestmigrate.php',1492953707,NULL),('phabricator:20130915.maniphestqdrop.sql',1492953708,NULL),('phabricator:20130919.mfieldconf.php',1492953707,NULL),('phabricator:20130920.repokeyspolicy.sql',1492953707,NULL),('phabricator:20130921.mtransactions.sql',1492953707,NULL),('phabricator:20130921.xmigratemaniphest.php',1492953707,NULL),('phabricator:20130923.mrename.sql',1492953707,NULL),('phabricator:20130924.mdraftkey.sql',1492953707,NULL),('phabricator:20130925.mpolicy.sql',1492953707,NULL),('phabricator:20130925.xpolicy.sql',1492953707,NULL),('phabricator:20130926.dcustom.sql',1492953707,NULL),('phabricator:20130926.dinkeys.sql',1492953707,NULL),('phabricator:20130926.dinline.php',1492953708,NULL),('phabricator:20130927.audiomacro.sql',1492953708,NULL),('phabricator:20130929.filepolicy.sql',1492953708,NULL),('phabricator:20131004.dxedgekey.sql',1492953708,NULL),('phabricator:20131004.dxreviewers.php',1492953708,NULL),('phabricator:20131006.hdisable.sql',1492953708,NULL),('phabricator:20131010.pstorage.sql',1492953708,NULL),('phabricator:20131015.cpolicy.sql',1492953708,NULL),('phabricator:20131020.col1.sql',1492953708,NULL),('phabricator:20131020.harbormaster.sql',1492953708,NULL),('phabricator:20131020.pcustom.sql',1492953708,NULL),('phabricator:20131020.pxaction.sql',1492953708,NULL),('phabricator:20131020.pxactionmig.php',1492953708,NULL),('phabricator:20131025.repopush.sql',1492953708,NULL),('phabricator:20131026.commitstatus.sql',1492953708,NULL),('phabricator:20131030.repostatusmessage.sql',1492953708,NULL),('phabricator:20131031.vcspassword.sql',1492953708,NULL),('phabricator:20131105.buildstep.sql',1492953708,NULL),('phabricator:20131106.diffphid.1.col.sql',1492953708,NULL),('phabricator:20131106.diffphid.2.mig.php',1492953708,NULL),('phabricator:20131106.diffphid.3.key.sql',1492953708,NULL),('phabricator:20131106.nuance-v0.sql',1492953708,NULL),('phabricator:20131107.buildlog.sql',1492953708,NULL),('phabricator:20131112.userverified.1.col.sql',1492953708,NULL),('phabricator:20131112.userverified.2.mig.php',1492953708,NULL),('phabricator:20131118.ownerorder.php',1492953708,NULL),('phabricator:20131119.passphrase.sql',1492953708,NULL),('phabricator:20131120.nuancesourcetype.sql',1492953708,NULL),('phabricator:20131121.passphraseedge.sql',1492953708,NULL),('phabricator:20131121.repocredentials.1.col.sql',1492953708,NULL),('phabricator:20131121.repocredentials.2.mig.php',1492953708,NULL),('phabricator:20131122.repomirror.sql',1492953708,NULL),('phabricator:20131123.drydockblueprintpolicy.sql',1492953708,NULL),('phabricator:20131129.drydockresourceblueprint.sql',1492953708,NULL),('phabricator:20131204.pushlog.sql',1492953708,NULL),('phabricator:20131205.buildsteporder.sql',1492953708,NULL),('phabricator:20131205.buildstepordermig.php',1492953708,NULL),('phabricator:20131205.buildtargets.sql',1492953708,NULL),('phabricator:20131206.phragment.sql',1492953708,NULL),('phabricator:20131206.phragmentnull.sql',1492953708,NULL),('phabricator:20131208.phragmentsnapshot.sql',1492953708,NULL),('phabricator:20131211.phragmentedges.sql',1492953708,NULL),('phabricator:20131217.pushlogphid.1.col.sql',1492953708,NULL),('phabricator:20131217.pushlogphid.2.mig.php',1492953708,NULL),('phabricator:20131217.pushlogphid.3.key.sql',1492953708,NULL),('phabricator:20131219.pxdrop.sql',1492953708,NULL),('phabricator:20131224.harbormanual.sql',1492953708,NULL),('phabricator:20131227.heraldobject.sql',1492953708,NULL),('phabricator:20131231.dropshortcut.sql',1492953708,NULL),('phabricator:20131302.maniphestvalue.sql',1492953705,NULL),('phabricator:20140104.harbormastercmd.sql',1492953709,NULL),('phabricator:20140106.macromailkey.1.sql',1492953709,NULL),('phabricator:20140106.macromailkey.2.php',1492953709,NULL),('phabricator:20140108.ddbpname.1.sql',1492953709,NULL),('phabricator:20140108.ddbpname.2.php',1492953709,NULL),('phabricator:20140109.ddxactions.sql',1492953709,NULL),('phabricator:20140109.projectcolumnsdates.sql',1492953709,NULL),('phabricator:20140113.legalpadsig.1.sql',1492953709,NULL),('phabricator:20140113.legalpadsig.2.php',1492953709,NULL),('phabricator:20140115.auth.1.id.sql',1492953709,NULL),('phabricator:20140115.auth.2.expires.sql',1492953709,NULL),('phabricator:20140115.auth.3.unlimit.php',1492953709,NULL),('phabricator:20140115.legalpadsigkey.sql',1492953709,NULL),('phabricator:20140116.reporefcursor.sql',1492953709,NULL),('phabricator:20140126.diff.1.parentrevisionid.sql',1492953709,NULL),('phabricator:20140126.diff.2.repositoryphid.sql',1492953709,NULL),('phabricator:20140130.dash.1.board.sql',1492953709,NULL),('phabricator:20140130.dash.2.panel.sql',1492953709,NULL),('phabricator:20140130.dash.3.boardxaction.sql',1492953709,NULL),('phabricator:20140130.dash.4.panelxaction.sql',1492953709,NULL),('phabricator:20140130.mail.1.retry.sql',1492953709,NULL),('phabricator:20140130.mail.2.next.sql',1492953709,NULL),('phabricator:20140201.gc.1.mailsent.sql',1492953709,NULL),('phabricator:20140201.gc.2.mailreceived.sql',1492953709,NULL),('phabricator:20140205.cal.1.rename.sql',1492953709,NULL),('phabricator:20140205.cal.2.phid-col.sql',1492953709,NULL),('phabricator:20140205.cal.3.phid-mig.php',1492953709,NULL),('phabricator:20140205.cal.4.phid-key.sql',1492953709,NULL),('phabricator:20140210.herald.rule-condition-mig.php',1492953709,NULL),('phabricator:20140210.projcfield.1.blurb.php',1492953709,NULL),('phabricator:20140210.projcfield.2.piccol.sql',1492953709,NULL),('phabricator:20140210.projcfield.3.picmig.sql',1492953709,NULL),('phabricator:20140210.projcfield.4.memmig.sql',1492953709,NULL),('phabricator:20140210.projcfield.5.dropprofile.sql',1492953709,NULL),('phabricator:20140211.dx.1.nullablechangesetid.sql',1492953709,NULL),('phabricator:20140211.dx.2.migcommenttext.php',1492953709,NULL),('phabricator:20140211.dx.3.migsubscriptions.sql',1492953709,NULL),('phabricator:20140211.dx.999.drop.relationships.sql',1492953709,NULL),('phabricator:20140212.dx.1.armageddon.php',1492953709,NULL),('phabricator:20140214.clean.1.legacycommentid.sql',1492953709,NULL),('phabricator:20140214.clean.2.dropcomment.sql',1492953709,NULL),('phabricator:20140214.clean.3.dropinline.sql',1492953709,NULL),('phabricator:20140218.differentialdraft.sql',1492953709,NULL),('phabricator:20140218.passwords.1.extend.sql',1492953709,NULL),('phabricator:20140218.passwords.2.prefix.sql',1492953709,NULL),('phabricator:20140218.passwords.3.vcsextend.sql',1492953709,NULL),('phabricator:20140218.passwords.4.vcs.php',1492953709,NULL),('phabricator:20140223.bigutf8scratch.sql',1492953709,NULL),('phabricator:20140224.dxclean.1.datecommitted.sql',1492953709,NULL),('phabricator:20140226.dxcustom.1.fielddata.php',1492953709,NULL),('phabricator:20140226.dxcustom.99.drop.sql',1492953709,NULL),('phabricator:20140228.dxcomment.1.sql',1492953709,NULL),('phabricator:20140305.diviner.1.slugcol.sql',1492953709,NULL),('phabricator:20140305.diviner.2.slugkey.sql',1492953709,NULL),('phabricator:20140311.mdroplegacy.sql',1492953709,NULL),('phabricator:20140314.projectcolumn.1.statuscol.sql',1492953709,NULL),('phabricator:20140314.projectcolumn.2.statuskey.sql',1492953709,NULL),('phabricator:20140317.mupdatedkey.sql',1492953709,NULL),('phabricator:20140321.harbor.1.bxaction.sql',1492953709,NULL),('phabricator:20140321.mstatus.1.col.sql',1492953709,NULL),('phabricator:20140321.mstatus.2.mig.php',1492953709,NULL),('phabricator:20140323.harbor.1.renames.php',1492953709,NULL),('phabricator:20140323.harbor.2.message.sql',1492953709,NULL),('phabricator:20140325.push.1.event.sql',1492953709,NULL),('phabricator:20140325.push.2.eventphid.sql',1492953709,NULL),('phabricator:20140325.push.3.groups.php',1492953709,NULL),('phabricator:20140325.push.4.prune.sql',1492953709,NULL),('phabricator:20140326.project.1.colxaction.sql',1492953709,NULL),('phabricator:20140328.releeph.1.productxaction.sql',1492953709,NULL),('phabricator:20140330.flagtext.sql',1492953709,NULL),('phabricator:20140402.actionlog.sql',1492953709,NULL),('phabricator:20140410.accountsecret.1.sql',1492953709,NULL),('phabricator:20140410.accountsecret.2.php',1492953709,NULL),('phabricator:20140416.harbor.1.sql',1492953710,NULL),('phabricator:20140420.rel.1.objectphid.sql',1492953710,NULL),('phabricator:20140420.rel.2.objectmig.php',1492953710,NULL),('phabricator:20140421.slowvotecolumnsisclosed.sql',1492953710,NULL),('phabricator:20140423.session.1.hisec.sql',1492953710,NULL),('phabricator:20140427.mfactor.1.sql',1492953710,NULL),('phabricator:20140430.auth.1.partial.sql',1492953710,NULL),('phabricator:20140430.dash.1.paneltype.sql',1492953710,NULL),('phabricator:20140430.dash.2.edge.sql',1492953710,NULL),('phabricator:20140501.passphraselockcredential.sql',1492953710,NULL),('phabricator:20140501.remove.1.dlog.sql',1492953710,NULL),('phabricator:20140507.smstable.sql',1492953710,NULL),('phabricator:20140509.coverage.1.sql',1492953710,NULL),('phabricator:20140509.dashboardlayoutconfig.sql',1492953710,NULL),('phabricator:20140512.dparents.1.sql',1492953710,NULL),('phabricator:20140514.harbormasterbuildabletransaction.sql',1492953710,NULL),('phabricator:20140514.pholiomockclose.sql',1492953710,NULL),('phabricator:20140515.trust-emails.sql',1492953710,NULL),('phabricator:20140517.dxbinarycache.sql',1492953710,NULL),('phabricator:20140518.dxmorebinarycache.sql',1492953710,NULL),('phabricator:20140519.dashboardinstall.sql',1492953710,NULL),('phabricator:20140520.authtemptoken.sql',1492953710,NULL),('phabricator:20140521.projectslug.1.create.sql',1492953710,NULL),('phabricator:20140521.projectslug.2.mig.php',1492953710,NULL),('phabricator:20140522.projecticon.sql',1492953710,NULL),('phabricator:20140524.auth.mfa.cache.sql',1492953710,NULL),('phabricator:20140525.hunkmodern.sql',1492953710,NULL),('phabricator:20140615.pholioedit.1.sql',1492953710,NULL),('phabricator:20140615.pholioedit.2.sql',1492953710,NULL),('phabricator:20140617.daemon.explicit-argv.sql',1492953710,NULL),('phabricator:20140617.daemonlog.sql',1492953710,NULL),('phabricator:20140624.projcolor.1.sql',1492953710,NULL),('phabricator:20140624.projcolor.2.sql',1492953710,NULL),('phabricator:20140629.dasharchive.1.sql',1492953710,NULL),('phabricator:20140629.legalsig.1.sql',1492953710,NULL),('phabricator:20140629.legalsig.2.php',1492953710,NULL),('phabricator:20140701.legalexemption.1.sql',1492953710,NULL),('phabricator:20140701.legalexemption.2.sql',1492953710,NULL),('phabricator:20140703.legalcorp.1.sql',1492953710,NULL),('phabricator:20140703.legalcorp.2.sql',1492953710,NULL),('phabricator:20140703.legalcorp.3.sql',1492953710,NULL),('phabricator:20140703.legalcorp.4.sql',1492953710,NULL),('phabricator:20140703.legalcorp.5.sql',1492953710,NULL),('phabricator:20140704.harbormasterstep.1.sql',1492953710,NULL),('phabricator:20140704.harbormasterstep.2.sql',1492953710,NULL),('phabricator:20140704.legalpreamble.1.sql',1492953710,NULL),('phabricator:20140706.harbormasterdepend.1.php',1492953710,NULL),('phabricator:20140706.pedge.1.sql',1492953710,NULL),('phabricator:20140711.pnames.1.sql',1492953710,NULL),('phabricator:20140711.pnames.2.php',1492953710,NULL),('phabricator:20140711.workerpriority.sql',1492953710,NULL),('phabricator:20140712.projcoluniq.sql',1492953710,NULL),('phabricator:20140721.phortune.1.cart.sql',1492953710,NULL),('phabricator:20140721.phortune.2.purchase.sql',1492953710,NULL),('phabricator:20140721.phortune.3.charge.sql',1492953710,NULL),('phabricator:20140721.phortune.4.cartstatus.sql',1492953710,NULL),('phabricator:20140721.phortune.5.cstatusdefault.sql',1492953710,NULL),('phabricator:20140721.phortune.6.onetimecharge.sql',1492953710,NULL),('phabricator:20140721.phortune.7.nullmethod.sql',1492953710,NULL),('phabricator:20140722.appname.php',1492953710,NULL),('phabricator:20140722.audit.1.xactions.sql',1492953710,NULL),('phabricator:20140722.audit.2.comments.sql',1492953710,NULL),('phabricator:20140722.audit.3.miginlines.php',1492953710,NULL),('phabricator:20140722.audit.4.migtext.php',1492953710,NULL),('phabricator:20140722.renameauth.php',1492953710,NULL),('phabricator:20140723.apprenamexaction.sql',1492953710,NULL),('phabricator:20140725.audit.1.migxactions.php',1492953710,NULL),('phabricator:20140731.audit.1.subscribers.php',1492953710,NULL),('phabricator:20140731.cancdn.php',1492953710,NULL),('phabricator:20140731.harbormasterstepdesc.sql',1492953710,NULL),('phabricator:20140805.boardcol.1.sql',1492953710,NULL),('phabricator:20140805.boardcol.2.php',1492953710,NULL),('phabricator:20140807.harbormastertargettime.sql',1492953711,NULL),('phabricator:20140808.boardprop.1.sql',1492953711,NULL),('phabricator:20140808.boardprop.2.sql',1492953711,NULL),('phabricator:20140808.boardprop.3.php',1492953711,NULL),('phabricator:20140811.blob.1.sql',1492953711,NULL),('phabricator:20140811.blob.2.sql',1492953711,NULL),('phabricator:20140812.projkey.1.sql',1492953711,NULL),('phabricator:20140812.projkey.2.sql',1492953711,NULL),('phabricator:20140814.passphrasecredentialconduit.sql',1492953711,NULL),('phabricator:20140815.cancdncase.php',1492953711,NULL),('phabricator:20140818.harbormasterindex.1.sql',1492953711,NULL),('phabricator:20140821.harbormasterbuildgen.1.sql',1492953711,NULL),('phabricator:20140822.daemonenvhash.sql',1492953711,NULL),('phabricator:20140902.almanacdevice.1.sql',1492953711,NULL),('phabricator:20140904.macroattach.php',1492953711,NULL),('phabricator:20140911.fund.1.initiative.sql',1492953711,NULL),('phabricator:20140911.fund.2.xaction.sql',1492953711,NULL),('phabricator:20140911.fund.3.edge.sql',1492953711,NULL),('phabricator:20140911.fund.4.backer.sql',1492953711,NULL),('phabricator:20140911.fund.5.backxaction.sql',1492953711,NULL),('phabricator:20140914.betaproto.php',1492953711,NULL),('phabricator:20140917.project.canlock.sql',1492953711,NULL),('phabricator:20140918.schema.1.dropaudit.sql',1492953711,NULL),('phabricator:20140918.schema.2.dropauditinline.sql',1492953711,NULL),('phabricator:20140918.schema.3.wipecache.sql',1492953711,NULL),('phabricator:20140918.schema.4.cachetype.sql',1492953711,NULL),('phabricator:20140918.schema.5.slowvote.sql',1492953711,NULL),('phabricator:20140919.schema.01.calstatus.sql',1492953711,NULL),('phabricator:20140919.schema.02.calname.sql',1492953711,NULL),('phabricator:20140919.schema.03.dropaux.sql',1492953711,NULL),('phabricator:20140919.schema.04.droptaskproj.sql',1492953711,NULL),('phabricator:20140926.schema.01.droprelev.sql',1492953711,NULL),('phabricator:20140926.schema.02.droprelreqev.sql',1492953711,NULL),('phabricator:20140926.schema.03.dropldapinfo.sql',1492953711,NULL),('phabricator:20140926.schema.04.dropoauthinfo.sql',1492953711,NULL),('phabricator:20140926.schema.05.dropprojaffil.sql',1492953711,NULL),('phabricator:20140926.schema.06.dropsubproject.sql',1492953711,NULL),('phabricator:20140926.schema.07.droppondcom.sql',1492953711,NULL),('phabricator:20140927.schema.01.dropsearchq.sql',1492953711,NULL),('phabricator:20140927.schema.02.pholio1.sql',1492953711,NULL),('phabricator:20140927.schema.03.pholio2.sql',1492953711,NULL),('phabricator:20140927.schema.04.pholio3.sql',1492953711,NULL),('phabricator:20140927.schema.05.phragment1.sql',1492953711,NULL),('phabricator:20140927.schema.06.releeph1.sql',1492953711,NULL),('phabricator:20141001.schema.01.version.sql',1492953711,NULL),('phabricator:20141001.schema.02.taskmail.sql',1492953711,NULL),('phabricator:20141002.schema.01.liskcounter.sql',1492953711,NULL),('phabricator:20141002.schema.02.draftnull.sql',1492953711,NULL),('phabricator:20141004.currency.01.sql',1492953711,NULL),('phabricator:20141004.currency.02.sql',1492953711,NULL),('phabricator:20141004.currency.03.sql',1492953711,NULL),('phabricator:20141004.currency.04.sql',1492953711,NULL),('phabricator:20141004.currency.05.sql',1492953711,NULL),('phabricator:20141004.currency.06.sql',1492953711,NULL),('phabricator:20141004.harborliskcounter.sql',1492953711,NULL),('phabricator:20141005.phortuneproduct.sql',1492953711,NULL),('phabricator:20141006.phortunecart.sql',1492953711,NULL),('phabricator:20141006.phortunemerchant.sql',1492953711,NULL),('phabricator:20141006.phortunemerchantx.sql',1492953711,NULL),('phabricator:20141007.fundmerchant.sql',1492953711,NULL),('phabricator:20141007.fundrisks.sql',1492953711,NULL),('phabricator:20141007.fundtotal.sql',1492953711,NULL),('phabricator:20141007.phortunecartmerchant.sql',1492953711,NULL),('phabricator:20141007.phortunecharge.sql',1492953711,NULL),('phabricator:20141007.phortunepayment.sql',1492953712,NULL),('phabricator:20141007.phortuneprovider.sql',1492953712,NULL),('phabricator:20141007.phortuneproviderx.sql',1492953712,NULL),('phabricator:20141008.phortunemerchdesc.sql',1492953712,NULL),('phabricator:20141008.phortuneprovdis.sql',1492953712,NULL),('phabricator:20141008.phortunerefund.sql',1492953712,NULL),('phabricator:20141010.fundmailkey.sql',1492953712,NULL),('phabricator:20141011.phortunemerchedit.sql',1492953712,NULL),('phabricator:20141012.phortunecartxaction.sql',1492953712,NULL),('phabricator:20141013.phortunecartkey.sql',1492953712,NULL),('phabricator:20141016.almanac.device.sql',1492953712,NULL),('phabricator:20141016.almanac.dxaction.sql',1492953712,NULL),('phabricator:20141016.almanac.interface.sql',1492953712,NULL),('phabricator:20141016.almanac.network.sql',1492953712,NULL),('phabricator:20141016.almanac.nxaction.sql',1492953712,NULL),('phabricator:20141016.almanac.service.sql',1492953712,NULL),('phabricator:20141016.almanac.sxaction.sql',1492953712,NULL),('phabricator:20141017.almanac.binding.sql',1492953712,NULL),('phabricator:20141017.almanac.bxaction.sql',1492953712,NULL),('phabricator:20141025.phriction.1.xaction.sql',1492953712,NULL),('phabricator:20141025.phriction.2.xaction.sql',1492953712,NULL),('phabricator:20141025.phriction.mailkey.sql',1492953712,NULL),('phabricator:20141103.almanac.1.delprop.sql',1492953712,NULL),('phabricator:20141103.almanac.2.addprop.sql',1492953712,NULL),('phabricator:20141104.almanac.3.edge.sql',1492953712,NULL),('phabricator:20141105.ssh.1.rename.sql',1492953712,NULL),('phabricator:20141106.dropold.sql',1492953712,NULL),('phabricator:20141106.uniqdrafts.php',1492953712,NULL),('phabricator:20141107.phriction.policy.1.sql',1492953712,NULL),('phabricator:20141107.phriction.policy.2.php',1492953712,NULL),('phabricator:20141107.phriction.popkeys.php',1492953712,NULL),('phabricator:20141107.ssh.1.colname.sql',1492953712,NULL),('phabricator:20141107.ssh.2.keyhash.sql',1492953712,NULL),('phabricator:20141107.ssh.3.keyindex.sql',1492953712,NULL),('phabricator:20141107.ssh.4.keymig.php',1492953712,NULL),('phabricator:20141107.ssh.5.indexnull.sql',1492953712,NULL),('phabricator:20141107.ssh.6.indexkey.sql',1492953712,NULL),('phabricator:20141107.ssh.7.colnull.sql',1492953712,NULL),('phabricator:20141113.auditdupes.php',1492953712,NULL),('phabricator:20141118.diffxaction.sql',1492953712,NULL),('phabricator:20141119.commitpedge.sql',1492953712,NULL),('phabricator:20141119.differential.diff.policy.sql',1492953712,NULL),('phabricator:20141119.sshtrust.sql',1492953712,NULL),('phabricator:20141123.taskpriority.1.sql',1492953712,NULL),('phabricator:20141123.taskpriority.2.sql',1492953712,NULL),('phabricator:20141210.maniphestsubscribersmig.1.sql',1492953712,NULL),('phabricator:20141210.maniphestsubscribersmig.2.sql',1492953712,NULL),('phabricator:20141210.reposervice.sql',1492953712,NULL),('phabricator:20141212.conduittoken.sql',1492953712,NULL),('phabricator:20141215.almanacservicetype.sql',1492953712,NULL),('phabricator:20141217.almanacdevicelock.sql',1492953712,NULL),('phabricator:20141217.almanaclock.sql',1492953712,NULL),('phabricator:20141218.maniphestcctxn.php',1492953712,NULL),('phabricator:20141222.maniphestprojtxn.php',1492953712,NULL),('phabricator:20141223.daemonloguser.sql',1492953712,NULL),('phabricator:20141223.daemonobjectphid.sql',1492953712,NULL),('phabricator:20141230.pasteeditpolicycolumn.sql',1492953712,NULL),('phabricator:20141230.pasteeditpolicyexisting.sql',1492953712,NULL),('phabricator:20150102.policyname.php',1492953712,NULL),('phabricator:20150102.tasksubscriber.sql',1492953712,NULL),('phabricator:20150105.conpsearch.sql',1492953712,NULL),('phabricator:20150114.oauthserver.client.policy.sql',1492953713,NULL),('phabricator:20150115.applicationemails.sql',1492953713,NULL),('phabricator:20150115.trigger.1.sql',1492953713,NULL),('phabricator:20150115.trigger.2.sql',1492953713,NULL),('phabricator:20150116.maniphestapplicationemails.php',1492953713,NULL),('phabricator:20150120.maniphestdefaultauthor.php',1492953713,NULL),('phabricator:20150124.subs.1.sql',1492953713,NULL),('phabricator:20150129.pastefileapplicationemails.php',1492953713,NULL),('phabricator:20150130.phortune.1.subphid.sql',1492953713,NULL),('phabricator:20150130.phortune.2.subkey.sql',1492953713,NULL),('phabricator:20150131.phortune.1.defaultpayment.sql',1492953713,NULL),('phabricator:20150205.authprovider.autologin.sql',1492953713,NULL),('phabricator:20150205.daemonenv.sql',1492953713,NULL),('phabricator:20150209.invite.sql',1492953713,NULL),('phabricator:20150209.oauthclient.trust.sql',1492953713,NULL),('phabricator:20150210.invitephid.sql',1492953713,NULL),('phabricator:20150212.legalpad.session.1.sql',1492953713,NULL),('phabricator:20150212.legalpad.session.2.sql',1492953713,NULL),('phabricator:20150219.scratch.nonmutable.sql',1492953713,NULL),('phabricator:20150223.daemon.1.id.sql',1492953713,NULL),('phabricator:20150223.daemon.2.idlegacy.sql',1492953713,NULL),('phabricator:20150223.daemon.3.idkey.sql',1492953713,NULL),('phabricator:20150312.filechunk.1.sql',1492953713,NULL),('phabricator:20150312.filechunk.2.sql',1492953713,NULL),('phabricator:20150312.filechunk.3.sql',1492953713,NULL),('phabricator:20150317.conpherence.isroom.1.sql',1492953713,NULL),('phabricator:20150317.conpherence.isroom.2.sql',1492953713,NULL),('phabricator:20150317.conpherence.policy.sql',1492953713,NULL),('phabricator:20150410.nukeruleedit.sql',1492953713,NULL),('phabricator:20150420.invoice.1.sql',1492953713,NULL),('phabricator:20150420.invoice.2.sql',1492953713,NULL),('phabricator:20150425.isclosed.sql',1492953713,NULL),('phabricator:20150427.calendar.1.edge.sql',1492953713,NULL),('phabricator:20150427.calendar.1.xaction.sql',1492953713,NULL),('phabricator:20150427.calendar.2.xaction.sql',1492953713,NULL),('phabricator:20150428.calendar.1.iscancelled.sql',1492953713,NULL),('phabricator:20150428.calendar.1.name.sql',1492953713,NULL),('phabricator:20150429.calendar.1.invitee.sql',1492953713,NULL),('phabricator:20150430.calendar.1.policies.sql',1492953713,NULL),('phabricator:20150430.multimeter.1.sql',1492953713,NULL),('phabricator:20150430.multimeter.2.host.sql',1492953713,NULL),('phabricator:20150430.multimeter.3.viewer.sql',1492953713,NULL),('phabricator:20150430.multimeter.4.context.sql',1492953713,NULL),('phabricator:20150430.multimeter.5.label.sql',1492953713,NULL),('phabricator:20150501.calendar.1.reply.sql',1492953713,NULL),('phabricator:20150501.calendar.2.reply.php',1492953713,NULL),('phabricator:20150501.conpherencepics.sql',1492953713,NULL),('phabricator:20150503.repositorysymbols.1.sql',1492953713,NULL),('phabricator:20150503.repositorysymbols.2.php',1492953713,NULL),('phabricator:20150503.repositorysymbols.3.sql',1492953713,NULL),('phabricator:20150504.symbolsproject.1.php',1492953713,NULL),('phabricator:20150504.symbolsproject.2.sql',1492953713,NULL),('phabricator:20150506.calendarunnamedevents.1.php',1492953713,NULL),('phabricator:20150507.calendar.1.isallday.sql',1492953713,NULL),('phabricator:20150513.user.cache.1.sql',1492953713,NULL),('phabricator:20150514.calendar.status.sql',1492953713,NULL),('phabricator:20150514.phame.blog.xaction.sql',1492953713,NULL),('phabricator:20150514.user.cache.2.sql',1492953713,NULL),('phabricator:20150515.phame.post.xaction.sql',1492953713,NULL),('phabricator:20150515.project.mailkey.1.sql',1492953713,NULL),('phabricator:20150515.project.mailkey.2.php',1492953713,NULL),('phabricator:20150519.calendar.calendaricon.sql',1492953713,NULL),('phabricator:20150521.releephrepository.sql',1492953713,NULL),('phabricator:20150525.diff.hidden.1.sql',1492953713,NULL),('phabricator:20150526.owners.mailkey.1.sql',1492953713,NULL),('phabricator:20150526.owners.mailkey.2.php',1492953713,NULL),('phabricator:20150526.owners.xaction.sql',1492953713,NULL),('phabricator:20150527.calendar.recurringevents.sql',1492953713,NULL),('phabricator:20150601.spaces.1.namespace.sql',1492953713,NULL),('phabricator:20150601.spaces.2.xaction.sql',1492953714,NULL),('phabricator:20150602.mlist.1.sql',1492953714,NULL),('phabricator:20150602.mlist.2.php',1492953714,NULL),('phabricator:20150604.spaces.1.sql',1492953714,NULL),('phabricator:20150605.diviner.edges.sql',1492953714,NULL),('phabricator:20150605.diviner.editPolicy.sql',1492953714,NULL),('phabricator:20150605.diviner.xaction.sql',1492953714,NULL),('phabricator:20150606.mlist.1.php',1492953714,NULL),('phabricator:20150609.inline.sql',1492953714,NULL),('phabricator:20150609.spaces.1.pholio.sql',1492953714,NULL),('phabricator:20150609.spaces.2.maniphest.sql',1492953714,NULL),('phabricator:20150610.spaces.1.desc.sql',1492953714,NULL),('phabricator:20150610.spaces.2.edge.sql',1492953714,NULL),('phabricator:20150610.spaces.3.archive.sql',1492953714,NULL),('phabricator:20150611.spaces.1.mailxaction.sql',1492953714,NULL),('phabricator:20150611.spaces.2.appmail.sql',1492953714,NULL),('phabricator:20150616.divinerrepository.sql',1492953714,NULL),('phabricator:20150617.harbor.1.lint.sql',1492953714,NULL),('phabricator:20150617.harbor.2.unit.sql',1492953714,NULL),('phabricator:20150618.harbor.1.planauto.sql',1492953714,NULL),('phabricator:20150618.harbor.2.stepauto.sql',1492953714,NULL),('phabricator:20150618.harbor.3.buildauto.sql',1492953714,NULL),('phabricator:20150619.conpherencerooms.1.sql',1492953714,NULL),('phabricator:20150619.conpherencerooms.2.sql',1492953714,NULL),('phabricator:20150619.conpherencerooms.3.sql',1492953714,NULL),('phabricator:20150621.phrase.1.sql',1492953714,NULL),('phabricator:20150621.phrase.2.sql',1492953714,NULL),('phabricator:20150622.bulk.1.job.sql',1492953714,NULL),('phabricator:20150622.bulk.2.task.sql',1492953714,NULL),('phabricator:20150622.bulk.3.xaction.sql',1492953714,NULL),('phabricator:20150622.bulk.4.edge.sql',1492953714,NULL),('phabricator:20150622.metamta.1.phid-col.sql',1492953714,NULL),('phabricator:20150622.metamta.2.phid-mig.php',1492953714,NULL),('phabricator:20150622.metamta.3.phid-key.sql',1492953714,NULL),('phabricator:20150622.metamta.4.actor-phid-col.sql',1492953714,NULL),('phabricator:20150622.metamta.5.actor-phid-mig.php',1492953714,NULL),('phabricator:20150622.metamta.6.actor-phid-key.sql',1492953714,NULL),('phabricator:20150624.spaces.1.repo.sql',1492953714,NULL),('phabricator:20150626.spaces.1.calendar.sql',1492953714,NULL),('phabricator:20150630.herald.1.sql',1492953714,NULL),('phabricator:20150630.herald.2.sql',1492953714,NULL),('phabricator:20150701.herald.1.sql',1492953714,NULL),('phabricator:20150701.herald.2.sql',1492953714,NULL),('phabricator:20150702.spaces.1.slowvote.sql',1492953714,NULL),('phabricator:20150706.herald.1.sql',1492953714,NULL),('phabricator:20150707.herald.1.sql',1492953714,NULL),('phabricator:20150708.arcanistproject.sql',1492953714,NULL),('phabricator:20150708.herald.1.sql',1492953714,NULL),('phabricator:20150708.herald.2.sql',1492953714,NULL),('phabricator:20150708.herald.3.sql',1492953714,NULL),('phabricator:20150712.badges.1.sql',1492953714,NULL),('phabricator:20150714.spaces.countdown.1.sql',1492953714,NULL),('phabricator:20150717.herald.1.sql',1492953714,NULL),('phabricator:20150719.countdown.1.sql',1492953714,NULL),('phabricator:20150719.countdown.2.sql',1492953714,NULL),('phabricator:20150719.countdown.3.sql',1492953714,NULL),('phabricator:20150721.phurl.1.url.sql',1492953714,NULL),('phabricator:20150721.phurl.2.xaction.sql',1492953714,NULL),('phabricator:20150721.phurl.3.xactioncomment.sql',1492953714,NULL),('phabricator:20150721.phurl.4.url.sql',1492953714,NULL),('phabricator:20150721.phurl.5.edge.sql',1492953714,NULL),('phabricator:20150721.phurl.6.alias.sql',1492953714,NULL),('phabricator:20150721.phurl.7.authorphid.sql',1492953714,NULL),('phabricator:20150722.dashboard.1.sql',1492953714,NULL),('phabricator:20150722.dashboard.2.sql',1492953714,NULL),('phabricator:20150723.countdown.1.sql',1492953714,NULL),('phabricator:20150724.badges.comments.1.sql',1492953714,NULL),('phabricator:20150724.countdown.comments.1.sql',1492953714,NULL),('phabricator:20150725.badges.mailkey.1.sql',1492953714,NULL),('phabricator:20150725.badges.mailkey.2.php',1492953714,NULL),('phabricator:20150725.badges.viewpolicy.3.sql',1492953714,NULL),('phabricator:20150725.countdown.mailkey.1.sql',1492953714,NULL),('phabricator:20150725.countdown.mailkey.2.php',1492953714,NULL),('phabricator:20150725.slowvote.mailkey.1.sql',1492953714,NULL),('phabricator:20150725.slowvote.mailkey.2.php',1492953714,NULL),('phabricator:20150727.heraldaction.1.sql',1492953715,NULL),('phabricator:20150730.herald.1.sql',1492953715,NULL),('phabricator:20150730.herald.2.sql',1492953715,NULL),('phabricator:20150730.herald.3.sql',1492953715,NULL),('phabricator:20150730.herald.4.sql',1492953715,NULL),('phabricator:20150730.herald.5.sql',1492953715,NULL),('phabricator:20150730.herald.6.sql',1492953715,NULL),('phabricator:20150730.herald.7.sql',1492953715,NULL),('phabricator:20150803.herald.1.sql',1492953715,NULL),('phabricator:20150803.herald.2.sql',1492953715,NULL),('phabricator:20150804.ponder.answer.mailkey.1.sql',1492953715,NULL),('phabricator:20150804.ponder.answer.mailkey.2.php',1492953715,NULL),('phabricator:20150804.ponder.question.1.sql',1492953715,NULL),('phabricator:20150804.ponder.question.2.sql',1492953715,NULL),('phabricator:20150804.ponder.question.3.sql',1492953715,NULL),('phabricator:20150804.ponder.spaces.4.sql',1492953715,NULL),('phabricator:20150805.paste.status.1.sql',1492953715,NULL),('phabricator:20150805.paste.status.2.sql',1492953715,NULL),('phabricator:20150806.ponder.answer.1.sql',1492953715,NULL),('phabricator:20150806.ponder.editpolicy.2.sql',1492953715,NULL),('phabricator:20150806.ponder.status.1.sql',1492953715,NULL),('phabricator:20150806.ponder.status.2.sql',1492953715,NULL),('phabricator:20150806.ponder.status.3.sql',1492953715,NULL),('phabricator:20150808.ponder.vote.1.sql',1492953715,NULL),('phabricator:20150808.ponder.vote.2.sql',1492953715,NULL),('phabricator:20150812.ponder.answer.1.sql',1492953715,NULL),('phabricator:20150812.ponder.answer.2.sql',1492953715,NULL),('phabricator:20150814.harbormater.artifact.phid.sql',1492953715,NULL),('phabricator:20150815.owners.status.1.sql',1492953715,NULL),('phabricator:20150815.owners.status.2.sql',1492953715,NULL),('phabricator:20150823.nuance.queue.1.sql',1492953715,NULL),('phabricator:20150823.nuance.queue.2.sql',1492953715,NULL),('phabricator:20150823.nuance.queue.3.sql',1492953715,NULL),('phabricator:20150823.nuance.queue.4.sql',1492953715,NULL),('phabricator:20150828.ponder.wiki.1.sql',1492953715,NULL),('phabricator:20150829.ponder.dupe.1.sql',1492953715,NULL),('phabricator:20150904.herald.1.sql',1492953715,NULL),('phabricator:20150906.mailinglist.sql',1492953715,NULL),('phabricator:20150910.owners.custom.1.sql',1492953715,NULL),('phabricator:20150916.drydock.slotlocks.1.sql',1492953715,NULL),('phabricator:20150922.drydock.commands.1.sql',1492953715,NULL),('phabricator:20150923.drydock.resourceid.1.sql',1492953715,NULL),('phabricator:20150923.drydock.resourceid.2.sql',1492953715,NULL),('phabricator:20150923.drydock.resourceid.3.sql',1492953715,NULL),('phabricator:20150923.drydock.taskid.1.sql',1492953715,NULL),('phabricator:20150924.drydock.disable.1.sql',1492953715,NULL),('phabricator:20150924.drydock.status.1.sql',1492953715,NULL),('phabricator:20150928.drydock.rexpire.1.sql',1492953715,NULL),('phabricator:20150930.drydock.log.1.sql',1492953715,NULL),('phabricator:20151001.drydock.rname.1.sql',1492953715,NULL),('phabricator:20151002.dashboard.status.1.sql',1492953715,NULL),('phabricator:20151002.harbormaster.bparam.1.sql',1492953715,NULL),('phabricator:20151009.drydock.auth.1.sql',1492953715,NULL),('phabricator:20151010.drydock.auth.2.sql',1492953715,NULL),('phabricator:20151013.drydock.op.1.sql',1492953715,NULL),('phabricator:20151023.harborpolicy.1.sql',1492953715,NULL),('phabricator:20151023.harborpolicy.2.php',1492953715,NULL),('phabricator:20151023.patchduration.sql',1492953715,13609),('phabricator:20151030.harbormaster.initiator.sql',1492953715,29433),('phabricator:20151106.editengine.1.table.sql',1492953715,8624),('phabricator:20151106.editengine.2.xactions.sql',1492953715,6593),('phabricator:20151106.phame.post.mailkey.1.sql',1492953715,19247),('phabricator:20151106.phame.post.mailkey.2.php',1492953715,1219),('phabricator:20151107.phame.blog.mailkey.1.sql',1492953715,37986),('phabricator:20151107.phame.blog.mailkey.2.php',1492953715,704),('phabricator:20151108.phame.blog.joinpolicy.sql',1492953715,44574),('phabricator:20151108.xhpast.stderr.sql',1492953715,43870),('phabricator:20151109.phame.post.comments.1.sql',1492953716,7807),('phabricator:20151109.repository.coverage.1.sql',1492953716,943),('phabricator:20151109.xhpast.db.1.sql',1492953716,1093),('phabricator:20151109.xhpast.db.2.sql',1492953716,490),('phabricator:20151110.daemonenvhash.sql',1492953716,34291),('phabricator:20151111.phame.blog.archive.1.sql',1492953716,17601),('phabricator:20151111.phame.blog.archive.2.sql',1492953716,398),('phabricator:20151112.herald.edge.sql',1492953716,15285),('phabricator:20151116.owners.edge.sql',1492953716,13252),('phabricator:20151128.phame.blog.picture.1.sql',1492953716,18001),('phabricator:20151130.phurl.mailkey.1.sql',1492953716,11335),('phabricator:20151130.phurl.mailkey.2.php',1492953716,992),('phabricator:20151202.versioneddraft.1.sql',1492953716,7153),('phabricator:20151207.editengine.1.sql',1492953716,72200),('phabricator:20151210.land.1.refphid.sql',1492953716,13730),('phabricator:20151210.land.2.refphid.php',1492953716,629),('phabricator:20151215.phame.1.autotitle.sql',1492953716,19593),('phabricator:20151218.key.1.keyphid.sql',1492953716,14029),('phabricator:20151218.key.2.keyphid.php',1492953716,375),('phabricator:20151219.proj.01.prislug.sql',1492953716,21886),('phabricator:20151219.proj.02.prislugkey.sql',1492953716,12589),('phabricator:20151219.proj.03.copyslug.sql',1492953716,388),('phabricator:20151219.proj.04.dropslugkey.sql',1492953716,6623),('phabricator:20151219.proj.05.dropslug.sql',1492953716,19905),('phabricator:20151219.proj.06.defaultpolicy.php',1492953716,795),('phabricator:20151219.proj.07.viewnull.sql',1492953716,17884),('phabricator:20151219.proj.08.editnull.sql',1492953716,10628),('phabricator:20151219.proj.09.joinnull.sql',1492953716,9450),('phabricator:20151219.proj.10.subcolumns.sql',1492953716,141357),('phabricator:20151219.proj.11.subprojectphids.sql',1492953716,22236),('phabricator:20151221.search.1.version.sql',1492953716,10587),('phabricator:20151221.search.2.ownersngrams.sql',1492953716,7323),('phabricator:20151221.search.3.reindex.php',1492953716,310),('phabricator:20151223.proj.01.paths.sql',1492953716,21893),('phabricator:20151223.proj.02.depths.sql',1492953716,28023),('phabricator:20151223.proj.03.pathkey.sql',1492953716,13779),('phabricator:20151223.proj.04.keycol.sql',1492953716,29651),('phabricator:20151223.proj.05.updatekeys.php',1492953716,375),('phabricator:20151223.proj.06.uniq.sql',1492953716,11845),('phabricator:20151226.reop.1.sql',1492953716,17667),('phabricator:20151227.proj.01.materialize.sql',1492953716,422),('phabricator:20151231.proj.01.icon.php',1492953716,1539),('phabricator:20160102.badges.award.sql',1492953716,8429),('phabricator:20160110.repo.01.slug.sql',1492953716,36030),('phabricator:20160110.repo.02.slug.php',1492953716,407),('phabricator:20160111.repo.01.slugx.sql',1492953716,639),('phabricator:20160112.repo.01.uri.sql',1492953716,9469),('phabricator:20160112.repo.02.uri.index.php',1492953716,80),('phabricator:20160113.propanel.1.storage.sql',1492953716,6940),('phabricator:20160113.propanel.2.xaction.sql',1492953716,7108),('phabricator:20160119.project.1.silence.sql',1492953716,457),('phabricator:20160122.project.1.boarddefault.php',1492953716,755),('phabricator:20160124.people.1.icon.sql',1492953716,12766),('phabricator:20160124.people.2.icondefault.sql',1492953716,351),('phabricator:20160128.repo.1.pull.sql',1492953716,9469),('phabricator:20160201.revision.properties.1.sql',1492953716,17863),('phabricator:20160201.revision.properties.2.sql',1492953716,370),('phabricator:20160202.board.1.proxy.sql',1492953716,18324),('phabricator:20160202.ipv6.1.sql',1492953716,18661),('phabricator:20160202.ipv6.2.php',1492953716,1141),('phabricator:20160206.cover.1.sql',1492953716,40105),('phabricator:20160208.task.1.sql',1492953716,35042),('phabricator:20160208.task.2.sql',1492953716,32770),('phabricator:20160208.task.3.sql',1492953716,34137),('phabricator:20160212.proj.1.sql',1492953717,29561),('phabricator:20160212.proj.2.sql',1492953717,314),('phabricator:20160215.owners.policy.1.sql',1492953717,18003),('phabricator:20160215.owners.policy.2.sql',1492953717,17598),('phabricator:20160215.owners.policy.3.sql',1492953717,387),('phabricator:20160215.owners.policy.4.sql',1492953717,291),('phabricator:20160218.callsigns.1.sql',1492953717,10721),('phabricator:20160221.almanac.1.devicen.sql',1492953717,8045),('phabricator:20160221.almanac.2.devicei.php',1492953717,965),('phabricator:20160221.almanac.3.servicen.sql',1492953717,5846),('phabricator:20160221.almanac.4.servicei.php',1492953717,510),('phabricator:20160221.almanac.5.networkn.sql',1492953717,6516),('phabricator:20160221.almanac.6.networki.php',1492953717,813),('phabricator:20160221.almanac.7.namespacen.sql',1492953717,6664),('phabricator:20160221.almanac.8.namespace.sql',1492953717,7358),('phabricator:20160221.almanac.9.namespacex.sql',1492953717,7375),('phabricator:20160222.almanac.1.properties.php',1492953717,1075),('phabricator:20160223.almanac.1.bound.sql',1492953717,15258),('phabricator:20160223.almanac.2.lockbind.sql',1492953717,363),('phabricator:20160223.almanac.3.devicelock.sql',1492953717,28139),('phabricator:20160223.almanac.4.servicelock.sql',1492953717,24328),('phabricator:20160223.paste.fileedges.php',1492953717,552),('phabricator:20160225.almanac.1.disablebinding.sql',1492953717,20577),('phabricator:20160225.almanac.2.stype.sql',1492953717,6572),('phabricator:20160225.almanac.3.stype.php',1492953717,402),('phabricator:20160227.harbormaster.1.plann.sql',1492953717,8121),('phabricator:20160227.harbormaster.2.plani.php',1492953717,346),('phabricator:20160303.drydock.1.bluen.sql',1492953717,7287),('phabricator:20160303.drydock.2.bluei.php',1492953717,355),('phabricator:20160303.drydock.3.edge.sql',1492953717,13334),('phabricator:20160308.nuance.01.disabled.sql',1492953717,16961),('phabricator:20160308.nuance.02.cursordata.sql',1492953717,7361),('phabricator:20160308.nuance.03.sourcen.sql',1492953717,6397),('phabricator:20160308.nuance.04.sourcei.php',1492953717,1144),('phabricator:20160308.nuance.05.sourcename.sql',1492953717,11455),('phabricator:20160308.nuance.06.label.sql',1492953717,18960),('phabricator:20160308.nuance.07.itemtype.sql',1492953717,22009),('phabricator:20160308.nuance.08.itemkey.sql',1492953717,21836),('phabricator:20160308.nuance.09.itemcontainer.sql',1492953717,21988),('phabricator:20160308.nuance.10.itemkeyu.sql',1492953717,366),('phabricator:20160308.nuance.11.requestor.sql',1492953717,11681),('phabricator:20160308.nuance.12.queue.sql',1492953717,22125),('phabricator:20160316.lfs.01.token.resource.sql',1492953717,19283),('phabricator:20160316.lfs.02.token.user.sql',1492953717,16100),('phabricator:20160316.lfs.03.token.properties.sql',1492953717,16663),('phabricator:20160316.lfs.04.token.default.sql',1492953717,369),('phabricator:20160317.lfs.01.ref.sql',1492953717,6665),('phabricator:20160321.nuance.01.taskbridge.sql',1492953717,29611),('phabricator:20160322.nuance.01.itemcommand.sql',1492953717,8177),('phabricator:20160323.badgemigrate.sql',1492953717,884),('phabricator:20160329.nuance.01.requestor.sql',1492953717,1317),('phabricator:20160329.nuance.02.requestorsource.sql',1492953717,1457),('phabricator:20160329.nuance.03.requestorxaction.sql',1492953717,1284),('phabricator:20160329.nuance.04.requestorcomment.sql',1492953717,1283),('phabricator:20160330.badges.migratequality.sql',1492953717,11262),('phabricator:20160330.badges.qualityxaction.mig.sql',1492953717,1541),('phabricator:20160331.fund.comments.1.sql',1492953717,6270),('phabricator:20160404.oauth.1.xaction.sql',1492953717,5694),('phabricator:20160405.oauth.2.disable.sql',1492953717,13715),('phabricator:20160406.badges.ngrams.php',1492953717,526),('phabricator:20160406.badges.ngrams.sql',1492953717,11583),('phabricator:20160406.columns.1.php',1492953717,524),('phabricator:20160411.repo.1.version.sql',1492953717,5926),('phabricator:20160418.repouri.1.sql',1492953717,5583),('phabricator:20160418.repouri.2.sql',1492953717,10524),('phabricator:20160418.repoversion.1.sql',1492953717,13780),('phabricator:20160419.pushlog.1.sql',1492953717,25781),('phabricator:20160424.locks.1.sql',1492953717,14645),('phabricator:20160426.searchedge.sql',1492953717,13370),('phabricator:20160428.repo.1.urixaction.sql',1492953717,7961),('phabricator:20160503.repo.01.lpath.sql',1492953717,62415),('phabricator:20160503.repo.02.lpathkey.sql',1492953717,25518),('phabricator:20160503.repo.03.lpathmigrate.php',1492953717,556),('phabricator:20160503.repo.04.mirrormigrate.php',1492953717,493),('phabricator:20160503.repo.05.urimigrate.php',1492953717,418),('phabricator:20160510.repo.01.uriindex.php',1492953717,4833),('phabricator:20160513.owners.01.autoreview.sql',1492953717,32054),('phabricator:20160513.owners.02.autoreviewnone.sql',1492953717,499),('phabricator:20160516.owners.01.dominion.sql',1492953717,14616),('phabricator:20160516.owners.02.dominionstrong.sql',1492953717,323),('phabricator:20160517.oauth.01.edge.sql',1492953717,11629),('phabricator:20160518.ssh.01.activecol.sql',1492953717,13262),('phabricator:20160518.ssh.02.activeval.sql',1492953717,282),('phabricator:20160518.ssh.03.activekey.sql',1492953717,21706),('phabricator:20160519.ssh.01.xaction.sql',1492953717,8147),('phabricator:20160531.pref.01.xaction.sql',1492953717,6483),('phabricator:20160531.pref.02.datecreatecol.sql',1492953717,12856),('phabricator:20160531.pref.03.datemodcol.sql',1492953717,14408),('phabricator:20160531.pref.04.datecreateval.sql',1492953717,367),('phabricator:20160531.pref.05.datemodval.sql',1492953717,298),('phabricator:20160531.pref.06.phidcol.sql',1492953717,16950),('phabricator:20160531.pref.07.phidval.php',1492953717,603),('phabricator:20160601.user.01.cache.sql',1492953717,8935),('phabricator:20160601.user.02.copyprefs.php',1492953717,1423),('phabricator:20160601.user.03.removetime.sql',1492953718,20344),('phabricator:20160601.user.04.removetranslation.sql',1492953718,21336),('phabricator:20160601.user.05.removesex.sql',1492953718,23471),('phabricator:20160603.user.01.removedcenabled.sql',1492953718,20090),('phabricator:20160603.user.02.removedctab.sql',1492953718,19685),('phabricator:20160603.user.03.removedcvisible.sql',1492953718,20696),('phabricator:20160604.user.01.stringmailprefs.php',1492953718,457),('phabricator:20160604.user.02.removeimagecache.sql',1492953718,19842),('phabricator:20160605.user.01.prefnulluser.sql',1492953718,12045),('phabricator:20160605.user.02.prefbuiltin.sql',1492953718,12871),('phabricator:20160605.user.03.builtinunique.sql',1492953718,10637),('phabricator:20160616.phame.blog.header.1.sql',1492953718,15672),('phabricator:20160616.repo.01.oldref.sql',1492953718,6173),('phabricator:20160617.harbormaster.01.arelease.sql',1492953718,15915),('phabricator:20160618.phame.blog.subtitle.sql',1492953718,15243),('phabricator:20160620.phame.blog.parentdomain.2.sql',1492953718,15398),('phabricator:20160620.phame.blog.parentsite.1.sql',1492953718,16059),('phabricator:20160623.phame.blog.fulldomain.1.sql',1492953718,16216),('phabricator:20160623.phame.blog.fulldomain.2.sql',1492953718,383),('phabricator:20160623.phame.blog.fulldomain.3.sql',1492953718,427),('phabricator:20160706.phame.blog.parentdomain.2.sql',1492953718,17416),('phabricator:20160706.phame.blog.parentsite.1.sql',1492953718,15821),('phabricator:20160707.calendar.01.stub.sql',1492953718,15805),('phabricator:20160711.files.01.builtin.sql',1492953718,23541),('phabricator:20160711.files.02.builtinkey.sql',1492953718,11421),('phabricator:20160713.event.01.host.sql',1492953718,11510),('phabricator:20160715.event.01.alldayfrom.sql',1492953718,17347),('phabricator:20160715.event.02.alldayto.sql',1492953718,16771),('phabricator:20160715.event.03.allday.php',1492953718,65),('phabricator:20160720.calendar.invitetxn.php',1492953718,1008),('phabricator:20160721.pack.01.pub.sql',1492953718,11271),('phabricator:20160721.pack.02.pubxaction.sql',1492953718,7364),('phabricator:20160721.pack.03.edge.sql',1492953718,13426),('phabricator:20160721.pack.04.pkg.sql',1492953718,7126),('phabricator:20160721.pack.05.pkgxaction.sql',1492953718,6996),('phabricator:20160721.pack.06.version.sql',1492953718,7508),('phabricator:20160721.pack.07.versionxaction.sql',1492953718,7013),('phabricator:20160722.pack.01.pubngrams.sql',1492953718,6915),('phabricator:20160722.pack.02.pkgngrams.sql',1492953718,7627),('phabricator:20160722.pack.03.versionngrams.sql',1492953718,7239),('phabricator:20160810.commit.01.summarylength.sql',1492953718,11762),('phabricator:20160824.connectionlog.sql',1492953718,1222),('phabricator:20160824.repohint.01.hint.sql',1492953718,6193),('phabricator:20160824.repohint.02.movebad.php',1492953718,459),('phabricator:20160824.repohint.03.nukebad.sql',1492953718,1167),('phabricator:20160825.ponder.sql',1492953718,719),('phabricator:20160829.pastebin.01.language.sql',1492953718,10809),('phabricator:20160829.pastebin.02.language.sql',1492953718,514),('phabricator:20160913.conpherence.topic.1.sql',1492953718,11994),('phabricator:20160919.repo.messagecount.sql',1492953718,13142),('phabricator:20160919.repo.messagedefault.sql',1492953718,7514),('phabricator:20160921.fileexternalrequest.sql',1492953718,6953),('phabricator:20160927.phurl.ngrams.php',1492953718,375),('phabricator:20160927.phurl.ngrams.sql',1492953718,7421),('phabricator:20160928.repo.messagecount.sql',1492953718,381),('phabricator:20160928.tokentoken.sql',1492953718,6865),('phabricator:20161003.cal.01.utcepoch.sql',1492953718,55561),('phabricator:20161003.cal.02.parameters.sql',1492953718,16511),('phabricator:20161004.cal.01.noepoch.php',1492953718,1626),('phabricator:20161005.cal.01.rrules.php',1492953718,276),('phabricator:20161005.cal.02.export.sql',1492953718,9626),('phabricator:20161005.cal.03.exportxaction.sql',1492953718,7148),('phabricator:20161005.conpherence.image.1.sql',1492953718,12044),('phabricator:20161005.conpherence.image.2.php',1492953718,340),('phabricator:20161011.conpherence.ngrams.php',1492953718,313),('phabricator:20161011.conpherence.ngrams.sql',1492953718,9940),('phabricator:20161012.cal.01.import.sql',1492953718,7387),('phabricator:20161012.cal.02.importxaction.sql',1492953718,6560),('phabricator:20161012.cal.03.eventimport.sql',1492953718,64748),('phabricator:20161013.cal.01.importlog.sql',1492953718,6363),('phabricator:20161016.conpherence.imagephids.sql',1492953718,11462),('phabricator:20161025.phortune.contact.1.sql',1492953718,13783),('phabricator:20161025.phortune.merchant.image.1.sql',1492953718,12905),('phabricator:20161026.calendar.01.importtriggers.sql',1492953718,29953),('phabricator:20161027.calendar.01.externalinvitee.sql',1492953718,7485),('phabricator:20161029.phortune.invoice.1.sql',1492953718,36760),('phabricator:20161031.calendar.01.seriesparent.sql',1492953718,18327),('phabricator:20161031.calendar.02.notifylog.sql',1492953718,8078),('phabricator:20161101.calendar.01.noholiday.sql',1492953718,1286),('phabricator:20161101.calendar.02.removecolumns.sql',1492953719,93220),('phabricator:20161104.calendar.01.availability.sql',1492953719,16257),('phabricator:20161104.calendar.02.availdefault.sql',1492953719,430),('phabricator:20161115.phamepost.01.subtitle.sql',1492953719,17588),('phabricator:20161115.phamepost.02.header.sql',1492953719,16388),('phabricator:20161121.cluster.01.hoststate.sql',1492953719,8311),('phabricator:20161124.search.01.stopwords.sql',1492953719,7121),('phabricator:20161125.search.01.stemmed.sql',1492953719,6287),('phabricator:20161130.search.01.manual.sql',1492953719,6232),('phabricator:20161130.search.02.rebuild.php',1492953719,2751),('phabricator:20161210.dashboards.01.author.sql',1492953719,12555),('phabricator:20161210.dashboards.02.author.php',1492953719,746),('phabricator:20161211.menu.01.itemkey.sql',1492953719,8205),('phabricator:20161211.menu.02.itemprops.sql',1492953719,6574),('phabricator:20161211.menu.03.order.sql',1492953719,5930),('phabricator:20161212.dashboardpanel.01.author.sql',1492953719,11841),('phabricator:20161212.dashboardpanel.02.author.php',1492953719,730),('phabricator:20161212.dashboards.01.icon.sql',1492953719,14194),('phabricator:20161213.diff.01.hunks.php',1492953719,542),('phabricator:20161216.dashboard.ngram.01.sql',1492953719,15640),('phabricator:20161216.dashboard.ngram.02.php',1492953719,675),('phabricator:20170106.menu.01.customphd.sql',1492953719,12003),('phabricator:20170109.diff.01.commit.sql',1492953719,16340),('phabricator:20170119.menuitem.motivator.01.php',1492953719,219),('phabricator:20170131.dashboard.personal.01.php',1492953719,672),('phabricator:20170301.subtype.01.col.sql',1492953719,16642),('phabricator:20170301.subtype.02.default.sql',1492953719,394),('phabricator:20170301.subtype.03.taskcol.sql',1492953719,28706),('phabricator:20170301.subtype.04.taskdefault.sql',1492953719,375),('phabricator:20170303.people.01.avatar.sql',1492953719,50756),('phabricator:20170313.reviewers.01.sql',1492953719,9787),('phabricator:20170316.rawfiles.01.php',1492953719,993),('phabricator:20170320.reviewers.01.lastaction.sql',1492953719,13463),('phabricator:20170320.reviewers.02.lastcomment.sql',1492953719,16623),('phabricator:20170320.reviewers.03.migrate.php',1492953719,787),('phabricator:20170322.reviewers.04.actor.sql',1492953719,19471),('phabricator:20170328.reviewers.01.void.sql',1492953719,15326),('phabricator:20170406.hmac.01.keystore.sql',1492953719,7476),('phabricator:20170410.calendar.01.repair.php',1492953719,445),('phabricator:20170412.conpherence.01.picturecrop.sql',1492953719,287),('phabricator:20170413.conpherence.01.recentparty.sql',1492953719,11813),('phabricator:20170417.files.ngrams.sql',1492953719,9145),('phabricator:20170418.1.application.01.xaction.sql',1492953719,6782),('phabricator:20170418.1.application.02.edge.sql',1492953719,12783),('phabricator:20170418.files.isDeleted.sql',1492953719,26882),('phabricator:20170419.app.01.table.sql',1492953719,9433),('phabricator:20170419.thread.01.behind.sql',1492953719,17501),('phabricator:20170419.thread.02.status.sql',1492953719,18503),('phabricator:20170419.thread.03.touched.sql',1492953719,20247),('phabricator:daemonstatus.sql',1492953704,NULL),('phabricator:daemonstatuskey.sql',1492953704,NULL),('phabricator:daemontaskarchive.sql',1492953705,NULL),('phabricator:db.almanac',1492953699,NULL),('phabricator:db.application',1492953699,NULL),('phabricator:db.audit',1492953699,NULL),('phabricator:db.auth',1492953699,NULL),('phabricator:db.badges',1492953699,NULL),('phabricator:db.cache',1492953699,NULL),('phabricator:db.calendar',1492953699,NULL),('phabricator:db.chatlog',1492953699,NULL),('phabricator:db.conduit',1492953699,NULL),('phabricator:db.config',1492953699,NULL),('phabricator:db.conpherence',1492953699,NULL),('phabricator:db.countdown',1492953699,NULL),('phabricator:db.daemon',1492953699,NULL),('phabricator:db.dashboard',1492953699,NULL),('phabricator:db.differential',1492953699,NULL),('phabricator:db.diviner',1492953699,NULL),('phabricator:db.doorkeeper',1492953699,NULL),('phabricator:db.draft',1492953699,NULL),('phabricator:db.drydock',1492953699,NULL),('phabricator:db.fact',1492953699,NULL),('phabricator:db.feed',1492953699,NULL),('phabricator:db.file',1492953699,NULL),('phabricator:db.flag',1492953699,NULL),('phabricator:db.fund',1492953699,NULL),('phabricator:db.harbormaster',1492953699,NULL),('phabricator:db.herald',1492953699,NULL),('phabricator:db.legalpad',1492953699,NULL),('phabricator:db.maniphest',1492953699,NULL),('phabricator:db.meta_data',1492953699,NULL),('phabricator:db.metamta',1492953699,NULL),('phabricator:db.multimeter',1492953699,NULL),('phabricator:db.nuance',1492953699,NULL),('phabricator:db.oauth_server',1492953699,NULL),('phabricator:db.owners',1492953699,NULL),('phabricator:db.packages',1492953699,NULL),('phabricator:db.passphrase',1492953699,NULL),('phabricator:db.pastebin',1492953699,NULL),('phabricator:db.phame',1492953699,NULL),('phabricator:db.phlux',1492953699,NULL),('phabricator:db.pholio',1492953699,NULL),('phabricator:db.phortune',1492953699,NULL),('phabricator:db.phragment',1492953699,NULL),('phabricator:db.phrequent',1492953699,NULL),('phabricator:db.phriction',1492953699,NULL),('phabricator:db.phurl',1492953699,NULL),('phabricator:db.policy',1492953699,NULL),('phabricator:db.ponder',1492953699,NULL),('phabricator:db.project',1492953699,NULL),('phabricator:db.releeph',1492953699,NULL),('phabricator:db.repository',1492953699,NULL),('phabricator:db.search',1492953699,NULL),('phabricator:db.slowvote',1492953699,NULL),('phabricator:db.spaces',1492953699,NULL),('phabricator:db.system',1492953699,NULL),('phabricator:db.timeline',1492953699,NULL),('phabricator:db.token',1492953699,NULL),('phabricator:db.user',1492953699,NULL),('phabricator:db.worker',1492953699,NULL),('phabricator:db.xhpast',1492953699,NULL),('phabricator:db.xhpastview',1492953699,NULL),('phabricator:db.xhprof',1492953699,NULL),('phabricator:differentialbookmarks.sql',1492953704,NULL),('phabricator:draft-metadata.sql',1492953704,NULL),('phabricator:dropfileproxyimage.sql',1492953705,NULL),('phabricator:drydockresoucetype.sql',1492953705,NULL),('phabricator:drydocktaskid.sql',1492953705,NULL),('phabricator:edgetype.sql',1492953704,NULL),('phabricator:emailtable.sql',1492953704,NULL),('phabricator:emailtableport.sql',1492953704,NULL),('phabricator:emailtableremove.sql',1492953704,NULL),('phabricator:fact-raw.sql',1492953704,NULL),('phabricator:harbormasterobject.sql',1492953704,NULL),('phabricator:holidays.sql',1492953704,NULL),('phabricator:ldapinfo.sql',1492953704,NULL),('phabricator:legalpad-mailkey-populate.php',1492953706,NULL),('phabricator:legalpad-mailkey.sql',1492953706,NULL),('phabricator:liskcounters-task.sql',1492953705,NULL),('phabricator:liskcounters.php',1492953705,NULL),('phabricator:liskcounters.sql',1492953705,NULL),('phabricator:maniphestxcache.sql',1492953704,NULL),('phabricator:markupcache.sql',1492953704,NULL),('phabricator:migrate-differential-dependencies.php',1492953704,NULL),('phabricator:migrate-maniphest-dependencies.php',1492953704,NULL),('phabricator:migrate-maniphest-revisions.php',1492953704,NULL),('phabricator:migrate-project-edges.php',1492953704,NULL),('phabricator:owners-exclude.sql',1492953705,NULL),('phabricator:pastepolicy.sql',1492953704,NULL),('phabricator:phameblog.sql',1492953704,NULL),('phabricator:phamedomain.sql',1492953704,NULL),('phabricator:phameoneblog.sql',1492953705,NULL),('phabricator:phamepolicy.sql',1492953704,NULL),('phabricator:phiddrop.sql',1492953704,NULL),('phabricator:pholio.sql',1492953705,NULL),('phabricator:policy-project.sql',1492953704,NULL),('phabricator:ponder-comments.sql',1492953704,NULL),('phabricator:ponder-mailkey-populate.php',1492953704,NULL),('phabricator:ponder-mailkey.sql',1492953704,NULL),('phabricator:ponder.sql',1492953704,NULL),('phabricator:releeph.sql',1492953705,NULL),('phabricator:repository-lint.sql',1492953705,NULL),('phabricator:statustxt.sql',1492953705,NULL),('phabricator:symbolcontexts.sql',1492953704,NULL),('phabricator:testdatabase.sql',1492953704,NULL),('phabricator:threadtopic.sql',1492953704,NULL),('phabricator:userstatus.sql',1492953704,NULL),('phabricator:usertranslation.sql',1492953704,NULL),('phabricator:xhprof.sql',1492953704,NULL); +INSERT INTO `patch_status` VALUES ('phabricator:000.project.sql',1505229294,NULL),('phabricator:0000.legacy.sql',1505229294,NULL),('phabricator:001.maniphest_projects.sql',1505229294,NULL),('phabricator:002.oauth.sql',1505229294,NULL),('phabricator:003.more_oauth.sql',1505229294,NULL),('phabricator:004.daemonrepos.sql',1505229294,NULL),('phabricator:005.workers.sql',1505229294,NULL),('phabricator:006.repository.sql',1505229295,NULL),('phabricator:007.daemonlog.sql',1505229295,NULL),('phabricator:008.repoopt.sql',1505229295,NULL),('phabricator:009.repo_summary.sql',1505229295,NULL),('phabricator:010.herald.sql',1505229295,NULL),('phabricator:011.badcommit.sql',1505229295,NULL),('phabricator:012.dropphidtype.sql',1505229295,NULL),('phabricator:013.commitdetail.sql',1505229295,NULL),('phabricator:014.shortcuts.sql',1505229295,NULL),('phabricator:015.preferences.sql',1505229295,NULL),('phabricator:016.userrealnameindex.sql',1505229295,NULL),('phabricator:017.sessionkeys.sql',1505229295,NULL),('phabricator:018.owners.sql',1505229295,NULL),('phabricator:019.arcprojects.sql',1505229295,NULL),('phabricator:020.pathcapital.sql',1505229295,NULL),('phabricator:021.xhpastview.sql',1505229295,NULL),('phabricator:022.differentialcommit.sql',1505229295,NULL),('phabricator:023.dxkeys.sql',1505229295,NULL),('phabricator:024.mlistkeys.sql',1505229295,NULL),('phabricator:025.commentopt.sql',1505229295,NULL),('phabricator:026.diffpropkey.sql',1505229295,NULL),('phabricator:027.metamtakeys.sql',1505229295,NULL),('phabricator:028.systemagent.sql',1505229295,NULL),('phabricator:029.cursors.sql',1505229295,NULL),('phabricator:030.imagemacro.sql',1505229295,NULL),('phabricator:031.workerrace.sql',1505229295,NULL),('phabricator:032.viewtime.sql',1505229295,NULL),('phabricator:033.privtest.sql',1505229295,NULL),('phabricator:034.savedheader.sql',1505229295,NULL),('phabricator:035.proxyimage.sql',1505229295,NULL),('phabricator:036.mailkey.sql',1505229295,NULL),('phabricator:037.setuptest.sql',1505229295,NULL),('phabricator:038.admin.sql',1505229295,NULL),('phabricator:039.userlog.sql',1505229295,NULL),('phabricator:040.transform.sql',1505229295,NULL),('phabricator:041.heraldrepetition.sql',1505229295,NULL),('phabricator:042.commentmetadata.sql',1505229295,NULL),('phabricator:043.pastebin.sql',1505229295,NULL),('phabricator:044.countdown.sql',1505229295,NULL),('phabricator:045.timezone.sql',1505229295,NULL),('phabricator:046.conduittoken.sql',1505229295,NULL),('phabricator:047.projectstatus.sql',1505229295,NULL),('phabricator:048.relationshipkeys.sql',1505229296,NULL),('phabricator:049.projectowner.sql',1505229296,NULL),('phabricator:050.taskdenormal.sql',1505229296,NULL),('phabricator:051.projectfilter.sql',1505229296,NULL),('phabricator:052.pastelanguage.sql',1505229296,NULL),('phabricator:053.feed.sql',1505229296,NULL),('phabricator:054.subscribers.sql',1505229296,NULL),('phabricator:055.add_author_to_files.sql',1505229296,NULL),('phabricator:056.slowvote.sql',1505229296,NULL),('phabricator:057.parsecache.sql',1505229296,NULL),('phabricator:058.missingkeys.sql',1505229296,NULL),('phabricator:059.engines.php',1505229296,NULL),('phabricator:060.phriction.sql',1505229296,NULL),('phabricator:061.phrictioncontent.sql',1505229296,NULL),('phabricator:062.phrictionmenu.sql',1505229296,NULL),('phabricator:063.pasteforks.sql',1505229296,NULL),('phabricator:064.subprojects.sql',1505229296,NULL),('phabricator:065.sshkeys.sql',1505229296,NULL),('phabricator:066.phrictioncontent.sql',1505229296,NULL),('phabricator:067.preferences.sql',1505229296,NULL),('phabricator:068.maniphestauxiliarystorage.sql',1505229296,NULL),('phabricator:069.heraldxscript.sql',1505229296,NULL),('phabricator:070.differentialaux.sql',1505229296,NULL),('phabricator:071.contentsource.sql',1505229296,NULL),('phabricator:072.blamerevert.sql',1505229296,NULL),('phabricator:073.reposymbols.sql',1505229296,NULL),('phabricator:074.affectedpath.sql',1505229296,NULL),('phabricator:075.revisionhash.sql',1505229296,NULL),('phabricator:076.indexedlanguages.sql',1505229296,NULL),('phabricator:077.originalemail.sql',1505229296,NULL),('phabricator:078.nametoken.sql',1505229296,NULL),('phabricator:079.nametokenindex.php',1505229296,NULL),('phabricator:080.filekeys.sql',1505229296,NULL),('phabricator:081.filekeys.php',1505229296,NULL),('phabricator:082.xactionkey.sql',1505229296,NULL),('phabricator:083.dxviewtime.sql',1505229296,NULL),('phabricator:084.pasteauthorkey.sql',1505229296,NULL),('phabricator:085.packagecommitrelationship.sql',1505229296,NULL),('phabricator:086.formeraffil.sql',1505229296,NULL),('phabricator:087.phrictiondelete.sql',1505229296,NULL),('phabricator:088.audit.sql',1505229296,NULL),('phabricator:089.projectwiki.sql',1505229296,NULL),('phabricator:090.forceuniqueprojectnames.php',1505229296,NULL),('phabricator:091.uniqueslugkey.sql',1505229296,NULL),('phabricator:092.dropgithubnotification.sql',1505229296,NULL),('phabricator:093.gitremotes.php',1505229296,NULL),('phabricator:094.phrictioncolumn.sql',1505229296,NULL),('phabricator:095.directory.sql',1505229296,NULL),('phabricator:096.filename.sql',1505229296,NULL),('phabricator:097.heraldruletypes.sql',1505229297,NULL),('phabricator:098.heraldruletypemigration.php',1505229297,NULL),('phabricator:099.drydock.sql',1505229297,NULL),('phabricator:100.projectxaction.sql',1505229297,NULL),('phabricator:101.heraldruleapplied.sql',1505229297,NULL),('phabricator:102.heraldcleanup.php',1505229297,NULL),('phabricator:103.heraldedithistory.sql',1505229297,NULL),('phabricator:104.searchkey.sql',1505229297,NULL),('phabricator:105.mimetype.sql',1505229297,NULL),('phabricator:106.chatlog.sql',1505229297,NULL),('phabricator:107.oauthserver.sql',1505229297,NULL),('phabricator:108.oauthscope.sql',1505229297,NULL),('phabricator:109.oauthclientphidkey.sql',1505229297,NULL),('phabricator:110.commitaudit.sql',1505229297,NULL),('phabricator:111.commitauditmigration.php',1505229297,NULL),('phabricator:112.oauthaccesscoderedirecturi.sql',1505229297,NULL),('phabricator:113.lastreviewer.sql',1505229297,NULL),('phabricator:114.auditrequest.sql',1505229297,NULL),('phabricator:115.prepareutf8.sql',1505229297,NULL),('phabricator:116.utf8-backup-first-expect-wait.sql',1505229299,NULL),('phabricator:117.repositorydescription.php',1505229299,NULL),('phabricator:118.auditinline.sql',1505229299,NULL),('phabricator:119.filehash.sql',1505229299,NULL),('phabricator:120.noop.sql',1505229299,NULL),('phabricator:121.drydocklog.sql',1505229299,NULL),('phabricator:122.flag.sql',1505229299,NULL),('phabricator:123.heraldrulelog.sql',1505229299,NULL),('phabricator:124.subpriority.sql',1505229299,NULL),('phabricator:125.ipv6.sql',1505229299,NULL),('phabricator:126.edges.sql',1505229299,NULL),('phabricator:127.userkeybody.sql',1505229299,NULL),('phabricator:128.phabricatorcom.sql',1505229299,NULL),('phabricator:129.savedquery.sql',1505229299,NULL),('phabricator:130.denormalrevisionquery.sql',1505229299,NULL),('phabricator:131.migraterevisionquery.php',1505229299,NULL),('phabricator:132.phame.sql',1505229299,NULL),('phabricator:133.imagemacro.sql',1505229299,NULL),('phabricator:134.emptysearch.sql',1505229299,NULL),('phabricator:135.datecommitted.sql',1505229299,NULL),('phabricator:136.sex.sql',1505229299,NULL),('phabricator:137.auditmetadata.sql',1505229299,NULL),('phabricator:138.notification.sql',1505229299,NULL),('phabricator:20121209.pholioxactions.sql',1505229300,NULL),('phabricator:20121209.xmacroadd.sql',1505229300,NULL),('phabricator:20121209.xmacromigrate.php',1505229300,NULL),('phabricator:20121209.xmacromigratekey.sql',1505229300,NULL),('phabricator:20121220.generalcache.sql',1505229300,NULL),('phabricator:20121226.config.sql',1505229300,NULL),('phabricator:20130101.confxaction.sql',1505229300,NULL),('phabricator:20130102.metamtareceivedmailmessageidhash.sql',1505229300,NULL),('phabricator:20130103.filemetadata.sql',1505229300,NULL),('phabricator:20130111.conpherence.sql',1505229300,NULL),('phabricator:20130127.altheraldtranscript.sql',1505229300,NULL),('phabricator:20130131.conpherencepics.sql',1505229300,NULL),('phabricator:20130201.revisionunsubscribed.php',1505229300,NULL),('phabricator:20130201.revisionunsubscribed.sql',1505229300,NULL),('phabricator:20130214.chatlogchannel.sql',1505229300,NULL),('phabricator:20130214.chatlogchannelid.sql',1505229300,NULL),('phabricator:20130214.token.sql',1505229300,NULL),('phabricator:20130215.phabricatorfileaddttl.sql',1505229300,NULL),('phabricator:20130217.cachettl.sql',1505229300,NULL),('phabricator:20130218.longdaemon.sql',1505229300,NULL),('phabricator:20130218.updatechannelid.php',1505229300,NULL),('phabricator:20130219.commitsummary.sql',1505229300,NULL),('phabricator:20130219.commitsummarymig.php',1505229300,NULL),('phabricator:20130222.dropchannel.sql',1505229300,NULL),('phabricator:20130226.commitkey.sql',1505229301,NULL),('phabricator:20130304.lintauthor.sql',1505229301,NULL),('phabricator:20130310.xactionmeta.sql',1505229301,NULL),('phabricator:20130317.phrictionedge.sql',1505229301,NULL),('phabricator:20130319.conpherence.sql',1505229301,NULL),('phabricator:20130319.phabricatorfileexplicitupload.sql',1505229301,NULL),('phabricator:20130320.phlux.sql',1505229301,NULL),('phabricator:20130321.token.sql',1505229301,NULL),('phabricator:20130322.phortune.sql',1505229301,NULL),('phabricator:20130323.phortunepayment.sql',1505229301,NULL),('phabricator:20130324.phortuneproduct.sql',1505229301,NULL),('phabricator:20130330.phrequent.sql',1505229301,NULL),('phabricator:20130403.conpherencecache.sql',1505229301,NULL),('phabricator:20130403.conpherencecachemig.php',1505229301,NULL),('phabricator:20130409.commitdrev.php',1505229301,NULL),('phabricator:20130417.externalaccount.sql',1505229301,NULL),('phabricator:20130423.conpherenceindices.sql',1505229301,NULL),('phabricator:20130423.phortunepaymentrevised.sql',1505229301,NULL),('phabricator:20130423.updateexternalaccount.sql',1505229301,NULL),('phabricator:20130426.search_savedquery.sql',1505229301,NULL),('phabricator:20130502.countdownrevamp1.sql',1505229301,NULL),('phabricator:20130502.countdownrevamp2.php',1505229301,NULL),('phabricator:20130502.countdownrevamp3.sql',1505229301,NULL),('phabricator:20130507.releephrqmailkey.sql',1505229301,NULL),('phabricator:20130507.releephrqmailkeypop.php',1505229301,NULL),('phabricator:20130507.releephrqsimplifycols.sql',1505229301,NULL),('phabricator:20130508.releephtransactions.sql',1505229301,NULL),('phabricator:20130508.releephtransactionsmig.php',1505229301,NULL),('phabricator:20130508.search_namedquery.sql',1505229301,NULL),('phabricator:20130513.receviedmailstatus.sql',1505229301,NULL),('phabricator:20130519.diviner.sql',1505229301,NULL),('phabricator:20130521.dropconphimages.sql',1505229301,NULL),('phabricator:20130523.maniphest_owners.sql',1505229301,NULL),('phabricator:20130524.repoxactions.sql',1505229301,NULL),('phabricator:20130529.macroauthor.sql',1505229301,NULL),('phabricator:20130529.macroauthormig.php',1505229301,NULL),('phabricator:20130530.macrodatekey.sql',1505229301,NULL),('phabricator:20130530.pastekeys.sql',1505229301,NULL),('phabricator:20130530.sessionhash.php',1505229301,NULL),('phabricator:20130531.filekeys.sql',1505229301,NULL),('phabricator:20130602.morediviner.sql',1505229301,NULL),('phabricator:20130602.namedqueries.sql',1505229302,NULL),('phabricator:20130606.userxactions.sql',1505229302,NULL),('phabricator:20130607.xaccount.sql',1505229302,NULL),('phabricator:20130611.migrateoauth.php',1505229302,NULL),('phabricator:20130611.nukeldap.php',1505229302,NULL),('phabricator:20130613.authdb.sql',1505229302,NULL),('phabricator:20130619.authconf.php',1505229302,NULL),('phabricator:20130620.diffxactions.sql',1505229302,NULL),('phabricator:20130621.diffcommentphid.sql',1505229302,NULL),('phabricator:20130621.diffcommentphidmig.php',1505229302,NULL),('phabricator:20130621.diffcommentunphid.sql',1505229302,NULL),('phabricator:20130622.doorkeeper.sql',1505229302,NULL),('phabricator:20130628.legalpadv0.sql',1505229302,NULL),('phabricator:20130701.conduitlog.sql',1505229302,NULL),('phabricator:20130703.legalpaddocdenorm.php',1505229302,NULL),('phabricator:20130703.legalpaddocdenorm.sql',1505229302,NULL),('phabricator:20130709.droptimeline.sql',1505229302,NULL),('phabricator:20130709.legalpadsignature.sql',1505229302,NULL),('phabricator:20130711.pholioimageobsolete.php',1505229302,NULL),('phabricator:20130711.pholioimageobsolete.sql',1505229302,NULL),('phabricator:20130711.pholioimageobsolete2.sql',1505229302,NULL),('phabricator:20130711.trimrealnames.php',1505229302,NULL),('phabricator:20130714.votexactions.sql',1505229302,NULL),('phabricator:20130715.votecomments.php',1505229302,NULL),('phabricator:20130715.voteedges.sql',1505229302,NULL),('phabricator:20130716.archivememberlessprojects.php',1505229302,NULL),('phabricator:20130722.pholioreplace.sql',1505229302,NULL),('phabricator:20130723.taskstarttime.sql',1505229302,NULL),('phabricator:20130726.ponderxactions.sql',1505229302,NULL),('phabricator:20130727.ponderquestionstatus.sql',1505229302,NULL),('phabricator:20130728.ponderunique.php',1505229302,NULL),('phabricator:20130728.ponderuniquekey.sql',1505229302,NULL),('phabricator:20130728.ponderxcomment.php',1505229302,NULL),('phabricator:20130731.releephcutpointidentifier.sql',1505229302,NULL),('phabricator:20130731.releephproject.sql',1505229302,NULL),('phabricator:20130731.releephrepoid.sql',1505229302,NULL),('phabricator:20130801.pastexactions.php',1505229302,NULL),('phabricator:20130801.pastexactions.sql',1505229302,NULL),('phabricator:20130802.heraldphid.sql',1505229302,NULL),('phabricator:20130802.heraldphids.php',1505229302,NULL),('phabricator:20130802.heraldphidukey.sql',1505229302,NULL),('phabricator:20130802.heraldxactions.sql',1505229302,NULL),('phabricator:20130805.pasteedges.sql',1505229302,NULL),('phabricator:20130805.pastemailkey.sql',1505229302,NULL),('phabricator:20130805.pastemailkeypop.php',1505229302,NULL),('phabricator:20130814.usercustom.sql',1505229302,NULL),('phabricator:20130820.file-mailkey-populate.php',1505229303,NULL),('phabricator:20130820.filemailkey.sql',1505229303,NULL),('phabricator:20130820.filexactions.sql',1505229303,NULL),('phabricator:20130820.releephxactions.sql',1505229303,NULL),('phabricator:20130826.divinernode.sql',1505229303,NULL),('phabricator:20130912.maniphest.1.touch.sql',1505229303,NULL),('phabricator:20130912.maniphest.2.created.sql',1505229303,NULL),('phabricator:20130912.maniphest.3.nameindex.sql',1505229303,NULL),('phabricator:20130912.maniphest.4.fillindex.php',1505229303,NULL),('phabricator:20130913.maniphest.1.migratesearch.php',1505229303,NULL),('phabricator:20130914.usercustom.sql',1505229303,NULL),('phabricator:20130915.maniphestcustom.sql',1505229303,NULL),('phabricator:20130915.maniphestmigrate.php',1505229303,NULL),('phabricator:20130915.maniphestqdrop.sql',1505229303,NULL),('phabricator:20130919.mfieldconf.php',1505229303,NULL),('phabricator:20130920.repokeyspolicy.sql',1505229303,NULL),('phabricator:20130921.mtransactions.sql',1505229303,NULL),('phabricator:20130921.xmigratemaniphest.php',1505229303,NULL),('phabricator:20130923.mrename.sql',1505229303,NULL),('phabricator:20130924.mdraftkey.sql',1505229303,NULL),('phabricator:20130925.mpolicy.sql',1505229303,NULL),('phabricator:20130925.xpolicy.sql',1505229303,NULL),('phabricator:20130926.dcustom.sql',1505229303,NULL),('phabricator:20130926.dinkeys.sql',1505229303,NULL),('phabricator:20130926.dinline.php',1505229303,NULL),('phabricator:20130927.audiomacro.sql',1505229303,NULL),('phabricator:20130929.filepolicy.sql',1505229303,NULL),('phabricator:20131004.dxedgekey.sql',1505229303,NULL),('phabricator:20131004.dxreviewers.php',1505229303,NULL),('phabricator:20131006.hdisable.sql',1505229303,NULL),('phabricator:20131010.pstorage.sql',1505229303,NULL),('phabricator:20131015.cpolicy.sql',1505229303,NULL),('phabricator:20131020.col1.sql',1505229303,NULL),('phabricator:20131020.harbormaster.sql',1505229303,NULL),('phabricator:20131020.pcustom.sql',1505229303,NULL),('phabricator:20131020.pxaction.sql',1505229303,NULL),('phabricator:20131020.pxactionmig.php',1505229303,NULL),('phabricator:20131025.repopush.sql',1505229304,NULL),('phabricator:20131026.commitstatus.sql',1505229304,NULL),('phabricator:20131030.repostatusmessage.sql',1505229304,NULL),('phabricator:20131031.vcspassword.sql',1505229304,NULL),('phabricator:20131105.buildstep.sql',1505229304,NULL),('phabricator:20131106.diffphid.1.col.sql',1505229304,NULL),('phabricator:20131106.diffphid.2.mig.php',1505229304,NULL),('phabricator:20131106.diffphid.3.key.sql',1505229304,NULL),('phabricator:20131106.nuance-v0.sql',1505229304,NULL),('phabricator:20131107.buildlog.sql',1505229304,NULL),('phabricator:20131112.userverified.1.col.sql',1505229304,NULL),('phabricator:20131112.userverified.2.mig.php',1505229304,NULL),('phabricator:20131118.ownerorder.php',1505229304,NULL),('phabricator:20131119.passphrase.sql',1505229304,NULL),('phabricator:20131120.nuancesourcetype.sql',1505229304,NULL),('phabricator:20131121.passphraseedge.sql',1505229304,NULL),('phabricator:20131121.repocredentials.1.col.sql',1505229304,NULL),('phabricator:20131121.repocredentials.2.mig.php',1505229304,NULL),('phabricator:20131122.repomirror.sql',1505229304,NULL),('phabricator:20131123.drydockblueprintpolicy.sql',1505229304,NULL),('phabricator:20131129.drydockresourceblueprint.sql',1505229304,NULL),('phabricator:20131204.pushlog.sql',1505229304,NULL),('phabricator:20131205.buildsteporder.sql',1505229304,NULL),('phabricator:20131205.buildstepordermig.php',1505229304,NULL),('phabricator:20131205.buildtargets.sql',1505229304,NULL),('phabricator:20131206.phragment.sql',1505229304,NULL),('phabricator:20131206.phragmentnull.sql',1505229304,NULL),('phabricator:20131208.phragmentsnapshot.sql',1505229305,NULL),('phabricator:20131211.phragmentedges.sql',1505229305,NULL),('phabricator:20131217.pushlogphid.1.col.sql',1505229305,NULL),('phabricator:20131217.pushlogphid.2.mig.php',1505229305,NULL),('phabricator:20131217.pushlogphid.3.key.sql',1505229305,NULL),('phabricator:20131219.pxdrop.sql',1505229305,NULL),('phabricator:20131224.harbormanual.sql',1505229305,NULL),('phabricator:20131227.heraldobject.sql',1505229305,NULL),('phabricator:20131231.dropshortcut.sql',1505229305,NULL),('phabricator:20131302.maniphestvalue.sql',1505229301,NULL),('phabricator:20140104.harbormastercmd.sql',1505229305,NULL),('phabricator:20140106.macromailkey.1.sql',1505229305,NULL),('phabricator:20140106.macromailkey.2.php',1505229305,NULL),('phabricator:20140108.ddbpname.1.sql',1505229305,NULL),('phabricator:20140108.ddbpname.2.php',1505229305,NULL),('phabricator:20140109.ddxactions.sql',1505229305,NULL),('phabricator:20140109.projectcolumnsdates.sql',1505229305,NULL),('phabricator:20140113.legalpadsig.1.sql',1505229305,NULL),('phabricator:20140113.legalpadsig.2.php',1505229305,NULL),('phabricator:20140115.auth.1.id.sql',1505229305,NULL),('phabricator:20140115.auth.2.expires.sql',1505229305,NULL),('phabricator:20140115.auth.3.unlimit.php',1505229305,NULL),('phabricator:20140115.legalpadsigkey.sql',1505229305,NULL),('phabricator:20140116.reporefcursor.sql',1505229305,NULL),('phabricator:20140126.diff.1.parentrevisionid.sql',1505229305,NULL),('phabricator:20140126.diff.2.repositoryphid.sql',1505229305,NULL),('phabricator:20140130.dash.1.board.sql',1505229305,NULL),('phabricator:20140130.dash.2.panel.sql',1505229305,NULL),('phabricator:20140130.dash.3.boardxaction.sql',1505229305,NULL),('phabricator:20140130.dash.4.panelxaction.sql',1505229305,NULL),('phabricator:20140130.mail.1.retry.sql',1505229305,NULL),('phabricator:20140130.mail.2.next.sql',1505229305,NULL),('phabricator:20140201.gc.1.mailsent.sql',1505229305,NULL),('phabricator:20140201.gc.2.mailreceived.sql',1505229305,NULL),('phabricator:20140205.cal.1.rename.sql',1505229305,NULL),('phabricator:20140205.cal.2.phid-col.sql',1505229305,NULL),('phabricator:20140205.cal.3.phid-mig.php',1505229305,NULL),('phabricator:20140205.cal.4.phid-key.sql',1505229305,NULL),('phabricator:20140210.herald.rule-condition-mig.php',1505229305,NULL),('phabricator:20140210.projcfield.1.blurb.php',1505229305,NULL),('phabricator:20140210.projcfield.2.piccol.sql',1505229305,NULL),('phabricator:20140210.projcfield.3.picmig.sql',1505229305,NULL),('phabricator:20140210.projcfield.4.memmig.sql',1505229305,NULL),('phabricator:20140210.projcfield.5.dropprofile.sql',1505229305,NULL),('phabricator:20140211.dx.1.nullablechangesetid.sql',1505229305,NULL),('phabricator:20140211.dx.2.migcommenttext.php',1505229305,NULL),('phabricator:20140211.dx.3.migsubscriptions.sql',1505229305,NULL),('phabricator:20140211.dx.999.drop.relationships.sql',1505229305,NULL),('phabricator:20140212.dx.1.armageddon.php',1505229305,NULL),('phabricator:20140214.clean.1.legacycommentid.sql',1505229305,NULL),('phabricator:20140214.clean.2.dropcomment.sql',1505229305,NULL),('phabricator:20140214.clean.3.dropinline.sql',1505229305,NULL),('phabricator:20140218.differentialdraft.sql',1505229305,NULL),('phabricator:20140218.passwords.1.extend.sql',1505229305,NULL),('phabricator:20140218.passwords.2.prefix.sql',1505229305,NULL),('phabricator:20140218.passwords.3.vcsextend.sql',1505229305,NULL),('phabricator:20140218.passwords.4.vcs.php',1505229305,NULL),('phabricator:20140223.bigutf8scratch.sql',1505229305,NULL),('phabricator:20140224.dxclean.1.datecommitted.sql',1505229305,NULL),('phabricator:20140226.dxcustom.1.fielddata.php',1505229305,NULL),('phabricator:20140226.dxcustom.99.drop.sql',1505229305,NULL),('phabricator:20140228.dxcomment.1.sql',1505229305,NULL),('phabricator:20140305.diviner.1.slugcol.sql',1505229305,NULL),('phabricator:20140305.diviner.2.slugkey.sql',1505229306,NULL),('phabricator:20140311.mdroplegacy.sql',1505229306,NULL),('phabricator:20140314.projectcolumn.1.statuscol.sql',1505229306,NULL),('phabricator:20140314.projectcolumn.2.statuskey.sql',1505229306,NULL),('phabricator:20140317.mupdatedkey.sql',1505229306,NULL),('phabricator:20140321.harbor.1.bxaction.sql',1505229306,NULL),('phabricator:20140321.mstatus.1.col.sql',1505229306,NULL),('phabricator:20140321.mstatus.2.mig.php',1505229306,NULL),('phabricator:20140323.harbor.1.renames.php',1505229306,NULL),('phabricator:20140323.harbor.2.message.sql',1505229306,NULL),('phabricator:20140325.push.1.event.sql',1505229306,NULL),('phabricator:20140325.push.2.eventphid.sql',1505229306,NULL),('phabricator:20140325.push.3.groups.php',1505229306,NULL),('phabricator:20140325.push.4.prune.sql',1505229306,NULL),('phabricator:20140326.project.1.colxaction.sql',1505229306,NULL),('phabricator:20140328.releeph.1.productxaction.sql',1505229306,NULL),('phabricator:20140330.flagtext.sql',1505229306,NULL),('phabricator:20140402.actionlog.sql',1505229306,NULL),('phabricator:20140410.accountsecret.1.sql',1505229306,NULL),('phabricator:20140410.accountsecret.2.php',1505229306,NULL),('phabricator:20140416.harbor.1.sql',1505229306,NULL),('phabricator:20140420.rel.1.objectphid.sql',1505229306,NULL),('phabricator:20140420.rel.2.objectmig.php',1505229306,NULL),('phabricator:20140421.slowvotecolumnsisclosed.sql',1505229306,NULL),('phabricator:20140423.session.1.hisec.sql',1505229306,NULL),('phabricator:20140427.mfactor.1.sql',1505229306,NULL),('phabricator:20140430.auth.1.partial.sql',1505229306,NULL),('phabricator:20140430.dash.1.paneltype.sql',1505229306,NULL),('phabricator:20140430.dash.2.edge.sql',1505229306,NULL),('phabricator:20140501.passphraselockcredential.sql',1505229306,NULL),('phabricator:20140501.remove.1.dlog.sql',1505229306,NULL),('phabricator:20140507.smstable.sql',1505229306,NULL),('phabricator:20140509.coverage.1.sql',1505229306,NULL),('phabricator:20140509.dashboardlayoutconfig.sql',1505229306,NULL),('phabricator:20140512.dparents.1.sql',1505229306,NULL),('phabricator:20140514.harbormasterbuildabletransaction.sql',1505229306,NULL),('phabricator:20140514.pholiomockclose.sql',1505229306,NULL),('phabricator:20140515.trust-emails.sql',1505229306,NULL),('phabricator:20140517.dxbinarycache.sql',1505229306,NULL),('phabricator:20140518.dxmorebinarycache.sql',1505229306,NULL),('phabricator:20140519.dashboardinstall.sql',1505229306,NULL),('phabricator:20140520.authtemptoken.sql',1505229306,NULL),('phabricator:20140521.projectslug.1.create.sql',1505229306,NULL),('phabricator:20140521.projectslug.2.mig.php',1505229306,NULL),('phabricator:20140522.projecticon.sql',1505229306,NULL),('phabricator:20140524.auth.mfa.cache.sql',1505229306,NULL),('phabricator:20140525.hunkmodern.sql',1505229306,NULL),('phabricator:20140615.pholioedit.1.sql',1505229306,NULL),('phabricator:20140615.pholioedit.2.sql',1505229306,NULL),('phabricator:20140617.daemon.explicit-argv.sql',1505229306,NULL),('phabricator:20140617.daemonlog.sql',1505229306,NULL),('phabricator:20140624.projcolor.1.sql',1505229306,NULL),('phabricator:20140624.projcolor.2.sql',1505229306,NULL),('phabricator:20140629.dasharchive.1.sql',1505229306,NULL),('phabricator:20140629.legalsig.1.sql',1505229306,NULL),('phabricator:20140629.legalsig.2.php',1505229306,NULL),('phabricator:20140701.legalexemption.1.sql',1505229306,NULL),('phabricator:20140701.legalexemption.2.sql',1505229306,NULL),('phabricator:20140703.legalcorp.1.sql',1505229306,NULL),('phabricator:20140703.legalcorp.2.sql',1505229306,NULL),('phabricator:20140703.legalcorp.3.sql',1505229306,NULL),('phabricator:20140703.legalcorp.4.sql',1505229306,NULL),('phabricator:20140703.legalcorp.5.sql',1505229306,NULL),('phabricator:20140704.harbormasterstep.1.sql',1505229306,NULL),('phabricator:20140704.harbormasterstep.2.sql',1505229306,NULL),('phabricator:20140704.legalpreamble.1.sql',1505229307,NULL),('phabricator:20140706.harbormasterdepend.1.php',1505229307,NULL),('phabricator:20140706.pedge.1.sql',1505229307,NULL),('phabricator:20140711.pnames.1.sql',1505229307,NULL),('phabricator:20140711.pnames.2.php',1505229307,NULL),('phabricator:20140711.workerpriority.sql',1505229307,NULL),('phabricator:20140712.projcoluniq.sql',1505229307,NULL),('phabricator:20140721.phortune.1.cart.sql',1505229307,NULL),('phabricator:20140721.phortune.2.purchase.sql',1505229307,NULL),('phabricator:20140721.phortune.3.charge.sql',1505229307,NULL),('phabricator:20140721.phortune.4.cartstatus.sql',1505229307,NULL),('phabricator:20140721.phortune.5.cstatusdefault.sql',1505229307,NULL),('phabricator:20140721.phortune.6.onetimecharge.sql',1505229307,NULL),('phabricator:20140721.phortune.7.nullmethod.sql',1505229307,NULL),('phabricator:20140722.appname.php',1505229307,NULL),('phabricator:20140722.audit.1.xactions.sql',1505229307,NULL),('phabricator:20140722.audit.2.comments.sql',1505229307,NULL),('phabricator:20140722.audit.3.miginlines.php',1505229307,NULL),('phabricator:20140722.audit.4.migtext.php',1505229307,NULL),('phabricator:20140722.renameauth.php',1505229307,NULL),('phabricator:20140723.apprenamexaction.sql',1505229307,NULL),('phabricator:20140725.audit.1.migxactions.php',1505229307,NULL),('phabricator:20140731.audit.1.subscribers.php',1505229307,NULL),('phabricator:20140731.cancdn.php',1505229307,NULL),('phabricator:20140731.harbormasterstepdesc.sql',1505229307,NULL),('phabricator:20140805.boardcol.1.sql',1505229307,NULL),('phabricator:20140805.boardcol.2.php',1505229307,NULL),('phabricator:20140807.harbormastertargettime.sql',1505229307,NULL),('phabricator:20140808.boardprop.1.sql',1505229307,NULL),('phabricator:20140808.boardprop.2.sql',1505229307,NULL),('phabricator:20140808.boardprop.3.php',1505229307,NULL),('phabricator:20140811.blob.1.sql',1505229307,NULL),('phabricator:20140811.blob.2.sql',1505229307,NULL),('phabricator:20140812.projkey.1.sql',1505229307,NULL),('phabricator:20140812.projkey.2.sql',1505229307,NULL),('phabricator:20140814.passphrasecredentialconduit.sql',1505229307,NULL),('phabricator:20140815.cancdncase.php',1505229307,NULL),('phabricator:20140818.harbormasterindex.1.sql',1505229307,NULL),('phabricator:20140821.harbormasterbuildgen.1.sql',1505229307,NULL),('phabricator:20140822.daemonenvhash.sql',1505229307,NULL),('phabricator:20140902.almanacdevice.1.sql',1505229307,NULL),('phabricator:20140904.macroattach.php',1505229307,NULL),('phabricator:20140911.fund.1.initiative.sql',1505229307,NULL),('phabricator:20140911.fund.2.xaction.sql',1505229307,NULL),('phabricator:20140911.fund.3.edge.sql',1505229307,NULL),('phabricator:20140911.fund.4.backer.sql',1505229307,NULL),('phabricator:20140911.fund.5.backxaction.sql',1505229307,NULL),('phabricator:20140914.betaproto.php',1505229307,NULL),('phabricator:20140917.project.canlock.sql',1505229307,NULL),('phabricator:20140918.schema.1.dropaudit.sql',1505229307,NULL),('phabricator:20140918.schema.2.dropauditinline.sql',1505229307,NULL),('phabricator:20140918.schema.3.wipecache.sql',1505229307,NULL),('phabricator:20140918.schema.4.cachetype.sql',1505229307,NULL),('phabricator:20140918.schema.5.slowvote.sql',1505229307,NULL),('phabricator:20140919.schema.01.calstatus.sql',1505229307,NULL),('phabricator:20140919.schema.02.calname.sql',1505229307,NULL),('phabricator:20140919.schema.03.dropaux.sql',1505229307,NULL),('phabricator:20140919.schema.04.droptaskproj.sql',1505229307,NULL),('phabricator:20140926.schema.01.droprelev.sql',1505229307,NULL),('phabricator:20140926.schema.02.droprelreqev.sql',1505229307,NULL),('phabricator:20140926.schema.03.dropldapinfo.sql',1505229307,NULL),('phabricator:20140926.schema.04.dropoauthinfo.sql',1505229307,NULL),('phabricator:20140926.schema.05.dropprojaffil.sql',1505229307,NULL),('phabricator:20140926.schema.06.dropsubproject.sql',1505229307,NULL),('phabricator:20140926.schema.07.droppondcom.sql',1505229307,NULL),('phabricator:20140927.schema.01.dropsearchq.sql',1505229307,NULL),('phabricator:20140927.schema.02.pholio1.sql',1505229307,NULL),('phabricator:20140927.schema.03.pholio2.sql',1505229307,NULL),('phabricator:20140927.schema.04.pholio3.sql',1505229307,NULL),('phabricator:20140927.schema.05.phragment1.sql',1505229307,NULL),('phabricator:20140927.schema.06.releeph1.sql',1505229307,NULL),('phabricator:20141001.schema.01.version.sql',1505229307,NULL),('phabricator:20141001.schema.02.taskmail.sql',1505229307,NULL),('phabricator:20141002.schema.01.liskcounter.sql',1505229307,NULL),('phabricator:20141002.schema.02.draftnull.sql',1505229307,NULL),('phabricator:20141004.currency.01.sql',1505229307,NULL),('phabricator:20141004.currency.02.sql',1505229307,NULL),('phabricator:20141004.currency.03.sql',1505229307,NULL),('phabricator:20141004.currency.04.sql',1505229307,NULL),('phabricator:20141004.currency.05.sql',1505229307,NULL),('phabricator:20141004.currency.06.sql',1505229307,NULL),('phabricator:20141004.harborliskcounter.sql',1505229307,NULL),('phabricator:20141005.phortuneproduct.sql',1505229308,NULL),('phabricator:20141006.phortunecart.sql',1505229308,NULL),('phabricator:20141006.phortunemerchant.sql',1505229308,NULL),('phabricator:20141006.phortunemerchantx.sql',1505229308,NULL),('phabricator:20141007.fundmerchant.sql',1505229308,NULL),('phabricator:20141007.fundrisks.sql',1505229308,NULL),('phabricator:20141007.fundtotal.sql',1505229308,NULL),('phabricator:20141007.phortunecartmerchant.sql',1505229308,NULL),('phabricator:20141007.phortunecharge.sql',1505229308,NULL),('phabricator:20141007.phortunepayment.sql',1505229308,NULL),('phabricator:20141007.phortuneprovider.sql',1505229308,NULL),('phabricator:20141007.phortuneproviderx.sql',1505229308,NULL),('phabricator:20141008.phortunemerchdesc.sql',1505229308,NULL),('phabricator:20141008.phortuneprovdis.sql',1505229308,NULL),('phabricator:20141008.phortunerefund.sql',1505229308,NULL),('phabricator:20141010.fundmailkey.sql',1505229308,NULL),('phabricator:20141011.phortunemerchedit.sql',1505229308,NULL),('phabricator:20141012.phortunecartxaction.sql',1505229308,NULL),('phabricator:20141013.phortunecartkey.sql',1505229308,NULL),('phabricator:20141016.almanac.device.sql',1505229308,NULL),('phabricator:20141016.almanac.dxaction.sql',1505229308,NULL),('phabricator:20141016.almanac.interface.sql',1505229308,NULL),('phabricator:20141016.almanac.network.sql',1505229308,NULL),('phabricator:20141016.almanac.nxaction.sql',1505229308,NULL),('phabricator:20141016.almanac.service.sql',1505229308,NULL),('phabricator:20141016.almanac.sxaction.sql',1505229308,NULL),('phabricator:20141017.almanac.binding.sql',1505229308,NULL),('phabricator:20141017.almanac.bxaction.sql',1505229308,NULL),('phabricator:20141025.phriction.1.xaction.sql',1505229308,NULL),('phabricator:20141025.phriction.2.xaction.sql',1505229308,NULL),('phabricator:20141025.phriction.mailkey.sql',1505229308,NULL),('phabricator:20141103.almanac.1.delprop.sql',1505229308,NULL),('phabricator:20141103.almanac.2.addprop.sql',1505229308,NULL),('phabricator:20141104.almanac.3.edge.sql',1505229308,NULL),('phabricator:20141105.ssh.1.rename.sql',1505229308,NULL),('phabricator:20141106.dropold.sql',1505229308,NULL),('phabricator:20141106.uniqdrafts.php',1505229308,NULL),('phabricator:20141107.phriction.policy.1.sql',1505229308,NULL),('phabricator:20141107.phriction.policy.2.php',1505229308,NULL),('phabricator:20141107.phriction.popkeys.php',1505229308,NULL),('phabricator:20141107.ssh.1.colname.sql',1505229308,NULL),('phabricator:20141107.ssh.2.keyhash.sql',1505229308,NULL),('phabricator:20141107.ssh.3.keyindex.sql',1505229308,NULL),('phabricator:20141107.ssh.4.keymig.php',1505229308,NULL),('phabricator:20141107.ssh.5.indexnull.sql',1505229308,NULL),('phabricator:20141107.ssh.6.indexkey.sql',1505229308,NULL),('phabricator:20141107.ssh.7.colnull.sql',1505229308,NULL),('phabricator:20141113.auditdupes.php',1505229308,NULL),('phabricator:20141118.diffxaction.sql',1505229308,NULL),('phabricator:20141119.commitpedge.sql',1505229308,NULL),('phabricator:20141119.differential.diff.policy.sql',1505229308,NULL),('phabricator:20141119.sshtrust.sql',1505229308,NULL),('phabricator:20141123.taskpriority.1.sql',1505229308,NULL),('phabricator:20141123.taskpriority.2.sql',1505229308,NULL),('phabricator:20141210.maniphestsubscribersmig.1.sql',1505229308,NULL),('phabricator:20141210.maniphestsubscribersmig.2.sql',1505229308,NULL),('phabricator:20141210.reposervice.sql',1505229309,NULL),('phabricator:20141212.conduittoken.sql',1505229309,NULL),('phabricator:20141215.almanacservicetype.sql',1505229309,NULL),('phabricator:20141217.almanacdevicelock.sql',1505229309,NULL),('phabricator:20141217.almanaclock.sql',1505229309,NULL),('phabricator:20141218.maniphestcctxn.php',1505229309,NULL),('phabricator:20141222.maniphestprojtxn.php',1505229309,NULL),('phabricator:20141223.daemonloguser.sql',1505229309,NULL),('phabricator:20141223.daemonobjectphid.sql',1505229309,NULL),('phabricator:20141230.pasteeditpolicycolumn.sql',1505229309,NULL),('phabricator:20141230.pasteeditpolicyexisting.sql',1505229309,NULL),('phabricator:20150102.policyname.php',1505229309,NULL),('phabricator:20150102.tasksubscriber.sql',1505229309,NULL),('phabricator:20150105.conpsearch.sql',1505229309,NULL),('phabricator:20150114.oauthserver.client.policy.sql',1505229309,NULL),('phabricator:20150115.applicationemails.sql',1505229309,NULL),('phabricator:20150115.trigger.1.sql',1505229309,NULL),('phabricator:20150115.trigger.2.sql',1505229309,NULL),('phabricator:20150116.maniphestapplicationemails.php',1505229309,NULL),('phabricator:20150120.maniphestdefaultauthor.php',1505229309,NULL),('phabricator:20150124.subs.1.sql',1505229309,NULL),('phabricator:20150129.pastefileapplicationemails.php',1505229309,NULL),('phabricator:20150130.phortune.1.subphid.sql',1505229309,NULL),('phabricator:20150130.phortune.2.subkey.sql',1505229309,NULL),('phabricator:20150131.phortune.1.defaultpayment.sql',1505229309,NULL),('phabricator:20150205.authprovider.autologin.sql',1505229309,NULL),('phabricator:20150205.daemonenv.sql',1505229309,NULL),('phabricator:20150209.invite.sql',1505229309,NULL),('phabricator:20150209.oauthclient.trust.sql',1505229309,NULL),('phabricator:20150210.invitephid.sql',1505229309,NULL),('phabricator:20150212.legalpad.session.1.sql',1505229309,NULL),('phabricator:20150212.legalpad.session.2.sql',1505229309,NULL),('phabricator:20150219.scratch.nonmutable.sql',1505229309,NULL),('phabricator:20150223.daemon.1.id.sql',1505229309,NULL),('phabricator:20150223.daemon.2.idlegacy.sql',1505229309,NULL),('phabricator:20150223.daemon.3.idkey.sql',1505229309,NULL),('phabricator:20150312.filechunk.1.sql',1505229309,NULL),('phabricator:20150312.filechunk.2.sql',1505229309,NULL),('phabricator:20150312.filechunk.3.sql',1505229309,NULL),('phabricator:20150317.conpherence.isroom.1.sql',1505229309,NULL),('phabricator:20150317.conpherence.isroom.2.sql',1505229309,NULL),('phabricator:20150317.conpherence.policy.sql',1505229309,NULL),('phabricator:20150410.nukeruleedit.sql',1505229309,NULL),('phabricator:20150420.invoice.1.sql',1505229309,NULL),('phabricator:20150420.invoice.2.sql',1505229309,NULL),('phabricator:20150425.isclosed.sql',1505229309,NULL),('phabricator:20150427.calendar.1.edge.sql',1505229309,NULL),('phabricator:20150427.calendar.1.xaction.sql',1505229309,NULL),('phabricator:20150427.calendar.2.xaction.sql',1505229309,NULL),('phabricator:20150428.calendar.1.iscancelled.sql',1505229309,NULL),('phabricator:20150428.calendar.1.name.sql',1505229309,NULL),('phabricator:20150429.calendar.1.invitee.sql',1505229309,NULL),('phabricator:20150430.calendar.1.policies.sql',1505229309,NULL),('phabricator:20150430.multimeter.1.sql',1505229309,NULL),('phabricator:20150430.multimeter.2.host.sql',1505229309,NULL),('phabricator:20150430.multimeter.3.viewer.sql',1505229309,NULL),('phabricator:20150430.multimeter.4.context.sql',1505229309,NULL),('phabricator:20150430.multimeter.5.label.sql',1505229309,NULL),('phabricator:20150501.calendar.1.reply.sql',1505229309,NULL),('phabricator:20150501.calendar.2.reply.php',1505229309,NULL),('phabricator:20150501.conpherencepics.sql',1505229309,NULL),('phabricator:20150503.repositorysymbols.1.sql',1505229309,NULL),('phabricator:20150503.repositorysymbols.2.php',1505229309,NULL),('phabricator:20150503.repositorysymbols.3.sql',1505229309,NULL),('phabricator:20150504.symbolsproject.1.php',1505229309,NULL),('phabricator:20150504.symbolsproject.2.sql',1505229309,NULL),('phabricator:20150506.calendarunnamedevents.1.php',1505229309,NULL),('phabricator:20150507.calendar.1.isallday.sql',1505229309,NULL),('phabricator:20150513.user.cache.1.sql',1505229310,NULL),('phabricator:20150514.calendar.status.sql',1505229310,NULL),('phabricator:20150514.phame.blog.xaction.sql',1505229310,NULL),('phabricator:20150514.user.cache.2.sql',1505229310,NULL),('phabricator:20150515.phame.post.xaction.sql',1505229310,NULL),('phabricator:20150515.project.mailkey.1.sql',1505229310,NULL),('phabricator:20150515.project.mailkey.2.php',1505229310,NULL),('phabricator:20150519.calendar.calendaricon.sql',1505229310,NULL),('phabricator:20150521.releephrepository.sql',1505229310,NULL),('phabricator:20150525.diff.hidden.1.sql',1505229310,NULL),('phabricator:20150526.owners.mailkey.1.sql',1505229310,NULL),('phabricator:20150526.owners.mailkey.2.php',1505229310,NULL),('phabricator:20150526.owners.xaction.sql',1505229310,NULL),('phabricator:20150527.calendar.recurringevents.sql',1505229310,NULL),('phabricator:20150601.spaces.1.namespace.sql',1505229310,NULL),('phabricator:20150601.spaces.2.xaction.sql',1505229310,NULL),('phabricator:20150602.mlist.1.sql',1505229310,NULL),('phabricator:20150602.mlist.2.php',1505229310,NULL),('phabricator:20150604.spaces.1.sql',1505229310,NULL),('phabricator:20150605.diviner.edges.sql',1505229310,NULL),('phabricator:20150605.diviner.editPolicy.sql',1505229310,NULL),('phabricator:20150605.diviner.xaction.sql',1505229310,NULL),('phabricator:20150606.mlist.1.php',1505229310,NULL),('phabricator:20150609.inline.sql',1505229310,NULL),('phabricator:20150609.spaces.1.pholio.sql',1505229310,NULL),('phabricator:20150609.spaces.2.maniphest.sql',1505229310,NULL),('phabricator:20150610.spaces.1.desc.sql',1505229310,NULL),('phabricator:20150610.spaces.2.edge.sql',1505229310,NULL),('phabricator:20150610.spaces.3.archive.sql',1505229310,NULL),('phabricator:20150611.spaces.1.mailxaction.sql',1505229310,NULL),('phabricator:20150611.spaces.2.appmail.sql',1505229310,NULL),('phabricator:20150616.divinerrepository.sql',1505229310,NULL),('phabricator:20150617.harbor.1.lint.sql',1505229310,NULL),('phabricator:20150617.harbor.2.unit.sql',1505229310,NULL),('phabricator:20150618.harbor.1.planauto.sql',1505229310,NULL),('phabricator:20150618.harbor.2.stepauto.sql',1505229310,NULL),('phabricator:20150618.harbor.3.buildauto.sql',1505229310,NULL),('phabricator:20150619.conpherencerooms.1.sql',1505229310,NULL),('phabricator:20150619.conpherencerooms.2.sql',1505229310,NULL),('phabricator:20150619.conpherencerooms.3.sql',1505229310,NULL),('phabricator:20150621.phrase.1.sql',1505229310,NULL),('phabricator:20150621.phrase.2.sql',1505229310,NULL),('phabricator:20150622.bulk.1.job.sql',1505229310,NULL),('phabricator:20150622.bulk.2.task.sql',1505229310,NULL),('phabricator:20150622.bulk.3.xaction.sql',1505229310,NULL),('phabricator:20150622.bulk.4.edge.sql',1505229310,NULL),('phabricator:20150622.metamta.1.phid-col.sql',1505229310,NULL),('phabricator:20150622.metamta.2.phid-mig.php',1505229310,NULL),('phabricator:20150622.metamta.3.phid-key.sql',1505229310,NULL),('phabricator:20150622.metamta.4.actor-phid-col.sql',1505229310,NULL),('phabricator:20150622.metamta.5.actor-phid-mig.php',1505229310,NULL),('phabricator:20150622.metamta.6.actor-phid-key.sql',1505229310,NULL),('phabricator:20150624.spaces.1.repo.sql',1505229310,NULL),('phabricator:20150626.spaces.1.calendar.sql',1505229310,NULL),('phabricator:20150630.herald.1.sql',1505229310,NULL),('phabricator:20150630.herald.2.sql',1505229310,NULL),('phabricator:20150701.herald.1.sql',1505229310,NULL),('phabricator:20150701.herald.2.sql',1505229310,NULL),('phabricator:20150702.spaces.1.slowvote.sql',1505229310,NULL),('phabricator:20150706.herald.1.sql',1505229310,NULL),('phabricator:20150707.herald.1.sql',1505229310,NULL),('phabricator:20150708.arcanistproject.sql',1505229310,NULL),('phabricator:20150708.herald.1.sql',1505229310,NULL),('phabricator:20150708.herald.2.sql',1505229310,NULL),('phabricator:20150708.herald.3.sql',1505229310,NULL),('phabricator:20150712.badges.1.sql',1505229310,NULL),('phabricator:20150714.spaces.countdown.1.sql',1505229310,NULL),('phabricator:20150717.herald.1.sql',1505229310,NULL),('phabricator:20150719.countdown.1.sql',1505229311,NULL),('phabricator:20150719.countdown.2.sql',1505229311,NULL),('phabricator:20150719.countdown.3.sql',1505229311,NULL),('phabricator:20150721.phurl.1.url.sql',1505229311,NULL),('phabricator:20150721.phurl.2.xaction.sql',1505229311,NULL),('phabricator:20150721.phurl.3.xactioncomment.sql',1505229311,NULL),('phabricator:20150721.phurl.4.url.sql',1505229311,NULL),('phabricator:20150721.phurl.5.edge.sql',1505229311,NULL),('phabricator:20150721.phurl.6.alias.sql',1505229311,NULL),('phabricator:20150721.phurl.7.authorphid.sql',1505229311,NULL),('phabricator:20150722.dashboard.1.sql',1505229311,NULL),('phabricator:20150722.dashboard.2.sql',1505229311,NULL),('phabricator:20150723.countdown.1.sql',1505229311,NULL),('phabricator:20150724.badges.comments.1.sql',1505229311,NULL),('phabricator:20150724.countdown.comments.1.sql',1505229311,NULL),('phabricator:20150725.badges.mailkey.1.sql',1505229311,NULL),('phabricator:20150725.badges.mailkey.2.php',1505229311,NULL),('phabricator:20150725.badges.viewpolicy.3.sql',1505229311,NULL),('phabricator:20150725.countdown.mailkey.1.sql',1505229311,NULL),('phabricator:20150725.countdown.mailkey.2.php',1505229311,NULL),('phabricator:20150725.slowvote.mailkey.1.sql',1505229311,NULL),('phabricator:20150725.slowvote.mailkey.2.php',1505229311,NULL),('phabricator:20150727.heraldaction.1.sql',1505229311,NULL),('phabricator:20150730.herald.1.sql',1505229311,NULL),('phabricator:20150730.herald.2.sql',1505229311,NULL),('phabricator:20150730.herald.3.sql',1505229311,NULL),('phabricator:20150730.herald.4.sql',1505229311,NULL),('phabricator:20150730.herald.5.sql',1505229311,NULL),('phabricator:20150730.herald.6.sql',1505229311,NULL),('phabricator:20150730.herald.7.sql',1505229311,NULL),('phabricator:20150803.herald.1.sql',1505229311,NULL),('phabricator:20150803.herald.2.sql',1505229311,NULL),('phabricator:20150804.ponder.answer.mailkey.1.sql',1505229311,NULL),('phabricator:20150804.ponder.answer.mailkey.2.php',1505229311,NULL),('phabricator:20150804.ponder.question.1.sql',1505229311,NULL),('phabricator:20150804.ponder.question.2.sql',1505229311,NULL),('phabricator:20150804.ponder.question.3.sql',1505229311,NULL),('phabricator:20150804.ponder.spaces.4.sql',1505229311,NULL),('phabricator:20150805.paste.status.1.sql',1505229311,NULL),('phabricator:20150805.paste.status.2.sql',1505229311,NULL),('phabricator:20150806.ponder.answer.1.sql',1505229311,NULL),('phabricator:20150806.ponder.editpolicy.2.sql',1505229311,NULL),('phabricator:20150806.ponder.status.1.sql',1505229311,NULL),('phabricator:20150806.ponder.status.2.sql',1505229311,NULL),('phabricator:20150806.ponder.status.3.sql',1505229311,NULL),('phabricator:20150808.ponder.vote.1.sql',1505229311,NULL),('phabricator:20150808.ponder.vote.2.sql',1505229311,NULL),('phabricator:20150812.ponder.answer.1.sql',1505229311,NULL),('phabricator:20150812.ponder.answer.2.sql',1505229311,NULL),('phabricator:20150814.harbormater.artifact.phid.sql',1505229311,NULL),('phabricator:20150815.owners.status.1.sql',1505229311,NULL),('phabricator:20150815.owners.status.2.sql',1505229311,NULL),('phabricator:20150823.nuance.queue.1.sql',1505229311,NULL),('phabricator:20150823.nuance.queue.2.sql',1505229311,NULL),('phabricator:20150823.nuance.queue.3.sql',1505229311,NULL),('phabricator:20150823.nuance.queue.4.sql',1505229311,NULL),('phabricator:20150828.ponder.wiki.1.sql',1505229311,NULL),('phabricator:20150829.ponder.dupe.1.sql',1505229311,NULL),('phabricator:20150904.herald.1.sql',1505229311,NULL),('phabricator:20150906.mailinglist.sql',1505229311,NULL),('phabricator:20150910.owners.custom.1.sql',1505229311,NULL),('phabricator:20150916.drydock.slotlocks.1.sql',1505229311,NULL),('phabricator:20150922.drydock.commands.1.sql',1505229311,NULL),('phabricator:20150923.drydock.resourceid.1.sql',1505229311,NULL),('phabricator:20150923.drydock.resourceid.2.sql',1505229311,NULL),('phabricator:20150923.drydock.resourceid.3.sql',1505229311,NULL),('phabricator:20150923.drydock.taskid.1.sql',1505229311,NULL),('phabricator:20150924.drydock.disable.1.sql',1505229311,NULL),('phabricator:20150924.drydock.status.1.sql',1505229311,NULL),('phabricator:20150928.drydock.rexpire.1.sql',1505229311,NULL),('phabricator:20150930.drydock.log.1.sql',1505229311,NULL),('phabricator:20151001.drydock.rname.1.sql',1505229312,NULL),('phabricator:20151002.dashboard.status.1.sql',1505229312,NULL),('phabricator:20151002.harbormaster.bparam.1.sql',1505229312,NULL),('phabricator:20151009.drydock.auth.1.sql',1505229312,NULL),('phabricator:20151010.drydock.auth.2.sql',1505229312,NULL),('phabricator:20151013.drydock.op.1.sql',1505229312,NULL),('phabricator:20151023.harborpolicy.1.sql',1505229312,NULL),('phabricator:20151023.harborpolicy.2.php',1505229312,NULL),('phabricator:20151023.patchduration.sql',1505229312,14707),('phabricator:20151030.harbormaster.initiator.sql',1505229312,20087),('phabricator:20151106.editengine.1.table.sql',1505229312,9727),('phabricator:20151106.editengine.2.xactions.sql',1505229312,7307),('phabricator:20151106.phame.post.mailkey.1.sql',1505229312,20376),('phabricator:20151106.phame.post.mailkey.2.php',1505229312,1318),('phabricator:20151107.phame.blog.mailkey.1.sql',1505229312,17775),('phabricator:20151107.phame.blog.mailkey.2.php',1505229312,704),('phabricator:20151108.phame.blog.joinpolicy.sql',1505229312,15522),('phabricator:20151108.xhpast.stderr.sql',1505229312,23616),('phabricator:20151109.phame.post.comments.1.sql',1505229312,8078),('phabricator:20151109.repository.coverage.1.sql',1505229312,893),('phabricator:20151109.xhpast.db.1.sql',1505229312,1560),('phabricator:20151109.xhpast.db.2.sql',1505229312,558),('phabricator:20151110.daemonenvhash.sql',1505229312,35993),('phabricator:20151111.phame.blog.archive.1.sql',1505229312,19773),('phabricator:20151111.phame.blog.archive.2.sql',1505229312,403),('phabricator:20151112.herald.edge.sql',1505229312,14166),('phabricator:20151116.owners.edge.sql',1505229312,13334),('phabricator:20151128.phame.blog.picture.1.sql',1505229312,16713),('phabricator:20151130.phurl.mailkey.1.sql',1505229312,18587),('phabricator:20151130.phurl.mailkey.2.php',1505229312,1132),('phabricator:20151202.versioneddraft.1.sql',1505229312,10406),('phabricator:20151207.editengine.1.sql',1505229312,80862),('phabricator:20151210.land.1.refphid.sql',1505229312,14814),('phabricator:20151210.land.2.refphid.php',1505229312,561),('phabricator:20151215.phame.1.autotitle.sql',1505229312,21908),('phabricator:20151218.key.1.keyphid.sql',1505229312,19006),('phabricator:20151218.key.2.keyphid.php',1505229312,391),('phabricator:20151219.proj.01.prislug.sql',1505229312,23570),('phabricator:20151219.proj.02.prislugkey.sql',1505229312,15523),('phabricator:20151219.proj.03.copyslug.sql',1505229312,373),('phabricator:20151219.proj.04.dropslugkey.sql',1505229312,8731),('phabricator:20151219.proj.05.dropslug.sql',1505229312,21708),('phabricator:20151219.proj.06.defaultpolicy.php',1505229312,915),('phabricator:20151219.proj.07.viewnull.sql',1505229312,14940),('phabricator:20151219.proj.08.editnull.sql',1505229312,12472),('phabricator:20151219.proj.09.joinnull.sql',1505229312,12507),('phabricator:20151219.proj.10.subcolumns.sql',1505229312,147026),('phabricator:20151219.proj.11.subprojectphids.sql',1505229312,28646),('phabricator:20151221.search.1.version.sql',1505229312,13333),('phabricator:20151221.search.2.ownersngrams.sql',1505229312,8185),('phabricator:20151221.search.3.reindex.php',1505229312,451),('phabricator:20151223.proj.01.paths.sql',1505229312,22899),('phabricator:20151223.proj.02.depths.sql',1505229312,31535),('phabricator:20151223.proj.03.pathkey.sql',1505229312,13308),('phabricator:20151223.proj.04.keycol.sql',1505229312,27422),('phabricator:20151223.proj.05.updatekeys.php',1505229312,365),('phabricator:20151223.proj.06.uniq.sql',1505229312,13132),('phabricator:20151226.reop.1.sql',1505229312,17967),('phabricator:20151227.proj.01.materialize.sql',1505229312,417),('phabricator:20151231.proj.01.icon.php',1505229312,1594),('phabricator:20160102.badges.award.sql',1505229313,10025),('phabricator:20160110.repo.01.slug.sql',1505229313,31100),('phabricator:20160110.repo.02.slug.php',1505229313,476),('phabricator:20160111.repo.01.slugx.sql',1505229313,551),('phabricator:20160112.repo.01.uri.sql',1505229313,8069),('phabricator:20160112.repo.02.uri.index.php',1505229313,68),('phabricator:20160113.propanel.1.storage.sql',1505229313,7292),('phabricator:20160113.propanel.2.xaction.sql',1505229313,7204),('phabricator:20160119.project.1.silence.sql',1505229313,474),('phabricator:20160122.project.1.boarddefault.php',1505229313,779),('phabricator:20160124.people.1.icon.sql',1505229313,12923),('phabricator:20160124.people.2.icondefault.sql',1505229313,422),('phabricator:20160128.repo.1.pull.sql',1505229313,8863),('phabricator:20160201.revision.properties.1.sql',1505229313,16179),('phabricator:20160201.revision.properties.2.sql',1505229313,378),('phabricator:20160202.board.1.proxy.sql',1505229313,19176),('phabricator:20160202.ipv6.1.sql',1505229313,25051),('phabricator:20160202.ipv6.2.php',1505229313,1285),('phabricator:20160206.cover.1.sql',1505229313,27640),('phabricator:20160208.task.1.sql',1505229313,60422),('phabricator:20160208.task.2.sql',1505229313,58138),('phabricator:20160208.task.3.sql',1505229313,46890),('phabricator:20160212.proj.1.sql',1505229313,34169),('phabricator:20160212.proj.2.sql',1505229313,344),('phabricator:20160215.owners.policy.1.sql',1505229313,17217),('phabricator:20160215.owners.policy.2.sql',1505229313,15547),('phabricator:20160215.owners.policy.3.sql',1505229313,405),('phabricator:20160215.owners.policy.4.sql',1505229313,359),('phabricator:20160218.callsigns.1.sql',1505229313,11296),('phabricator:20160221.almanac.1.devicen.sql',1505229313,6896),('phabricator:20160221.almanac.2.devicei.php',1505229313,1178),('phabricator:20160221.almanac.3.servicen.sql',1505229313,6698),('phabricator:20160221.almanac.4.servicei.php',1505229313,600),('phabricator:20160221.almanac.5.networkn.sql',1505229313,6506),('phabricator:20160221.almanac.6.networki.php',1505229313,941),('phabricator:20160221.almanac.7.namespacen.sql',1505229313,7199),('phabricator:20160221.almanac.8.namespace.sql',1505229313,7093),('phabricator:20160221.almanac.9.namespacex.sql',1505229313,6710),('phabricator:20160222.almanac.1.properties.php',1505229313,1060),('phabricator:20160223.almanac.1.bound.sql',1505229313,17026),('phabricator:20160223.almanac.2.lockbind.sql',1505229313,375),('phabricator:20160223.almanac.3.devicelock.sql',1505229313,18835),('phabricator:20160223.almanac.4.servicelock.sql',1505229313,22200),('phabricator:20160223.paste.fileedges.php',1505229313,519),('phabricator:20160225.almanac.1.disablebinding.sql',1505229313,22641),('phabricator:20160225.almanac.2.stype.sql',1505229313,8017),('phabricator:20160225.almanac.3.stype.php',1505229313,427),('phabricator:20160227.harbormaster.1.plann.sql',1505229313,7807),('phabricator:20160227.harbormaster.2.plani.php',1505229313,378),('phabricator:20160303.drydock.1.bluen.sql',1505229313,6891),('phabricator:20160303.drydock.2.bluei.php',1505229313,396),('phabricator:20160303.drydock.3.edge.sql',1505229313,14275),('phabricator:20160308.nuance.01.disabled.sql',1505229313,16906),('phabricator:20160308.nuance.02.cursordata.sql',1505229313,8314),('phabricator:20160308.nuance.03.sourcen.sql',1505229313,6308),('phabricator:20160308.nuance.04.sourcei.php',1505229313,1217),('phabricator:20160308.nuance.05.sourcename.sql',1505229313,10566),('phabricator:20160308.nuance.06.label.sql',1505229313,19354),('phabricator:20160308.nuance.07.itemtype.sql',1505229313,27249),('phabricator:20160308.nuance.08.itemkey.sql',1505229313,20363),('phabricator:20160308.nuance.09.itemcontainer.sql',1505229313,24353),('phabricator:20160308.nuance.10.itemkeyu.sql',1505229313,376),('phabricator:20160308.nuance.11.requestor.sql',1505229313,12897),('phabricator:20160308.nuance.12.queue.sql',1505229313,19233),('phabricator:20160316.lfs.01.token.resource.sql',1505229313,16537),('phabricator:20160316.lfs.02.token.user.sql',1505229313,15671),('phabricator:20160316.lfs.03.token.properties.sql',1505229313,16678),('phabricator:20160316.lfs.04.token.default.sql',1505229313,377),('phabricator:20160317.lfs.01.ref.sql',1505229313,7480),('phabricator:20160321.nuance.01.taskbridge.sql',1505229313,30440),('phabricator:20160322.nuance.01.itemcommand.sql',1505229313,11935),('phabricator:20160323.badgemigrate.sql',1505229313,1027),('phabricator:20160329.nuance.01.requestor.sql',1505229313,1791),('phabricator:20160329.nuance.02.requestorsource.sql',1505229313,1816),('phabricator:20160329.nuance.03.requestorxaction.sql',1505229313,1772),('phabricator:20160329.nuance.04.requestorcomment.sql',1505229313,1425),('phabricator:20160330.badges.migratequality.sql',1505229313,11451),('phabricator:20160330.badges.qualityxaction.mig.sql',1505229313,1561),('phabricator:20160331.fund.comments.1.sql',1505229313,6923),('phabricator:20160404.oauth.1.xaction.sql',1505229313,8413),('phabricator:20160405.oauth.2.disable.sql',1505229313,15191),('phabricator:20160406.badges.ngrams.php',1505229313,578),('phabricator:20160406.badges.ngrams.sql',1505229313,7954),('phabricator:20160406.columns.1.php',1505229313,437),('phabricator:20160411.repo.1.version.sql',1505229313,6469),('phabricator:20160418.repouri.1.sql',1505229313,6484),('phabricator:20160418.repouri.2.sql',1505229314,23815),('phabricator:20160418.repoversion.1.sql',1505229314,15654),('phabricator:20160419.pushlog.1.sql',1505229314,25941),('phabricator:20160424.locks.1.sql',1505229314,20267),('phabricator:20160426.searchedge.sql',1505229314,17197),('phabricator:20160428.repo.1.urixaction.sql',1505229314,6462),('phabricator:20160503.repo.01.lpath.sql',1505229314,21613),('phabricator:20160503.repo.02.lpathkey.sql',1505229314,14346),('phabricator:20160503.repo.03.lpathmigrate.php',1505229314,414),('phabricator:20160503.repo.04.mirrormigrate.php',1505229314,485),('phabricator:20160503.repo.05.urimigrate.php',1505229314,311),('phabricator:20160510.repo.01.uriindex.php',1505229314,4235),('phabricator:20160513.owners.01.autoreview.sql',1505229314,16067),('phabricator:20160513.owners.02.autoreviewnone.sql',1505229314,365),('phabricator:20160516.owners.01.dominion.sql',1505229314,20175),('phabricator:20160516.owners.02.dominionstrong.sql',1505229314,423),('phabricator:20160517.oauth.01.edge.sql',1505229314,14825),('phabricator:20160518.ssh.01.activecol.sql',1505229314,15395),('phabricator:20160518.ssh.02.activeval.sql',1505229314,337),('phabricator:20160518.ssh.03.activekey.sql',1505229314,10601),('phabricator:20160519.ssh.01.xaction.sql',1505229314,7825),('phabricator:20160531.pref.01.xaction.sql',1505229314,7035),('phabricator:20160531.pref.02.datecreatecol.sql',1505229314,11831),('phabricator:20160531.pref.03.datemodcol.sql',1505229314,14881),('phabricator:20160531.pref.04.datecreateval.sql',1505229314,459),('phabricator:20160531.pref.05.datemodval.sql',1505229314,394),('phabricator:20160531.pref.06.phidcol.sql',1505229314,16492),('phabricator:20160531.pref.07.phidval.php',1505229314,744),('phabricator:20160601.user.01.cache.sql',1505229314,9486),('phabricator:20160601.user.02.copyprefs.php',1505229314,1253),('phabricator:20160601.user.03.removetime.sql',1505229314,23634),('phabricator:20160601.user.04.removetranslation.sql',1505229314,21819),('phabricator:20160601.user.05.removesex.sql',1505229314,23387),('phabricator:20160603.user.01.removedcenabled.sql',1505229314,21090),('phabricator:20160603.user.02.removedctab.sql',1505229314,25564),('phabricator:20160603.user.03.removedcvisible.sql',1505229314,19922),('phabricator:20160604.user.01.stringmailprefs.php',1505229314,494),('phabricator:20160604.user.02.removeimagecache.sql',1505229314,21502),('phabricator:20160605.user.01.prefnulluser.sql',1505229314,11987),('phabricator:20160605.user.02.prefbuiltin.sql',1505229314,11726),('phabricator:20160605.user.03.builtinunique.sql',1505229314,12112),('phabricator:20160616.phame.blog.header.1.sql',1505229314,16566),('phabricator:20160616.repo.01.oldref.sql',1505229314,8781),('phabricator:20160617.harbormaster.01.arelease.sql',1505229314,17884),('phabricator:20160618.phame.blog.subtitle.sql',1505229314,18739),('phabricator:20160620.phame.blog.parentdomain.2.sql',1505229314,15869),('phabricator:20160620.phame.blog.parentsite.1.sql',1505229314,16664),('phabricator:20160623.phame.blog.fulldomain.1.sql',1505229314,16500),('phabricator:20160623.phame.blog.fulldomain.2.sql',1505229314,397),('phabricator:20160623.phame.blog.fulldomain.3.sql',1505229314,396),('phabricator:20160706.phame.blog.parentdomain.2.sql',1505229314,16071),('phabricator:20160706.phame.blog.parentsite.1.sql',1505229314,16648),('phabricator:20160707.calendar.01.stub.sql',1505229314,16706),('phabricator:20160711.files.01.builtin.sql',1505229314,27703),('phabricator:20160711.files.02.builtinkey.sql',1505229314,11792),('phabricator:20160713.event.01.host.sql',1505229314,12444),('phabricator:20160715.event.01.alldayfrom.sql',1505229314,16604),('phabricator:20160715.event.02.alldayto.sql',1505229314,15977),('phabricator:20160715.event.03.allday.php',1505229314,56),('phabricator:20160720.calendar.invitetxn.php',1505229314,589),('phabricator:20160721.pack.01.pub.sql',1505229314,7385),('phabricator:20160721.pack.02.pubxaction.sql',1505229314,6493),('phabricator:20160721.pack.03.edge.sql',1505229314,12007),('phabricator:20160721.pack.04.pkg.sql',1505229314,6712),('phabricator:20160721.pack.05.pkgxaction.sql',1505229314,6539),('phabricator:20160721.pack.06.version.sql',1505229314,6797),('phabricator:20160721.pack.07.versionxaction.sql',1505229314,6317),('phabricator:20160722.pack.01.pubngrams.sql',1505229314,6296),('phabricator:20160722.pack.02.pkgngrams.sql',1505229314,6651),('phabricator:20160722.pack.03.versionngrams.sql',1505229314,7330),('phabricator:20160810.commit.01.summarylength.sql',1505229314,11365),('phabricator:20160824.connectionlog.sql',1505229314,1280),('phabricator:20160824.repohint.01.hint.sql',1505229314,6434),('phabricator:20160824.repohint.02.movebad.php',1505229314,428),('phabricator:20160824.repohint.03.nukebad.sql',1505229314,1191),('phabricator:20160825.ponder.sql',1505229314,709),('phabricator:20160829.pastebin.01.language.sql',1505229314,10111),('phabricator:20160829.pastebin.02.language.sql',1505229314,567),('phabricator:20160913.conpherence.topic.1.sql',1505229314,12573),('phabricator:20160919.repo.messagecount.sql',1505229314,14535),('phabricator:20160919.repo.messagedefault.sql',1505229314,7168),('phabricator:20160921.fileexternalrequest.sql',1505229314,6804),('phabricator:20160927.phurl.ngrams.php',1505229314,378),('phabricator:20160927.phurl.ngrams.sql',1505229314,6850),('phabricator:20160928.repo.messagecount.sql',1505229314,396),('phabricator:20160928.tokentoken.sql',1505229314,6945),('phabricator:20161003.cal.01.utcepoch.sql',1505229315,50814),('phabricator:20161003.cal.02.parameters.sql',1505229315,18699),('phabricator:20161004.cal.01.noepoch.php',1505229315,1559),('phabricator:20161005.cal.01.rrules.php',1505229315,313),('phabricator:20161005.cal.02.export.sql',1505229315,7026),('phabricator:20161005.cal.03.exportxaction.sql',1505229315,7513),('phabricator:20161005.conpherence.image.1.sql',1505229315,12660),('phabricator:20161005.conpherence.image.2.php',1505229315,62),('phabricator:20161011.conpherence.ngrams.php',1505229315,320),('phabricator:20161011.conpherence.ngrams.sql',1505229315,8482),('phabricator:20161012.cal.01.import.sql',1505229315,8265),('phabricator:20161012.cal.02.importxaction.sql',1505229315,6919),('phabricator:20161012.cal.03.eventimport.sql',1505229315,76721),('phabricator:20161013.cal.01.importlog.sql',1505229315,8173),('phabricator:20161016.conpherence.imagephids.sql',1505229315,14268),('phabricator:20161025.phortune.contact.1.sql',1505229315,53977),('phabricator:20161025.phortune.merchant.image.1.sql',1505229315,29422),('phabricator:20161026.calendar.01.importtriggers.sql',1505229315,47454),('phabricator:20161027.calendar.01.externalinvitee.sql',1505229315,8092),('phabricator:20161029.phortune.invoice.1.sql',1505229315,28242),('phabricator:20161031.calendar.01.seriesparent.sql',1505229315,18636),('phabricator:20161031.calendar.02.notifylog.sql',1505229315,7982),('phabricator:20161101.calendar.01.noholiday.sql',1505229315,1735),('phabricator:20161101.calendar.02.removecolumns.sql',1505229315,105662),('phabricator:20161104.calendar.01.availability.sql',1505229315,18003),('phabricator:20161104.calendar.02.availdefault.sql',1505229315,442),('phabricator:20161115.phamepost.01.subtitle.sql',1505229315,17700),('phabricator:20161115.phamepost.02.header.sql',1505229315,21438),('phabricator:20161121.cluster.01.hoststate.sql',1505229315,9268),('phabricator:20161124.search.01.stopwords.sql',1505229315,6479),('phabricator:20161125.search.01.stemmed.sql',1505229315,7807),('phabricator:20161130.search.01.manual.sql',1505229315,7039),('phabricator:20161130.search.02.rebuild.php',1505229315,1862),('phabricator:20161210.dashboards.01.author.sql',1505229315,12501),('phabricator:20161210.dashboards.02.author.php',1505229315,916),('phabricator:20161211.menu.01.itemkey.sql',1505229315,8485),('phabricator:20161211.menu.02.itemprops.sql',1505229315,6725),('phabricator:20161211.menu.03.order.sql',1505229315,6170),('phabricator:20161212.dashboardpanel.01.author.sql',1505229315,13290),('phabricator:20161212.dashboardpanel.02.author.php',1505229315,735),('phabricator:20161212.dashboards.01.icon.sql',1505229315,15462),('phabricator:20161213.diff.01.hunks.php',1505229315,731),('phabricator:20161216.dashboard.ngram.01.sql',1505229315,16242),('phabricator:20161216.dashboard.ngram.02.php',1505229315,527),('phabricator:20170106.menu.01.customphd.sql',1505229315,12809),('phabricator:20170109.diff.01.commit.sql',1505229315,20022),('phabricator:20170119.menuitem.motivator.01.php',1505229315,252),('phabricator:20170131.dashboard.personal.01.php',1505229315,814),('phabricator:20170301.subtype.01.col.sql',1505229315,16520),('phabricator:20170301.subtype.02.default.sql',1505229315,416),('phabricator:20170301.subtype.03.taskcol.sql',1505229315,31002),('phabricator:20170301.subtype.04.taskdefault.sql',1505229315,355),('phabricator:20170303.people.01.avatar.sql',1505229315,44589),('phabricator:20170313.reviewers.01.sql',1505229315,13846),('phabricator:20170316.rawfiles.01.php',1505229315,1162),('phabricator:20170320.reviewers.01.lastaction.sql',1505229315,14750),('phabricator:20170320.reviewers.02.lastcomment.sql',1505229315,16419),('phabricator:20170320.reviewers.03.migrate.php',1505229315,855),('phabricator:20170322.reviewers.04.actor.sql',1505229315,16195),('phabricator:20170328.reviewers.01.void.sql',1505229315,16793),('phabricator:20170404.files.retroactive-content-hash.sql',1505229315,16177),('phabricator:20170406.hmac.01.keystore.sql',1505229315,6617),('phabricator:20170410.calendar.01.repair.php',1505229315,524),('phabricator:20170412.conpherence.01.picturecrop.sql',1505229315,325),('phabricator:20170413.conpherence.01.recentparty.sql',1505229315,12114),('phabricator:20170417.files.ngrams.sql',1505229315,8328),('phabricator:20170418.1.application.01.xaction.sql',1505229315,7862),('phabricator:20170418.1.application.02.edge.sql',1505229315,13057),('phabricator:20170418.files.isDeleted.sql',1505229316,33550),('phabricator:20170419.app.01.table.sql',1505229316,12496),('phabricator:20170419.thread.01.behind.sql',1505229316,18329),('phabricator:20170419.thread.02.status.sql',1505229316,37811),('phabricator:20170419.thread.03.touched.sql',1505229316,20278),('phabricator:20170424.user.01.verify.php',1505229316,369),('phabricator:20170427.owners.01.long.sql',1505229316,23690),('phabricator:20170504.1.slowvote.shuffle.sql',1505229316,14509),('phabricator:20170522.nuance.01.itemkey.sql',1505229316,20530),('phabricator:20170524.nuance.01.command.sql',1505229316,44533),('phabricator:20170524.nuance.02.commandstatus.sql',1505229316,14130),('phabricator:20170526.dropdifferentialdrafts.sql',1505229316,1085),('phabricator:20170526.milestones.php',1505229316,380),('phabricator:20170528.maniphestdupes.php',1505229316,307),('phabricator:20170612.repository.image.01.sql',1505229316,25441),('phabricator:20170614.taskstatus.sql',1505229316,19933),('phabricator:20170725.legalpad.date.01.sql',1505229316,794),('phabricator:20170811.differential.01.status.php',1505229316,380),('phabricator:20170811.differential.02.modernstatus.sql',1505229316,854),('phabricator:20170811.differential.03.modernxaction.php',1505229316,988),('phabricator:20170814.search.01.qconfig.sql',1505229316,6870),('phabricator:20170820.phame.01.post.views.sql',1505229316,16670),('phabricator:20170820.phame.02.post.views.sql',1505229316,349),('phabricator:20170824.search.01.saved.php',1505229316,597),('phabricator:20170825.phame.01.post.views.sql',1505229316,20405),('phabricator:20170828.ferret.01.taskdoc.sql',1505229316,7658),('phabricator:20170828.ferret.02.taskfield.sql',1505229316,6337),('phabricator:20170828.ferret.03.taskngrams.sql',1505229316,6695),('phabricator:20170830.ferret.01.unique.sql',1505229316,13328),('phabricator:20170830.ferret.02.term.sql',1505229316,15215),('phabricator:20170905.ferret.01.diff.doc.sql',1505229316,10278),('phabricator:20170905.ferret.02.diff.field.sql',1505229316,8206),('phabricator:20170905.ferret.03.diff.ngrams.sql',1505229316,7430),('phabricator:20170907.ferret.01.user.doc.sql',1505229316,6356),('phabricator:20170907.ferret.02.user.field.sql',1505229316,6335),('phabricator:20170907.ferret.03.user.ngrams.sql',1505229316,6307),('phabricator:20170907.ferret.04.fund.doc.sql',1505229316,6319),('phabricator:20170907.ferret.05.fund.field.sql',1505229316,5897),('phabricator:20170907.ferret.06.fund.ngrams.sql',1505229316,6511),('phabricator:20170907.ferret.07.passphrase.doc.sql',1505229316,5758),('phabricator:20170907.ferret.08.passphrase.field.sql',1505229316,6319),('phabricator:20170907.ferret.09.passphrase.ngrams.sql',1505229316,7128),('phabricator:20170907.ferret.10.owners.doc.sql',1505229316,6814),('phabricator:20170907.ferret.11.owners.field.sql',1505229316,6241),('phabricator:20170907.ferret.12.owners.ngrams.sql',1505229316,6010),('phabricator:20170907.ferret.13.blog.doc.sql',1505229316,6643),('phabricator:20170907.ferret.14.blog.field.sql',1505229316,7260),('phabricator:20170907.ferret.15.blog.ngrams.sql',1505229316,6603),('phabricator:20170907.ferret.16.post.doc.sql',1505229316,6246),('phabricator:20170907.ferret.17.post.field.sql',1505229316,6483),('phabricator:20170907.ferret.18.post.ngrams.sql',1505229316,6941),('phabricator:20170907.ferret.19.project.doc.sql',1505229316,5891),('phabricator:20170907.ferret.20.project.field.sql',1505229316,6006),('phabricator:20170907.ferret.21.project.ngrams.sql',1505229316,6046),('phabricator:20170907.ferret.22.phriction.doc.sql',1505229316,7390),('phabricator:20170907.ferret.23.phriction.field.sql',1505229316,5842),('phabricator:20170907.ferret.24.phriction.ngrams.sql',1505229316,6742),('phabricator:20170907.ferret.25.event.doc.sql',1505229316,6395),('phabricator:20170907.ferret.26.event.field.sql',1505229316,6675),('phabricator:20170907.ferret.27.event.ngrams.sql',1505229316,6619),('phabricator:20170907.ferret.28.mock.doc.sql',1505229316,7358),('phabricator:20170907.ferret.29.mock.field.sql',1505229316,5954),('phabricator:20170907.ferret.30.mock.ngrams.sql',1505229316,6756),('phabricator:20170907.ferret.31.repo.doc.sql',1505229316,6597),('phabricator:20170907.ferret.32.repo.field.sql',1505229316,7493),('phabricator:20170907.ferret.33.repo.ngrams.sql',1505229316,6181),('phabricator:20170907.ferret.34.commit.doc.sql',1505229316,6161),('phabricator:20170907.ferret.35.commit.field.sql',1505229316,6439),('phabricator:20170907.ferret.36.commit.ngrams.sql',1505229316,7055),('phabricator:20170912.ferret.01.activity.php',1505229316,396),('phabricator:daemonstatus.sql',1505229299,NULL),('phabricator:daemonstatuskey.sql',1505229300,NULL),('phabricator:daemontaskarchive.sql',1505229300,NULL),('phabricator:db.almanac',1505229294,NULL),('phabricator:db.application',1505229294,NULL),('phabricator:db.audit',1505229294,NULL),('phabricator:db.auth',1505229294,NULL),('phabricator:db.badges',1505229294,NULL),('phabricator:db.cache',1505229294,NULL),('phabricator:db.calendar',1505229294,NULL),('phabricator:db.chatlog',1505229294,NULL),('phabricator:db.conduit',1505229294,NULL),('phabricator:db.config',1505229294,NULL),('phabricator:db.conpherence',1505229294,NULL),('phabricator:db.countdown',1505229294,NULL),('phabricator:db.daemon',1505229294,NULL),('phabricator:db.dashboard',1505229294,NULL),('phabricator:db.differential',1505229294,NULL),('phabricator:db.diviner',1505229294,NULL),('phabricator:db.doorkeeper',1505229294,NULL),('phabricator:db.draft',1505229294,NULL),('phabricator:db.drydock',1505229294,NULL),('phabricator:db.fact',1505229294,NULL),('phabricator:db.feed',1505229294,NULL),('phabricator:db.file',1505229294,NULL),('phabricator:db.flag',1505229294,NULL),('phabricator:db.fund',1505229294,NULL),('phabricator:db.harbormaster',1505229294,NULL),('phabricator:db.herald',1505229294,NULL),('phabricator:db.legalpad',1505229294,NULL),('phabricator:db.maniphest',1505229294,NULL),('phabricator:db.meta_data',1505229294,NULL),('phabricator:db.metamta',1505229294,NULL),('phabricator:db.multimeter',1505229294,NULL),('phabricator:db.nuance',1505229294,NULL),('phabricator:db.oauth_server',1505229294,NULL),('phabricator:db.owners',1505229294,NULL),('phabricator:db.packages',1505229294,NULL),('phabricator:db.passphrase',1505229294,NULL),('phabricator:db.pastebin',1505229294,NULL),('phabricator:db.phame',1505229294,NULL),('phabricator:db.phlux',1505229294,NULL),('phabricator:db.pholio',1505229294,NULL),('phabricator:db.phortune',1505229294,NULL),('phabricator:db.phragment',1505229294,NULL),('phabricator:db.phrequent',1505229294,NULL),('phabricator:db.phriction',1505229294,NULL),('phabricator:db.phurl',1505229294,NULL),('phabricator:db.policy',1505229294,NULL),('phabricator:db.ponder',1505229294,NULL),('phabricator:db.project',1505229294,NULL),('phabricator:db.releeph',1505229294,NULL),('phabricator:db.repository',1505229294,NULL),('phabricator:db.search',1505229294,NULL),('phabricator:db.slowvote',1505229294,NULL),('phabricator:db.spaces',1505229294,NULL),('phabricator:db.system',1505229294,NULL),('phabricator:db.timeline',1505229294,NULL),('phabricator:db.token',1505229294,NULL),('phabricator:db.user',1505229294,NULL),('phabricator:db.worker',1505229294,NULL),('phabricator:db.xhpast',1505229294,NULL),('phabricator:db.xhpastview',1505229294,NULL),('phabricator:db.xhprof',1505229294,NULL),('phabricator:differentialbookmarks.sql',1505229299,NULL),('phabricator:draft-metadata.sql',1505229300,NULL),('phabricator:dropfileproxyimage.sql',1505229300,NULL),('phabricator:drydockresoucetype.sql',1505229300,NULL),('phabricator:drydocktaskid.sql',1505229300,NULL),('phabricator:edgetype.sql',1505229300,NULL),('phabricator:emailtable.sql',1505229299,NULL),('phabricator:emailtableport.sql',1505229299,NULL),('phabricator:emailtableremove.sql',1505229299,NULL),('phabricator:fact-raw.sql',1505229299,NULL),('phabricator:harbormasterobject.sql',1505229299,NULL),('phabricator:holidays.sql',1505229299,NULL),('phabricator:ldapinfo.sql',1505229299,NULL),('phabricator:legalpad-mailkey-populate.php',1505229302,NULL),('phabricator:legalpad-mailkey.sql',1505229302,NULL),('phabricator:liskcounters-task.sql',1505229300,NULL),('phabricator:liskcounters.php',1505229300,NULL),('phabricator:liskcounters.sql',1505229300,NULL),('phabricator:maniphestxcache.sql',1505229299,NULL),('phabricator:markupcache.sql',1505229299,NULL),('phabricator:migrate-differential-dependencies.php',1505229299,NULL),('phabricator:migrate-maniphest-dependencies.php',1505229299,NULL),('phabricator:migrate-maniphest-revisions.php',1505229299,NULL),('phabricator:migrate-project-edges.php',1505229299,NULL),('phabricator:owners-exclude.sql',1505229300,NULL),('phabricator:pastepolicy.sql',1505229300,NULL),('phabricator:phameblog.sql',1505229299,NULL),('phabricator:phamedomain.sql',1505229300,NULL),('phabricator:phameoneblog.sql',1505229300,NULL),('phabricator:phamepolicy.sql',1505229300,NULL),('phabricator:phiddrop.sql',1505229299,NULL),('phabricator:pholio.sql',1505229300,NULL),('phabricator:policy-project.sql',1505229300,NULL),('phabricator:ponder-comments.sql',1505229300,NULL),('phabricator:ponder-mailkey-populate.php',1505229300,NULL),('phabricator:ponder-mailkey.sql',1505229300,NULL),('phabricator:ponder.sql',1505229299,NULL),('phabricator:releeph.sql',1505229301,NULL),('phabricator:repository-lint.sql',1505229300,NULL),('phabricator:statustxt.sql',1505229300,NULL),('phabricator:symbolcontexts.sql',1505229299,NULL),('phabricator:testdatabase.sql',1505229299,NULL),('phabricator:threadtopic.sql',1505229299,NULL),('phabricator:userstatus.sql',1505229299,NULL),('phabricator:usertranslation.sql',1505229299,NULL),('phabricator:xhprof.sql',1505229300,NULL); CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_metamta` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; @@ -2139,8 +2236,7 @@ CREATE TABLE `owners_owner` ( CREATE TABLE `owners_package` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, - `name` varchar(128) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, - `originalName` varchar(255) COLLATE {$COLLATE_TEXT} NOT NULL, + `name` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, `description` longtext COLLATE {$COLLATE_TEXT} NOT NULL, `primaryOwnerPHID` varbinary(64) DEFAULT NULL, `auditingEnabled` tinyint(1) NOT NULL DEFAULT '0', @@ -2154,6 +2250,42 @@ CREATE TABLE `owners_package` ( UNIQUE KEY `key_phid` (`phid`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `owners_package_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `owners_package_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `owners_package_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `owners_packagetransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -2319,6 +2451,42 @@ CREATE TABLE `phame_blog` ( UNIQUE KEY `domain` (`domain`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `phame_blog_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phame_blog_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phame_blog_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `phame_blogtransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -2361,6 +2529,42 @@ CREATE TABLE `phame_post` ( KEY `bloggerPosts` (`bloggerPHID`,`visibility`,`datePublished`,`id`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `phame_post_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phame_post_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phame_post_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `phame_posttransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -2457,6 +2661,42 @@ CREATE TABLE `phriction_document` ( UNIQUE KEY `depth` (`depth`,`slug`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `phriction_document_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phriction_document_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `phriction_document_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `phriction_transaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -2643,6 +2883,42 @@ CREATE TABLE `project_datasourcetoken` ( KEY `projectID` (`projectID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `project_project_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `project_project_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `project_project_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `project_slug` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `projectPHID` varbinary(64) NOT NULL, @@ -2715,6 +2991,7 @@ CREATE TABLE `repository` ( `spacePHID` varbinary(64) DEFAULT NULL, `repositorySlug` varchar(64) CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} DEFAULT NULL, `localPath` varchar(128) COLLATE {$COLLATE_TEXT} DEFAULT NULL, + `profileImagePHID` varbinary(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), UNIQUE KEY `callsign` (`callsign`), @@ -2769,6 +3046,42 @@ CREATE TABLE `repository_commit` ( KEY `key_author` (`authorPHID`,`epoch`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `repository_commit_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `repository_commit_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `repository_commit_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `repository_commitdata` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `commitID` int(10) unsigned NOT NULL, @@ -2963,6 +3276,42 @@ CREATE TABLE `repository_refcursor` ( KEY `key_cursor` (`repositoryPHID`,`refType`,`refNameHash`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `repository_repository_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `repository_repository_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `repository_repository_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `repository_statusmessage` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `repositoryID` int(10) unsigned NOT NULL, @@ -3128,7 +3477,7 @@ CREATE TABLE `search_documentfield` ( `stemmedCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT}, KEY `phid` (`phid`), FULLTEXT KEY `key_corpus` (`corpus`,`stemmedCorpus`) -) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `search_documentrelationship` ( `phid` varbinary(64) NOT NULL, @@ -3209,6 +3558,17 @@ CREATE TABLE `search_namedquery` ( UNIQUE KEY `key_userquery` (`userPHID`,`engineClassName`,`queryKey`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `search_namedqueryconfig` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `engineClassName` varchar(128) COLLATE {$COLLATE_TEXT} NOT NULL, + `scopePHID` varbinary(64) NOT NULL, + `properties` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_scope` (`engineClassName`,`scopePHID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `search_profilepanelconfiguration` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -3314,7 +3674,7 @@ CREATE TABLE `slowvote_poll` ( `phid` varbinary(64) NOT NULL, `authorPHID` varbinary(64) NOT NULL, `responseVisibility` int(10) unsigned NOT NULL, - `shuffle` int(10) unsigned NOT NULL, + `shuffle` tinyint(1) NOT NULL DEFAULT '0', `method` int(10) unsigned NOT NULL, `dateCreated` int(10) unsigned NOT NULL, `dateModified` int(10) unsigned NOT NULL, @@ -3627,6 +3987,42 @@ CREATE TABLE `user_transaction` ( KEY `key_object` (`objectPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `user_user_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `user_user_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `user_user_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$NAMESPACE}_worker` /*!40100 DEFAULT CHARACTER SET {$CHARSET} COLLATE {$COLLATE_TEXT} */; USE `{$NAMESPACE}_worker`; @@ -3956,7 +4352,7 @@ CREATE TABLE `ponder_question` ( KEY `authorPHID` (`authorPHID`), KEY `status` (`status`), KEY `key_space` (`spacePHID`) -) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `ponder_questiontransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -4076,6 +4472,42 @@ CREATE TABLE `pholio_mock` ( KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `pholio_mock_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `pholio_mock_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `pholio_mock_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `pholio_transaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -4136,7 +4568,7 @@ CREATE TABLE `conpherence_index` ( UNIQUE KEY `key_previous` (`previousTransactionPHID`), KEY `key_thread` (`threadPHID`), FULLTEXT KEY `key_corpus` (`corpus`) -) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `conpherence_participant` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -5249,7 +5681,7 @@ CREATE TABLE `nuance_item` ( `dateModified` int(10) unsigned NOT NULL, `queuePHID` varbinary(64) DEFAULT NULL, `itemType` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, - `itemKey` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, + `itemKey` varchar(64) COLLATE {$COLLATE_TEXT} DEFAULT NULL, `itemContainerKey` varchar(64) COLLATE {$COLLATE_TEXT} DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `key_phid` (`phid`), @@ -5267,8 +5699,12 @@ CREATE TABLE `nuance_itemcommand` ( `authorPHID` varbinary(64) NOT NULL, `command` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, `parameters` longtext COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + `queuePHID` varbinary(64) DEFAULT NULL, + `status` varchar(64) COLLATE {$COLLATE_TEXT} NOT NULL, PRIMARY KEY (`id`), - KEY `key_item` (`itemPHID`) + KEY `key_pending` (`itemPHID`,`status`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `nuance_itemtransaction` ( @@ -5476,6 +5912,42 @@ CREATE TABLE `passphrase_credential` ( KEY `key_space` (`spacePHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `passphrase_credential_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `passphrase_credential_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `passphrase_credential_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `passphrase_credentialtransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, @@ -5809,6 +6281,42 @@ CREATE TABLE `fund_initiative` ( KEY `key_owner` (`ownerPHID`) ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +CREATE TABLE `fund_initiative_fdocument` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `objectPHID` varbinary(64) NOT NULL, + `isClosed` tinyint(1) NOT NULL, + `authorPHID` varbinary(64) DEFAULT NULL, + `ownerPHID` varbinary(64) DEFAULT NULL, + `epochCreated` int(10) unsigned NOT NULL, + `epochModified` int(10) unsigned NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_object` (`objectPHID`), + KEY `key_author` (`authorPHID`), + KEY `key_owner` (`ownerPHID`), + KEY `key_created` (`epochCreated`), + KEY `key_modified` (`epochModified`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `fund_initiative_ffield` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `fieldKey` varchar(4) COLLATE {$COLLATE_TEXT} NOT NULL, + `rawCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `termCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + `normalCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT} NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `key_documentfield` (`documentID`,`fieldKey`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + +CREATE TABLE `fund_initiative_fngrams` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `documentID` int(10) unsigned NOT NULL, + `ngram` char(3) COLLATE {$COLLATE_TEXT} NOT NULL, + PRIMARY KEY (`id`), + KEY `key_ngram` (`ngram`,`documentID`), + KEY `key_object` (`documentID`) +) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; + CREATE TABLE `fund_initiativetransaction` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `phid` varbinary(64) NOT NULL, From 6cedd4a95cfc23a1679400fb64863c49f24f2306 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Sep 2017 12:24:23 -0700 Subject: [PATCH 158/865] Revert quickstart for tables with native FULLTEXT indexes to MyISAM See D18594. --- resources/sql/quickstart.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/sql/quickstart.sql b/resources/sql/quickstart.sql index e9ae4465a4..b29472d5e7 100644 --- a/resources/sql/quickstart.sql +++ b/resources/sql/quickstart.sql @@ -3477,7 +3477,7 @@ CREATE TABLE `search_documentfield` ( `stemmedCorpus` longtext CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT}, KEY `phid` (`phid`), FULLTEXT KEY `key_corpus` (`corpus`,`stemmedCorpus`) -) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `search_documentrelationship` ( `phid` varbinary(64) NOT NULL, @@ -4568,7 +4568,7 @@ CREATE TABLE `conpherence_index` ( UNIQUE KEY `key_previous` (`previousTransactionPHID`), KEY `key_thread` (`threadPHID`), FULLTEXT KEY `key_corpus` (`corpus`) -) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; +) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT}; CREATE TABLE `conpherence_participant` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, From 29f625ef6882939f277ff4f3ce7e0367313499f4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Sep 2017 14:51:34 -0700 Subject: [PATCH 159/865] Make "No Notifications" setting less broad, and fix a bug with default display behavior Summary: Fixes T12979. In D18457, we added a "No Notifications" setting to let users disable the blue and yellow pop-up notifications that alert you when an object has been updated, since some users found them distracting. However, the change made "do nothing" the default, so all other `JX.Notification` callsites -- which never pass a preference -- were effectively turned off no matter what your setting was set to. This includes the "Read-Only" mode warning (grey), the "High Security" mode warning (purple), the "timezone" warning, and a few others. Tweak things a little bit so the setting applies to ONLY blue and yellow ("object you're following was updated" / "this object was updated") notifications, not other types of popup notifications. Test Plan: - With notifications on in settings, got blue notifications and "Read-only". - With notifications off in settings, got "Read-only" but no blue notifications. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12979 Differential Revision: https://secure.phabricator.com/D18600 --- resources/celerity/map.php | 52 +++++++++---------- .../PhabricatorNotificationBuilder.php | 12 ++--- ...icatorNotificationIndividualController.php | 8 +-- .../aphlict/behavior-aphlict-listen.js | 7 ++- webroot/rsrc/js/core/Notification.js | 19 ++----- 5 files changed, 42 insertions(+), 56 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2093e3e70e..552fe22a68 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', 'core.pkg.css' => 'e9473020', - 'core.pkg.js' => '6c085267', + 'core.pkg.js' => '28552e58', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', 'differential.pkg.js' => 'b71b8c5d', @@ -374,7 +374,7 @@ 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => 'e1d4b11a', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'caade6f2', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'a14cbdfc', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '4cc4f460', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '27ca6289', 'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443', @@ -467,7 +467,7 @@ 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'c19dd9b9', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', - 'rsrc/js/core/Notification.js' => '5c3349b2', + 'rsrc/js/core/Notification.js' => '008faf9c', 'rsrc/js/core/Prefab.js' => 'c5af80a2', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', @@ -585,7 +585,7 @@ 'javelin-aphlict' => 'e1d4b11a', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => 'caade6f2', - 'javelin-behavior-aphlict-listen' => 'a14cbdfc', + 'javelin-behavior-aphlict-listen' => '4cc4f460', 'javelin-behavior-aphlict-status' => '5e2634b9', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-drag-and-drop-textarea' => '484a6e22', @@ -789,7 +789,7 @@ 'phabricator-keyboard-shortcut-manager' => 'c19dd9b9', 'phabricator-main-menu-view' => '1802a242', 'phabricator-nav-view-css' => 'faf6a6fc', - 'phabricator-notification' => '5c3349b2', + 'phabricator-notification' => '008faf9c', 'phabricator-notification-css' => '457861ec', 'phabricator-notification-menu-css' => '10685bd4', 'phabricator-object-selector-css' => '85ee8ce6', @@ -904,6 +904,13 @@ 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( + '008faf9c' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + 'phabricator-notification-css', + ), '013ffff9' => array( 'javelin-install', 'javelin-util', @@ -1236,6 +1243,20 @@ 'javelin-uri', 'phabricator-notification', ), + '4cc4f460' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'javelin-stratcom', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', + 'javelin-router', + 'javelin-util', + 'javelin-leader', + 'javelin-sound', + 'phabricator-notification', + ), '4d863052' => array( 'javelin-dom', 'javelin-util', @@ -1326,13 +1347,6 @@ 'javelin-vector', 'javelin-dom', ), - '5c3349b2' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'phabricator-notification-css', - ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1684,20 +1698,6 @@ 'javelin-util', 'phabricator-keyboard-shortcut', ), - 'a14cbdfc' => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'javelin-leader', - 'javelin-sound', - 'phabricator-notification', - ), 'a3a63478' => array( 'phui-workcard-view-css', ), diff --git a/src/applications/notification/builder/PhabricatorNotificationBuilder.php b/src/applications/notification/builder/PhabricatorNotificationBuilder.php index 5691755cc7..026f55149c 100644 --- a/src/applications/notification/builder/PhabricatorNotificationBuilder.php +++ b/src/applications/notification/builder/PhabricatorNotificationBuilder.php @@ -153,8 +153,8 @@ public function buildDict() { foreach ($stories as $story) { if ($story instanceof PhabricatorApplicationTransactionFeedStory) { $dict[] = array( - 'desktopReady' => $desktop_ready, - 'webReady' => $web_ready, + 'showAnyNotification' => $web_ready, + 'showDesktopNotification' => $desktop_ready, 'title' => $story->renderText(), 'body' => $story->renderTextBody(), 'href' => $story->getURI(), @@ -162,8 +162,8 @@ public function buildDict() { ); } else if ($story instanceof PhabricatorNotificationTestFeedStory) { $dict[] = array( - 'desktopReady' => $desktop_ready, - 'webReady' => $web_ready, + 'showAnyNotification' => $web_ready, + 'showDesktopNotification' => $desktop_ready, 'title' => pht('Test Notification'), 'body' => $story->renderText(), 'href' => null, @@ -171,8 +171,8 @@ public function buildDict() { ); } else { $dict[] = array( - 'desktopReady' => false, - 'webReady' => false, + 'showWebNotification' => false, + 'showDesktopNotification' => false, 'title' => null, 'body' => null, 'href' => null, diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php index 8ef6286225..bac429288e 100644 --- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php +++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php @@ -38,15 +38,9 @@ public function handleRequest(AphrontRequest $request) { $dict = $builder->buildDict(); $data = $dict[0]; - $response = array( + $response = $data + array( 'pertinent' => true, 'primaryObjectPHID' => $story->getPrimaryObjectPHID(), - 'desktopReady' => $data['desktopReady'], - 'webReady' => $data['webReady'], - 'href' => $data['href'], - 'icon' => $data['icon'], - 'title' => $data['title'], - 'body' => $data['body'], 'content' => hsprintf('%s', $content), 'uniqueID' => 'story/'.$story->getChronologicalKey(), ); diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js index 2886aa0372..24571b5981 100644 --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -78,12 +78,15 @@ JX.behavior('aphlict-listen', function(config) { JX.Stratcom.invoke('notification-panel-update', null, {}); var response = e.getData(); + if (!response.showAnyNotification) { + return; + } + // Show the notification itself. new JX.Notification() .setContent(JX.$H(response.content)) - .setDesktopReady(response.desktopReady) - .setWebReady(response.webReady) .setKey(response.primaryObjectPHID) + .setShowAsDesktopNotification(response.showDesktopNotification) .setTitle(response.title) .setBody(response.body) .setHref(response.href) diff --git a/webroot/rsrc/js/core/Notification.js b/webroot/rsrc/js/core/Notification.js index 9e59ec1501..13e6cc47ae 100644 --- a/webroot/rsrc/js/core/Notification.js +++ b/webroot/rsrc/js/core/Notification.js @@ -26,8 +26,7 @@ JX.install('Notification', { _visible : false, _hideTimer : null, _duration : 12000, - _desktopReady : false, - _webReady : false, + _asDesktop : false, _key : null, _title : null, _body : null, @@ -37,11 +36,6 @@ JX.install('Notification', { show : function() { var self = JX.Notification; - // This person doesn't like any real-time notification - if (!this._desktopReady && !this._webReady) { - return; - } - if (!this._visible) { this._visible = true; @@ -51,7 +45,7 @@ JX.install('Notification', { if (self.supportsDesktopNotifications() && self.desktopNotificationsEnabled() && - this._desktopReady) { + this._asDesktop) { // Note: specifying "tag" means that notifications with matching // keys will aggregate. var n = new window.Notification(this._title, { @@ -94,13 +88,8 @@ JX.install('Notification', { return this; }, - setDesktopReady : function(ready) { - this._desktopReady = ready; - return this; - }, - - setWebReady : function(ready) { - this._webReady = ready; + setShowAsDesktopNotification : function(mode) { + this._asDesktop = mode; return this; }, From 6fb3f857fb57bd902b826567d38c07eb131b008b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 12 Sep 2017 15:32:15 -0700 Subject: [PATCH 160/865] Stop the bleeding caused by attaching enormous patches to revision mail Summary: Ref T12033. This is a very narrow fix for this issue, but it should fix the major error: don't attach patches if they're bigger than the mail body limit (by default, 512KB). Specifically, the logs from an install in T12033 show a 112MB patch being attached, and that's the biggest practical problem here. I'll follow up on the tasks with more nuanced future work. Test Plan: Enabled `differential.attach-patches`, saw a patch attached to email. Set the byte limit very low, saw patches get thrown away. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12033 Differential Revision: https://secure.phabricator.com/D18598 --- .../editor/DifferentialTransactionEditor.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index e916728c86..c6270013aa 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -766,10 +766,14 @@ protected function buildMailBody( } if ($config_attach) { - $name = pht('D%s.%s.patch', $object->getID(), $diff->getID()); - $mime_type = 'text/x-patch; charset=utf-8'; - $body->addAttachment( - new PhabricatorMetaMTAAttachment($patch, $name, $mime_type)); + // See T12033, T11767, and PHI55. This is a crude fix to stop the + // major concrete problems that lackluster email size limits cause. + if (strlen($patch) < $body_limit) { + $name = pht('D%s.%s.patch', $object->getID(), $diff->getID()); + $mime_type = 'text/x-patch; charset=utf-8'; + $body->addAttachment( + new PhabricatorMetaMTAAttachment($patch, $name, $mime_type)); + } } } } From c310f08b7a5cf7ae339a2d6c91955ba08560ce93 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Sep 2017 12:14:06 -0700 Subject: [PATCH 161/865] Work around workflow blocking error with duplicate "master" refs in "Land Revision" Summary: Ref T11823. See PHI68. T11823 has a full description of this issue and a plan to fix it, but the full plan is relatively complicated. Until that can happen, provide a workaround for the biggest immediate issue, where multiple copies of a ref cursor can cause `executeOne()` to throw, since it expects a single result. In practice, these copies are always identical so we can just pick the first one. This will get cleaned up once T11823 is fixed properly. Test Plan: Forced the table into a duplicate/ambiguous state, reproduced a similar-looking error: {F5180999} Applied the patch, got the "Land" to work as expected: {F5181000} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11823 Differential Revision: https://secure.phabricator.com/D18599 --- .../DifferentialRevisionOperationController.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php index feeb6770b8..593815f15b 100644 --- a/src/applications/differential/controller/DifferentialRevisionOperationController.php +++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php @@ -137,9 +137,19 @@ private function loadDefaultRef( return null; } - return $this->newRefQuery($repository) + // NOTE: See PHI68. This is a workaround to make "Land Revision" work + // until T11823 is fixed properly. If we find multiple refs with the same + // name (normally, duplicate "master" refs), just pick the first one. + + $refs = $this->newRefQuery($repository) ->withRefNames(array($default_name)) - ->executeOne(); + ->execute(); + + if ($refs) { + return head($refs); + } + + return null; } private function getDefaultRefName( From bc995048c913755e9a7b6f77e3b1d92c57610acd Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Sep 2017 12:14:06 -0700 Subject: [PATCH 162/865] (stable) Work around workflow blocking error with duplicate "master" refs in "Land Revision" Summary: Ref T11823. See PHI68. T11823 has a full description of this issue and a plan to fix it, but the full plan is relatively complicated. Until that can happen, provide a workaround for the biggest immediate issue, where multiple copies of a ref cursor can cause `executeOne()` to throw, since it expects a single result. In practice, these copies are always identical so we can just pick the first one. This will get cleaned up once T11823 is fixed properly. Test Plan: Forced the table into a duplicate/ambiguous state, reproduced a similar-looking error: {F5180999} Applied the patch, got the "Land" to work as expected: {F5181000} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11823 Differential Revision: https://secure.phabricator.com/D18599 --- .../DifferentialRevisionOperationController.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php index feeb6770b8..593815f15b 100644 --- a/src/applications/differential/controller/DifferentialRevisionOperationController.php +++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php @@ -137,9 +137,19 @@ private function loadDefaultRef( return null; } - return $this->newRefQuery($repository) + // NOTE: See PHI68. This is a workaround to make "Land Revision" work + // until T11823 is fixed properly. If we find multiple refs with the same + // name (normally, duplicate "master" refs), just pick the first one. + + $refs = $this->newRefQuery($repository) ->withRefNames(array($default_name)) - ->executeOne(); + ->execute(); + + if ($refs) { + return head($refs); + } + + return null; } private function getDefaultRefName( From 939008e64c2661aa82d42514e043a4a3e8c4fe99 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Sep 2017 04:23:53 -0700 Subject: [PATCH 163/865] Correct an issue where Maniphest's awful legacy "reports" UI was extra broken on merges Summary: See PHI66. See that issue for context. This UI is bad broken legacy junk, but was especially broken when reporting merges. These do not currently generate a "status" transaction, so they were never counted as task closures. Pretend they're normal closures. This is still wrong, but should be much closer to the real numbers. Specifically, if you merge a closed task into another task, it will incorrectly be counted as an extra close. This could result in negative tasks, but the numbers should be much closer to reality than they are today even so. The "Facts" application (T1562) is the real pathway forward here in the longer term. Test Plan: - Moved my `maniphest_transactions` table aside with `RENAME TABLE ...`. - Created a new empty table with `CREATE TABLE ... LIKE ...`. - Reloaded reports UI, saw empty chart. - Created, closed, and reopened tasks while reloading the chart, saw accurate reporting. - Merged an open task into another task, saw bad reporting. - Applied patch, saw the right chart again. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18601 --- .../controller/ManiphestReportController.php | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 3cc420a4c6..6a5b3ad50c 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -88,23 +88,40 @@ public function renderBurn() { $data = queryfx_all( $conn, - 'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q - WHERE transactionType = %s + 'SELECT x.transactionType, x.oldValue, x.newValue, x.dateCreated + FROM %T x %Q + WHERE transactionType IN (%Ls) ORDER BY x.dateCreated ASC', $table->getTableName(), $joins, - ManiphestTaskStatusTransaction::TRANSACTIONTYPE); + array( + ManiphestTaskStatusTransaction::TRANSACTIONTYPE, + ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE, + )); $stats = array(); $day_buckets = array(); $open_tasks = array(); + $default_status = ManiphestTaskStatus::getDefaultStatus(); + $duplicate_status = ManiphestTaskStatus::getDuplicateStatus(); foreach ($data as $key => $row) { - - // NOTE: Hack to avoid json_decode(). - $oldv = trim($row['oldValue'], '"'); - $newv = trim($row['newValue'], '"'); + switch ($row['transactionType']) { + case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: + // NOTE: Hack to avoid json_decode(). + $oldv = trim($row['oldValue'], '"'); + $newv = trim($row['newValue'], '"'); + break; + case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: + // NOTE: Merging a task does not generate a "status" transaction. + // We pretend it did. Note that this is not always accurate: it is + // possble to merge a task which was previously closed, but this + // fake transaction always counts a merge as a closure. + $oldv = $default_status; + $newv = $duplicate_status; + break; + } if ($oldv == 'null') { $old_is_open = false; From c71cb944a42851de00b2129f15899f7681b0f7ed Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Thu, 14 Sep 2017 13:18:40 -0700 Subject: [PATCH 164/865] Add edit methods for Almanac services and devices Summary: See T12414. This just gets started; we still need edit endpoints for network interfaces and bindings. Test Plan: Created some devices/services from the conduit UI. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18605 --- src/__phutil_library_map__.php | 4 ++++ .../AlamancServiceEditConduitAPIMethod.php | 19 +++++++++++++++++++ .../AlmanacDeviceEditConduitAPIMethod.php | 19 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 src/applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php create mode 100644 src/applications/almanac/conduit/AlmanacDeviceEditConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 97f5b480a4..282e5aaf62 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -9,6 +9,7 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( + 'AlamancServiceEditConduitAPIMethod' => 'applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php', 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', 'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php', 'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php', @@ -36,6 +37,7 @@ 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', + 'AlmanacDeviceEditConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceEditConduitAPIMethod.php', 'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php', 'AlmanacDeviceEditEngine' => 'applications/almanac/editor/AlmanacDeviceEditEngine.php', 'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php', @@ -4930,6 +4932,7 @@ 'require_celerity_resource' => 'applications/celerity/api.php', ), 'xmap' => array( + 'AlamancServiceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacAddress' => 'Phobject', 'AlmanacBinding' => array( 'AlmanacDAO', @@ -4975,6 +4978,7 @@ 'PhabricatorExtendedPolicyInterface', ), 'AlmanacDeviceController' => 'AlmanacController', + 'AlmanacDeviceEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'AlmanacDeviceEditController' => 'AlmanacDeviceController', 'AlmanacDeviceEditEngine' => 'PhabricatorEditEngine', 'AlmanacDeviceEditor' => 'AlmanacEditor', diff --git a/src/applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php b/src/applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php new file mode 100644 index 0000000000..d56b07a21d --- /dev/null +++ b/src/applications/almanac/conduit/AlamancServiceEditConduitAPIMethod.php @@ -0,0 +1,19 @@ + Date: Fri, 15 Sep 2017 05:33:08 -0700 Subject: [PATCH 165/865] Fix an outdated HTML anchor link in Diffusion table of contents Summary: See . In D18465, I updated these but this hard-coded the anchor for some reason (???) and I missed it while `grep`-ing. Test Plan: Viewed a commit (`/rXYZaaaa`) and clicked a file link in the table of contents. Got modern `#change-...` anchor and navigation into the document. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18609 --- .../diffusion/controller/DiffusionCommitController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 554ad64e5e..59a132b17d 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -946,7 +946,7 @@ private function buildTableOfContents( foreach ($changesets as $changeset_id => $changeset) { $path = $changeset->getFilename(); - $anchor = substr(md5($path), 0, 8); + $anchor = $changeset->getAnchorName(); $history_link = $diffusion_view->linkHistory($path); $browse_link = $diffusion_view->linkBrowse( From bd923d1ce0914f587a8ef5ad1a4f171b52cc4925 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 05:46:41 -0700 Subject: [PATCH 166/865] Provide an explicit "-R" flag to "hg serve" Summary: See . The Mercurial commit is helpful in particular: We weren't vulnerable to the security issue (users can not control any part of the command) but pass the working directory explicitly to get past the new safety check. I left `setCWD()` in place (a few lines below) just because it can't hurt, and in some other contexts it sometimes matter (for example, if commit hooks execute, they might inherit the parent CWD here or in other VCSes). Test Plan: - Cloned from a Mercurial repo locally over HTTP. - Verified that SSH cloning already uses `-R` (it does, see `DiffusionMercurialServeSSHWorkflow`). - Did not actually upgrade to Mercurial 4.0/4.1.3 to completely verify this, but a user in the Discourse thread asserted that a substantially similar fix worked correctly. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18611 --- .../diffusion/controller/DiffusionServeController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 5e61ae3a7c..98942cf2e7 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -768,7 +768,10 @@ private function serveMercurialRequest( $input = strlen($input)."\n".$input."0\n"; } - $command = csprintf('%s serve --stdio', $bin); + $command = csprintf( + '%s serve -R %s --stdio', + $bin, + $repository->getLocalPath()); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); list($err, $stdout, $stderr) = id(new ExecFuture('%C', $command)) From 9d5a2b3b4f6808cd8172104e221b9217315d8313 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Sep 2017 05:08:35 -0700 Subject: [PATCH 167/865] Add a RefPosition table to hold branch/tag positions once the RefCursor table is split Summary: Ref T11823. Currently, we have a "RefCursor" table which stores rows like `` with some more data. Because Mercurial can have a single branch pointing at several different places, this table must allow multiple rows with the same branch or tag name. Among other things, this means there isn't a single PHID which can be used to identify a branch name in a stable way. However, we have several UIs where we want to be able to do this. Some specific examples where we run into trouble: in Mercurial, if there are 5 heads for "default", that means there are 5 phids. And currently, if someone deletes a branch, we lose the PHID for it. Instead, we'd rather retain it so the whole world doesn't break if you accidentally delete a branch and then fix it a little later. (I'll likely hold this until the rest of the logic is fleshed out a little more in followup changes.) Test Plan: Ran `bin/storage upgrade`, saw the table get created without warnings. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11823 Differential Revision: https://secure.phabricator.com/D18602 --- .../autopatches/20170914.ref.01.position.sql | 6 +++++ src/__phutil_library_map__.php | 2 ++ .../PhabricatorRepositoryRefPosition.php | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 resources/sql/autopatches/20170914.ref.01.position.sql create mode 100644 src/applications/repository/storage/PhabricatorRepositoryRefPosition.php diff --git a/resources/sql/autopatches/20170914.ref.01.position.sql b/resources/sql/autopatches/20170914.ref.01.position.sql new file mode 100644 index 0000000000..2d0a505d17 --- /dev/null +++ b/resources/sql/autopatches/20170914.ref.01.position.sql @@ -0,0 +1,6 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_refposition ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + cursorID INT UNSIGNED NOT NULL, + commitIdentifier VARCHAR(40) NOT NULL COLLATE {$COLLATE_TEXT}, + isClosed BOOL NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 282e5aaf62..c535d46322 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3863,6 +3863,7 @@ 'PhabricatorRepositoryRefCursorPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRefCursorPHIDType.php', 'PhabricatorRepositoryRefCursorQuery' => 'applications/repository/query/PhabricatorRepositoryRefCursorQuery.php', 'PhabricatorRepositoryRefEngine' => 'applications/repository/engine/PhabricatorRepositoryRefEngine.php', + 'PhabricatorRepositoryRefPosition' => 'applications/repository/storage/PhabricatorRepositoryRefPosition.php', 'PhabricatorRepositoryRepositoryPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php', 'PhabricatorRepositorySchemaSpec' => 'applications/repository/storage/PhabricatorRepositorySchemaSpec.php', 'PhabricatorRepositorySearchEngine' => 'applications/repository/query/PhabricatorRepositorySearchEngine.php', @@ -9434,6 +9435,7 @@ 'PhabricatorRepositoryRefCursorPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryRefCursorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryRefEngine' => 'PhabricatorRepositoryEngine', + 'PhabricatorRepositoryRefPosition' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryRepositoryPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositorySchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorRepositorySearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/repository/storage/PhabricatorRepositoryRefPosition.php b/src/applications/repository/storage/PhabricatorRepositoryRefPosition.php new file mode 100644 index 0000000000..838037b1a0 --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositoryRefPosition.php @@ -0,0 +1,26 @@ + false, + self::CONFIG_COLUMN_SCHEMA => array( + 'commitIdentifier' => 'text40', + 'isClosed' => 'bool', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_position' => array( + 'columns' => array('cursorID', 'commitIdentifier'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + +} From 782b18e7e2ed17ede8f707bd68839a3f652c4df4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 08:21:15 -0700 Subject: [PATCH 168/865] Migrate RefCursor data to RefPosition table Summary: Ref T11823. This populates the new RefPosition table based on the existing RefCursor table, and deletes now-duplicate rows in the RefCursor table so the next change can add a unique key. This change is not standalone, and there need to be separate code updates. I have a rough version of that written, but this migration needs to happen first to test it. I'll hold this whole series of changes until after the release cut and until the code is updated. Test Plan: Ran migration, spot-checked database tables. Saw redundant rows remove and correct-looking rows populated into the new RefPosition table. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11823 Differential Revision: https://secure.phabricator.com/D18612 --- .../autopatches/20170915.ref.01.migrate.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 resources/sql/autopatches/20170915.ref.01.migrate.php diff --git a/resources/sql/autopatches/20170915.ref.01.migrate.php b/resources/sql/autopatches/20170915.ref.01.migrate.php new file mode 100644 index 0000000000..21102fa4ab --- /dev/null +++ b/resources/sql/autopatches/20170915.ref.01.migrate.php @@ -0,0 +1,71 @@ +establishConnection('w'); + +$map = array(); +foreach (new LiskMigrationIterator($table) as $ref) { + $repository_phid = $ref->getRepositoryPHID(); + $ref_type = $ref->getRefType(); + $ref_hash = $ref->getRefNameHash(); + + $ref_key = "{$repository_phid}/{$ref_type}/{$ref_hash}"; + + if (!isset($map[$ref_key])) { + $map[$ref_key] = array( + 'id' => $ref->getID(), + 'type' => $ref_type, + 'hash' => $ref_hash, + 'repositoryPHID' => $repository_phid, + 'positions' => array(), + ); + } + + // NOTE: When this migration runs, the table will have "commitIdentifier" and + // "isClosed" fields. Later, it won't. Since they'll be removed, we can't + // rely on being able to access them via the object. Instead, run a separate + // raw query to read them. + + $row = queryfx_one( + $conn, + 'SELECT commitIdentifier, isClosed FROM %T WHERE id = %d', + $ref->getTableName(), + $ref->getID()); + + $map[$ref_key]['positions'][] = array( + 'identifier' => $row['commitIdentifier'], + 'isClosed' => (int)$row['isClosed'], + ); +} + +// Now, write all the position rows. +$position_table = new PhabricatorRepositoryRefPosition(); +foreach ($map as $ref_key => $spec) { + $id = $spec['id']; + foreach ($spec['positions'] as $position) { + queryfx( + $conn, + 'INSERT IGNORE INTO %T (cursorID, commitIdentifier, isClosed) + VALUES (%d, %s, %d)', + $position_table->getTableName(), + $id, + $position['identifier'], + $position['isClosed']); + } +} + +// Finally, delete all the redundant RefCursor rows (rows with the same name) +// so we can add proper unique keys in the next migration. +foreach ($map as $ref_key => $spec) { + queryfx( + $conn, + 'DELETE FROM %T WHERE refType = %s + AND refNameHash = %s + AND repositoryPHID = %s + AND id != %d', + $table->getTableName(), + $spec['type'], + $spec['hash'], + $spec['repositoryPHID'], + $spec['id']); +} From 5cf62f86d7283bde6348b1b8ef84c4737b2cfedc Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 08:34:20 -0700 Subject: [PATCH 169/865] Remove obsolete columns from RefCursor table Summary: Ref T11823. This change isn't standalone, but prepares for the more involved code change by dropping obsolete columns from the RefCursor table and adding the unique key we need to prevent the ambiguous/duplicate refs issue. This data was moved to the RefPosition table in D18612. Test Plan: Ran storage upgrade. See next revision for more substantial testing of this change series. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11823 Differential Revision: https://secure.phabricator.com/D18613 --- resources/sql/autopatches/20170915.ref.02.drop.id.sql | 2 ++ resources/sql/autopatches/20170915.ref.03.drop.closed.sql | 2 ++ resources/sql/autopatches/20170915.ref.04.uniq.sql | 2 ++ .../repository/storage/PhabricatorRepositoryRefCursor.php | 7 ++----- 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 resources/sql/autopatches/20170915.ref.02.drop.id.sql create mode 100644 resources/sql/autopatches/20170915.ref.03.drop.closed.sql create mode 100644 resources/sql/autopatches/20170915.ref.04.uniq.sql diff --git a/resources/sql/autopatches/20170915.ref.02.drop.id.sql b/resources/sql/autopatches/20170915.ref.02.drop.id.sql new file mode 100644 index 0000000000..177fde3b6a --- /dev/null +++ b/resources/sql/autopatches/20170915.ref.02.drop.id.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_refcursor + DROP COLUMN commitIdentifier; diff --git a/resources/sql/autopatches/20170915.ref.03.drop.closed.sql b/resources/sql/autopatches/20170915.ref.03.drop.closed.sql new file mode 100644 index 0000000000..927ee04b65 --- /dev/null +++ b/resources/sql/autopatches/20170915.ref.03.drop.closed.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_refcursor + DROP COLUMN isClosed; diff --git a/resources/sql/autopatches/20170915.ref.04.uniq.sql b/resources/sql/autopatches/20170915.ref.04.uniq.sql new file mode 100644 index 0000000000..0bef69fc4a --- /dev/null +++ b/resources/sql/autopatches/20170915.ref.04.uniq.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_refcursor + ADD UNIQUE KEY `key_ref` (repositoryPHID, refType, refNameHash); diff --git a/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php b/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php index febbed3ee6..ec0c90744c 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php +++ b/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php @@ -19,8 +19,6 @@ final class PhabricatorRepositoryRefCursor protected $refNameHash; protected $refNameRaw; protected $refNameEncoding; - protected $commitIdentifier; - protected $isClosed = 0; private $repository = self::ATTACHABLE; @@ -34,13 +32,12 @@ protected function getConfiguration() { self::CONFIG_COLUMN_SCHEMA => array( 'refType' => 'text32', 'refNameHash' => 'bytes12', - 'commitIdentifier' => 'text40', 'refNameEncoding' => 'text16?', - 'isClosed' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( - 'key_cursor' => array( + 'key_ref' => array( 'columns' => array('repositoryPHID', 'refType', 'refNameHash'), + 'unique' => true, ), ), ) + parent::getConfiguration(); From 8982e3e52dd81d0a6a4825b015c2dee4b5004b85 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 14 Sep 2017 10:09:36 -0700 Subject: [PATCH 170/865] Update major RefCursor callsites to work properly with RefPosition Summary: Ref T11823. This is the meaty part of the change, and updates `RefEngine` to use separate RefCursor (for names) and RefPosition (for actual commit positions) tables. I'll hold this whole series until after the release cut so it has some time to bake on `secure` to look for issues. It's also not a huge problem if there are bugs here since these tables are just caches anyway, although they do feed into some other things, and obviously it's never good to have bugs. Test Plan: - This logic can be invoked directly with `bin/repository refs --trace --verbose`. - Ran that on unchanged repositories, new branches, removed branches, and modified branches. Saw appropriate output and cursor positions. - Ran on a mercurial repository to test the close/open logic, saw it correct open/closed state of incorrect positions. - Browed around Diffusion in various repositories. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11823 Differential Revision: https://secure.phabricator.com/D18614 --- ...ifferentialRevisionOperationController.php | 14 +- .../query/DiffusionCachedResolveRefsQuery.php | 6 +- .../engine/PhabricatorRepositoryRefEngine.php | 264 +++++++++++++----- .../PhabricatorRepositoryRefCursorQuery.php | 22 ++ .../PhabricatorRepositoryRefCursor.php | 15 + 5 files changed, 234 insertions(+), 87 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionOperationController.php b/src/applications/differential/controller/DifferentialRevisionOperationController.php index 593815f15b..feeb6770b8 100644 --- a/src/applications/differential/controller/DifferentialRevisionOperationController.php +++ b/src/applications/differential/controller/DifferentialRevisionOperationController.php @@ -137,19 +137,9 @@ private function loadDefaultRef( return null; } - // NOTE: See PHI68. This is a workaround to make "Land Revision" work - // until T11823 is fixed properly. If we find multiple refs with the same - // name (normally, duplicate "master" refs), just pick the first one. - - $refs = $this->newRefQuery($repository) + return $this->newRefQuery($repository) ->withRefNames(array($default_name)) - ->execute(); - - if ($refs) { - return head($refs); - } - - return null; + ->executeOne(); } private function getDefaultRefName( diff --git a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php index 55e86ce879..22ff1d61f0 100644 --- a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php +++ b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php @@ -106,9 +106,11 @@ private function resolveGitAndMercurialRefs() { $cursors = queryfx_all( $conn_r, - 'SELECT refNameHash, refType, commitIdentifier, isClosed FROM %T - WHERE repositoryPHID = %s AND refNameHash IN (%Ls)', + 'SELECT c.refNameHash, c.refType, p.commitIdentifier, p.isClosed + FROM %T c JOIN %T p ON p.cursorID = c.id + WHERE c.repositoryPHID = %s AND c.refNameHash IN (%Ls)', id(new PhabricatorRepositoryRefCursor())->getTableName(), + id(new PhabricatorRepositoryRefPosition())->getTableName(), $repository->getPHID(), array_keys($name_hashes)); diff --git a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php index af07ea052a..4b1cd829d5 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryRefEngine.php @@ -7,17 +7,18 @@ final class PhabricatorRepositoryRefEngine extends PhabricatorRepositoryEngine { - private $newRefs = array(); - private $deadRefs = array(); + private $newPositions = array(); + private $deadPositions = array(); private $closeCommits = array(); private $hasNoCursors; public function updateRefs() { - $this->newRefs = array(); - $this->deadRefs = array(); + $this->newPositions = array(); + $this->deadPositions = array(); $this->closeCommits = array(); $repository = $this->getRepository(); + $viewer = $this->getViewer(); $branches_may_close = false; @@ -53,8 +54,9 @@ public function updateRefs() { ); $all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withRepositoryPHIDs(array($repository->getPHID())) + ->needPositions(true) ->execute(); $cursor_groups = mgroup($all_cursors, 'getRefType'); @@ -63,8 +65,15 @@ public function updateRefs() { // Find all the heads of closing refs. $all_closing_heads = array(); foreach ($all_cursors as $cursor) { - if ($this->shouldCloseRef($cursor->getRefType(), $cursor->getRefName())) { - $all_closing_heads[] = $cursor->getCommitIdentifier(); + $should_close = $this->shouldCloseRef( + $cursor->getRefType(), + $cursor->getRefName()); + if (!$should_close) { + continue; + } + + foreach ($cursor->getPositionIdentifiers() as $identifier) { + $all_closing_heads[] = $identifier; } } $all_closing_heads = array_unique($all_closing_heads); @@ -79,25 +88,13 @@ public function updateRefs() { $this->setCloseFlagOnCommits($this->closeCommits); } - if ($this->newRefs || $this->deadRefs) { + if ($this->newPositions || $this->deadPositions) { $repository->openTransaction(); - foreach ($this->newRefs as $ref) { - $ref->save(); - } - foreach ($this->deadRefs as $ref) { - // Shove this ref into the old refs table so the discovery engine - // can check if any commits have been rendered unreachable. - id(new PhabricatorRepositoryOldRef()) - ->setRepositoryPHID($repository->getPHID()) - ->setCommitIdentifier($ref->getCommitIdentifier()) - ->save(); - - $ref->delete(); - } - $repository->saveTransaction(); - $this->newRefs = array(); - $this->deadRefs = array(); + $this->saveNewPositions(); + $this->deleteDeadPositions(); + + $repository->saveTransaction(); } $branches = $maps[PhabricatorRepositoryRefCursor::TYPE_BRANCH]; @@ -111,10 +108,12 @@ private function updateBranchStates( array $branches) { assert_instances_of($branches, 'DiffusionRepositoryRef'); + $viewer = $this->getViewer(); $all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withRepositoryPHIDs(array($repository->getPHID())) + ->needPositions(true) ->execute(); $state_map = array(); @@ -124,36 +123,57 @@ private function updateBranchStates( continue; } $raw_name = $cursor->getRefNameRaw(); - $hash = $cursor->getCommitIdentifier(); - $state_map[$raw_name][$hash] = $cursor; + foreach ($cursor->getPositions() as $position) { + $hash = $position->getCommitIdentifier(); + $state_map[$raw_name][$hash] = $position; + } } + $updates = array(); foreach ($branches as $branch) { - $cursor = idx($state_map, $branch->getShortName(), array()); - $cursor = idx($cursor, $branch->getCommitIdentifier()); - if (!$cursor) { + $position = idx($state_map, $branch->getShortName(), array()); + $position = idx($position, $branch->getCommitIdentifier()); + if (!$position) { continue; } $fields = $branch->getRawFields(); - $cursor_state = (bool)$cursor->getIsClosed(); + $position_state = (bool)$position->getIsClosed(); $branch_state = (bool)idx($fields, 'closed'); - if ($cursor_state != $branch_state) { - $cursor->setIsClosed((int)$branch_state)->save(); + if ($position_state != $branch_state) { + $updates[$position->getID()] = (int)$branch_state; } } + + if ($updates) { + $position_table = id(new PhabricatorRepositoryRefPosition()); + $conn = $position_table->establishConnection('w'); + + $position_table->openTransaction(); + foreach ($updates as $position_id => $branch_state) { + queryfx( + $conn, + 'UPDATE %T SET isClosed = %d WHERE id = %d', + $position_table->getTableName(), + $branch_state, + $position_id); + } + $position_table->saveTransaction(); + } } - private function markRefNew(PhabricatorRepositoryRefCursor $cursor) { - $this->newRefs[] = $cursor; + private function markPositionNew( + PhabricatorRepositoryRefPosition $position) { + $this->newPositions[] = $position; return $this; } - private function markRefDead(PhabricatorRepositoryRefCursor $cursor) { - $this->deadRefs[] = $cursor; + private function markPositionDead( + PhabricatorRepositoryRefPosition $position) { + $this->deadPositions[] = $position; return $this; } @@ -203,10 +223,7 @@ private function updateCursors( // NOTE: Mercurial branches may have multiple branch heads; this logic // is complex primarily to account for that. - // Group all the cursors by their ref name, like "master". Since Mercurial - // branches may have multiple heads, there could be several cursors with - // the same name. - $cursor_groups = mgroup($cursors, 'getRefNameRaw'); + $cursors = mpull($cursors, null, 'getRefNameRaw'); // Group all the new ref values by their name. As above, these groups may // have multiple members in Mercurial. @@ -215,38 +232,47 @@ private function updateCursors( foreach ($ref_groups as $name => $refs) { $new_commits = mpull($refs, 'getCommitIdentifier', 'getCommitIdentifier'); - $ref_cursors = idx($cursor_groups, $name, array()); - $old_commits = mpull($ref_cursors, null, 'getCommitIdentifier'); + $ref_cursor = idx($cursors, $name); + if ($ref_cursor) { + $old_positions = $ref_cursor->getPositions(); + } else { + $old_positions = array(); + } // We're going to delete all the cursors pointing at commits which are // no longer associated with the refs. This primarily makes the Mercurial // multiple head case easier, and means that when we update a ref we // delete the old one and write a new one. - foreach ($ref_cursors as $cursor) { - if (isset($new_commits[$cursor->getCommitIdentifier()])) { + foreach ($old_positions as $old_position) { + $hash = $old_position->getCommitIdentifier(); + if (isset($new_commits[$hash])) { // This ref previously pointed at this commit, and still does. $this->log( pht( 'Ref %s "%s" still points at %s.', $ref_type, $name, - $cursor->getCommitIdentifier())); - } else { - // This ref previously pointed at this commit, but no longer does. - $this->log( - pht( - 'Ref %s "%s" no longer points at %s.', - $ref_type, - $name, - $cursor->getCommitIdentifier())); - - // Nuke the obsolete cursor. - $this->markRefDead($cursor); + $hash)); + continue; } + + // This ref previously pointed at this commit, but no longer does. + $this->log( + pht( + 'Ref %s "%s" no longer points at %s.', + $ref_type, + $name, + $hash)); + + // Nuke the obsolete cursor. + $this->markPositionDead($old_position); } // Now, we're going to insert new cursors for all the commits which are // associated with this ref that don't currently have cursors. + $old_commits = mpull($old_positions, 'getCommitIdentifier'); + $old_commits = array_fuse($old_commits); + $added_commits = array_diff_key($new_commits, $old_commits); foreach ($added_commits as $identifier) { $this->log( @@ -255,12 +281,24 @@ private function updateCursors( $ref_type, $name, $identifier)); - $this->markRefNew( - id(new PhabricatorRepositoryRefCursor()) - ->setRepositoryPHID($repository->getPHID()) - ->setRefType($ref_type) - ->setRefName($name) - ->setCommitIdentifier($identifier)); + + if (!$ref_cursor) { + // If this is the first time we've seen a particular ref (for + // example, a new branch) we need to insert a RefCursor record + // for it before we can insert a RefPosition. + + $ref_cursor = $this->newRefCursor( + $repository, + $ref_type, + $name); + } + + $new_position = id(new PhabricatorRepositoryRefPosition()) + ->setCursorID($ref_cursor->getID()) + ->setCommitIdentifier($identifier) + ->setIsClosed(0); + + $this->markPositionNew($new_position); } if ($this->shouldCloseRef($ref_type, $name)) { @@ -277,16 +315,21 @@ private function updateCursors( // Find any cursors for refs which no longer exist. This happens when a // branch, tag or bookmark is deleted. - foreach ($cursor_groups as $name => $cursor_group) { - if (idx($ref_groups, $name) === null) { - foreach ($cursor_group as $cursor) { - $this->log( - pht( - 'Ref %s "%s" no longer exists.', - $cursor->getRefType(), - $cursor->getRefName())); - $this->markRefDead($cursor); - } + foreach ($cursors as $name => $cursor) { + if (!empty($ref_groups[$name])) { + // This ref still has some positions, so we don't need to wipe it + // out. Try the next one. + continue; + } + + foreach ($cursor->getPositions() as $position) { + $this->log( + pht( + 'Ref %s "%s" no longer exists.', + $cursor->getRefType(), + $cursor->getRefName())); + + $this->markPositionDead($position); } } } @@ -452,6 +495,81 @@ private function setCloseFlagOnCommits(array $identifiers) { return $this; } + private function newRefCursor( + PhabricatorRepository $repository, + $ref_type, + $ref_name) { + + $cursor = id(new PhabricatorRepositoryRefCursor()) + ->setRepositoryPHID($repository->getPHID()) + ->setRefType($ref_type) + ->setRefName($ref_name); + + try { + return $cursor->save(); + } catch (AphrontDuplicateKeyQueryException $ex) { + // If we raced another daemon to create this position and lost the race, + // load the cursor the other daemon created instead. + } + + $viewer = $this->getViewer(); + + $cursor = id(new PhabricatorRepositoryRefCursorQuery()) + ->setViewer($viewer) + ->withRepositoryPHIDs(array($repository->getPHID())) + ->withRefTypes(array($ref_type)) + ->withRefNames(array($ref_name)) + ->needPositions(true) + ->executeOne(); + if (!$cursor) { + throw new Exception( + pht( + 'Failed to create a new ref cursor (for "%s", of type "%s", in '. + 'repository "%s") because it collided with an existing cursor, '. + 'but then failed to load that cursor.', + $ref_name, + $ref_type, + $repository->getDisplayName())); + } + + return $cursor; + } + + private function saveNewPositions() { + $positions = $this->newPositions; + + foreach ($positions as $position) { + try { + $position->save(); + } catch (AphrontDuplicateKeyQueryException $ex) { + // We may race another daemon to create this position. If we do, and + // we lose the race, that's fine: the other daemon did our work for + // us and we can continue. + } + } + + $this->newPositions = array(); + } + + private function deleteDeadPositions() { + $positions = $this->deadPositions; + $repository = $this->getRepository(); + + foreach ($positions as $position) { + // Shove this ref into the old refs table so the discovery engine + // can check if any commits have been rendered unreachable. + id(new PhabricatorRepositoryOldRef()) + ->setRepositoryPHID($repository->getPHID()) + ->setCommitIdentifier($position->getCommitIdentifier()) + ->save(); + + $position->delete(); + } + + $this->deadPositions = array(); + } + + /* -( Updating Git Refs )-------------------------------------------------- */ diff --git a/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php b/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php index 706fe801b6..b3c960e025 100644 --- a/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryRefCursorQuery.php @@ -9,6 +9,7 @@ final class PhabricatorRepositoryRefCursorQuery private $refTypes; private $refNames; private $datasourceQuery; + private $needPositions; public function withIDs(array $ids) { $this->ids = $ids; @@ -40,6 +41,11 @@ public function withDatasourceQuery($query) { return $this; } + public function needPositions($need) { + $this->needPositions = $need; + return $this; + } + public function newResultObject() { return new PhabricatorRepositoryRefCursor(); } @@ -68,6 +74,22 @@ protected function willFilterPage(array $refs) { $ref->attachRepository($repository); } + if (!$refs) { + return $refs; + } + + if ($this->needPositions) { + $positions = id(new PhabricatorRepositoryRefPosition())->loadAllWhere( + 'cursorID IN (%Ld)', + mpull($refs, 'getID')); + $positions = mgroup($positions, 'getCursorID'); + + foreach ($refs as $key => $ref) { + $ref_positions = idx($positions, $ref->getID(), array()); + $ref->attachPositions($ref_positions); + } + } + return $refs; } diff --git a/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php b/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php index ec0c90744c..87f737d86e 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php +++ b/src/applications/repository/storage/PhabricatorRepositoryRefCursor.php @@ -21,6 +21,7 @@ final class PhabricatorRepositoryRefCursor protected $refNameEncoding; private $repository = self::ATTACHABLE; + private $positions = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -71,6 +72,20 @@ public function getRepository() { return $this->assertAttached($this->repository); } + public function attachPositions(array $positions) { + assert_instances_of($positions, 'PhabricatorRepositoryRefPosition'); + $this->positions = $positions; + return $this; + } + + public function getPositions() { + return $this->assertAttached($this->positions); + } + + public function getPositionIdentifiers() { + return mpull($this->getPositions(), 'getCommitIdentifier'); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ From 49b718178066d9f75981039f1a13f4c16f52e116 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 09:22:26 -0700 Subject: [PATCH 171/865] Update utility "bin/repository parents" workflow to work with RefPosition Summary: Ref T11823. I think this is the last callsite which relies on the old data format: `bin/repository parents` rebuilds a cache which we don't currently use very heavily. Update it to work with the new data. Test Plan: Ran `bin/repository parents --trace`, saw successful script execution and reasonable-looking output. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11823 Differential Revision: https://secure.phabricator.com/D18615 --- ...torRepositoryManagementParentsWorkflow.php | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php index 61c5fedf96..671287c3d6 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php @@ -54,6 +54,7 @@ private function rebuildRepository(PhabricatorRepository $repo) { ->setViewer($this->getViewer()) ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH)) ->withRepositoryPHIDs(array($repo->getPHID())) + ->needPositions(true) ->execute(); $graph = array(); @@ -66,23 +67,23 @@ private function rebuildRepository(PhabricatorRepository $repo) { "%s\n", pht('Rebuilding branch "%s"...', $ref->getRefName())); - $commit = $ref->getCommitIdentifier(); - - if ($repo->isGit()) { - $stream = new PhabricatorGitGraphStream($repo, $commit); - } else { - $stream = new PhabricatorMercurialGraphStream($repo, $commit); - } - - $discover = array($commit); - while ($discover) { - $target = array_pop($discover); - if (isset($graph[$target])) { - continue; + foreach ($ref->getPositionIdentifiers() as $commit) { + if ($repo->isGit()) { + $stream = new PhabricatorGitGraphStream($repo, $commit); + } else { + $stream = new PhabricatorMercurialGraphStream($repo, $commit); } - $graph[$target] = $stream->getParents($target); - foreach ($graph[$target] as $parent) { - $discover[] = $parent; + + $discover = array($commit); + while ($discover) { + $target = array_pop($discover); + if (isset($graph[$target])) { + continue; + } + $graph[$target] = $stream->getParents($target); + foreach ($graph[$target] as $parent) { + $discover[] = $parent; + } } } } From b352cacdd9179bede1117fa7b15b4475f8867fc6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 12:16:58 -0700 Subject: [PATCH 172/865] Swap "-R" and "serve" argument order for Mercurial Summary: See . I missed that `-R` is order-sensitive. Test Plan: Verified both orders work on 3.5.2. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18616 --- .../diffusion/controller/DiffusionServeController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 98942cf2e7..475f688a4c 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -769,7 +769,7 @@ private function serveMercurialRequest( } $command = csprintf( - '%s serve -R %s --stdio', + '%s -R %s serve --stdio', $bin, $repository->getLocalPath()); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); From 76cdaa4f2d108b3b8104a0d09008a6869f084ece Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 12:16:58 -0700 Subject: [PATCH 173/865] (stable) Swap "-R" and "serve" argument order for Mercurial Summary: See . I missed that `-R` is order-sensitive. Test Plan: Verified both orders work on 3.5.2. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18616 --- .../diffusion/controller/DiffusionServeController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 98942cf2e7..475f688a4c 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -769,7 +769,7 @@ private function serveMercurialRequest( } $command = csprintf( - '%s serve -R %s --stdio', + '%s -R %s serve --stdio', $bin, $repository->getLocalPath()); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); From 51b810b0eb74797743154a11ced42b2c4637397c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 17:46:15 -0700 Subject: [PATCH 174/865] Fix "Author's projects" Herald rules for revisions and diffs Summary: See PHI71. These didn't get properly updated when we wrote Subprojects and Milestones, and should use materialized members, not raw members. Swap the query so projects you are an indirect member of (e.g., milestones you are a member of the parent for, and parent projects you are a member of a subproject of) are included in the result list. Also fix a bad typeahead datasource. Test Plan: - Ran a dry run with the test console, saw project PHIDs for milestones and parent projects in the raw field value. - Tried to set "Author's projects" to a user, no longer could. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18619 --- .../DifferentialDiffAuthorProjectsHeraldField.php | 13 +++++++++---- ...ifferentialRevisionAuthorProjectsHeraldField.php | 11 ++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php b/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php index bec1e2a66c..e484a66ce6 100644 --- a/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php +++ b/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php @@ -10,9 +10,14 @@ public function getHeraldFieldName() { } public function getHeraldFieldValue($object) { - return PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getAuthorPHID(), - PhabricatorProjectMemberOfProjectEdgeType::EDGECONST); + $viewer = PhabricatorUser::getOmnipotentUser(); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withMemberPHIDs(array($object->getAuthorPHID())) + ->execute(); + + return mpull($projects, 'getPHID'); } protected function getHeraldFieldStandardType() { @@ -20,7 +25,7 @@ protected function getHeraldFieldStandardType() { } protected function getDatasource() { - return new PhabricatorProjectOrUserDatasource(); + return new PhabricatorProjectDatasource(); } } diff --git a/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php b/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php index cb916fcf3d..bd91e810b2 100644 --- a/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php +++ b/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php @@ -10,9 +10,14 @@ public function getHeraldFieldName() { } public function getHeraldFieldValue($object) { - return PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getAuthorPHID(), - PhabricatorProjectMemberOfProjectEdgeType::EDGECONST); + $viewer = PhabricatorUser::getOmnipotentUser(); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withMemberPHIDs(array($object->getAuthorPHID())) + ->execute(); + + return mpull($projects, 'getPHID'); } protected function getHeraldFieldStandardType() { From 3f3ce88c7b5d52142d0e239f2fa83cf14a47d5cc Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Sep 2017 17:46:15 -0700 Subject: [PATCH 175/865] (stable) Fix "Author's projects" Herald rules for revisions and diffs Summary: See PHI71. These didn't get properly updated when we wrote Subprojects and Milestones, and should use materialized members, not raw members. Swap the query so projects you are an indirect member of (e.g., milestones you are a member of the parent for, and parent projects you are a member of a subproject of) are included in the result list. Also fix a bad typeahead datasource. Test Plan: - Ran a dry run with the test console, saw project PHIDs for milestones and parent projects in the raw field value. - Tried to set "Author's projects" to a user, no longer could. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18619 --- .../DifferentialDiffAuthorProjectsHeraldField.php | 13 +++++++++---- ...ifferentialRevisionAuthorProjectsHeraldField.php | 11 ++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php b/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php index bec1e2a66c..e484a66ce6 100644 --- a/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php +++ b/src/applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php @@ -10,9 +10,14 @@ public function getHeraldFieldName() { } public function getHeraldFieldValue($object) { - return PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getAuthorPHID(), - PhabricatorProjectMemberOfProjectEdgeType::EDGECONST); + $viewer = PhabricatorUser::getOmnipotentUser(); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withMemberPHIDs(array($object->getAuthorPHID())) + ->execute(); + + return mpull($projects, 'getPHID'); } protected function getHeraldFieldStandardType() { @@ -20,7 +25,7 @@ protected function getHeraldFieldStandardType() { } protected function getDatasource() { - return new PhabricatorProjectOrUserDatasource(); + return new PhabricatorProjectDatasource(); } } diff --git a/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php b/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php index cb916fcf3d..bd91e810b2 100644 --- a/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php +++ b/src/applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php @@ -10,9 +10,14 @@ public function getHeraldFieldName() { } public function getHeraldFieldValue($object) { - return PhabricatorEdgeQuery::loadDestinationPHIDs( - $object->getAuthorPHID(), - PhabricatorProjectMemberOfProjectEdgeType::EDGECONST); + $viewer = PhabricatorUser::getOmnipotentUser(); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withMemberPHIDs(array($object->getAuthorPHID())) + ->execute(); + + return mpull($projects, 'getPHID'); } protected function getHeraldFieldStandardType() { From 94ab0c9afb916ba3df3d2929167d60880a2764d6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Sep 2017 09:33:35 -0700 Subject: [PATCH 176/865] Spell "Relevance" correctly Summary: Despite how I (and everyone else?) pronounce it, it is spelled with an "a". See PHI38. Test Plan: Googled both spellings. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18622 --- .../query/policy/PhabricatorCursorPagedPolicyAwareQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index cd4ccf30b5..09cb3e499c 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -809,7 +809,7 @@ public function getBuiltinOrders() { if ($this->supportsFerretEngine()) { $orders['relevance'] = array( 'vector' => array('rank', 'fulltext-modified', 'id'), - 'name' => pht('Relevence'), + 'name' => pht('Relevance'), ); } From 3ad727ba787cb9de70243ecee6285a9f60bc2a84 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Sep 2017 11:47:08 -0700 Subject: [PATCH 177/865] Guarantee the `key_position` key is created properly Summary: Ref T12987. I was focused on the RefCursor table and overlooked that we need some care on this key. It's currently possible to run `bin/storage upgrade --no-adjust`, then start Phabricator, and end up with duplicate records in this table. If you try to run `bin/storage adjust` later, it will try to add the unique key but fail. This is unusual for normal installs (they usually do not use `--no-adjust`) but we do it in the cluster and I did this exact thing on `secure`. Normally, to avoid this, when a new table with a unique key is introduced, we also add a migration to explicitly add that key. This is mostly harmless in this case. Fix this mistake (force the table to contain only unique rows; add the key) and try using `LOCK TABLES` to make this atomic. If this doesn't cause problems we can use this in similar situations in the future. The "alter table may unlock things" warning comes from here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables.html It seems like it's fine to issue `UNLOCK TABLES` even if you don't have any locks, so I think this script should always do the right thing now, regardless of ALTER TABLE unlocking or not unlocking tables. Test Plan: Ran `bin/storage upgrade -f`, saw table end up in the right state. I'll also check this on `secure`, where the starting state is a little messier. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12987 Differential Revision: https://secure.phabricator.com/D18623 --- .../autopatches/20170918.ref.01.position.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 resources/sql/autopatches/20170918.ref.01.position.php diff --git a/resources/sql/autopatches/20170918.ref.01.position.php b/resources/sql/autopatches/20170918.ref.01.position.php new file mode 100644 index 0000000000..f95eb8d406 --- /dev/null +++ b/resources/sql/autopatches/20170918.ref.01.position.php @@ -0,0 +1,52 @@ +establishConnection('w'); +$key_name = 'key_position'; + +try { + queryfx( + $conn, + 'ALTER TABLE %T DROP KEY %T', + $table->getTableName(), + $key_name); +} catch (AphrontQueryException $ex) { + // This key may or may not exist, depending on exactly when the install + // ran previous migrations and adjustments. We're just dropping it if it + // does exist. + + // We're doing this first (outside of the lock) because the MySQL + // documentation says "if you ALTER TABLE a locked table, it may become + // unlocked". +} + +queryfx( + $conn, + 'LOCK TABLES %T WRITE', + $table->getTableName()); + +$seen = array(); +foreach (new LiskMigrationIterator($table) as $position) { + $cursor_id = $position->getCursorID(); + $hash = $position->getCommitIdentifier(); + + // If this is the first copy of this row we've seen, mark it as seen and + // move on. + if (empty($seen[$cursor_id][$hash])) { + $seen[$cursor_id][$hash] = true; + continue; + } + + // Otherwise, get rid of this row as it duplicates a row we saw previously. + $position->delete(); +} + +queryfx( + $conn, + 'ALTER TABLE %T ADD UNIQUE KEY %T (cursorID, commitIdentifier)', + $table->getTableName(), + $key_name); + +queryfx( + $conn, + 'UNLOCK TABLES'); From 156adccef0ad18104389bd9cdfa71cc0ac7876b2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Sep 2017 12:13:11 -0700 Subject: [PATCH 178/865] Fix an issue where "bin/differential migrate-hunk" could decompress data Summary: Fixes T12986. I caught this bug in the changes from D18584: when we moved a large hunk to file storage, we would decompress it but keep the "deflated" flag. This could cause confusion when loading it later. I missed this in testing since I wasn't exhaustive enough in checking hunks and didn't run into a compressed one. Instead of compressing on `save()`, compress during the normal workflow. We currently never advise users to run this workflow so I didn't bother trying to clean up possible existing migrations. Test Plan: - Ran `bin/differential migrate-hunk` on compressed hunks, moving them to and from file storage. Saw them work correctly and remain compressed. - Created new small (uncompressed) and large (compressed) hunks, verified they work properly and get compressed (if applicable). - Used `bin/cache purge --caches changeset` to clear changeset caches and make sure the actual table was being hit. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12986 Differential Revision: https://secure.phabricator.com/D18624 --- .../storage/DifferentialModernHunk.php | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/applications/differential/storage/DifferentialModernHunk.php b/src/applications/differential/storage/DifferentialModernHunk.php index 6f63f680a1..cde6f29329 100644 --- a/src/applications/differential/storage/DifferentialModernHunk.php +++ b/src/applications/differential/storage/DifferentialModernHunk.php @@ -51,8 +51,11 @@ public function setChanges($text) { $this->dataEncoding = $this->detectEncodingForStorage($text); $this->dataType = self::DATATYPE_TEXT; - $this->dataFormat = self::DATAFORMAT_RAW; - $this->data = $text; + + list($format, $data) = $this->formatDataForStorage($text); + + $this->dataFormat = $format; + $this->data = $data; return $this; } @@ -68,24 +71,13 @@ public function forceEncoding($encoding) { return $this; } - public function save() { - - $type = $this->getDataType(); - $format = $this->getDataFormat(); - - // Before saving the data, attempt to compress it. - if ($type == self::DATATYPE_TEXT) { - if ($format == self::DATAFORMAT_RAW) { - $data = $this->getData(); - $deflated = PhabricatorCaches::maybeDeflateData($data); - if ($deflated !== null) { - $this->data = $deflated; - $this->dataFormat = self::DATAFORMAT_DEFLATED; - } - } + private function formatDataForStorage($data) { + $deflated = PhabricatorCaches::maybeDeflateData($data); + if ($deflated !== null) { + return array(self::DATAFORMAT_DEFLATED, $deflated); } - return parent::save(); + return array(self::DATAFORMAT_RAW, $data); } public function saveAsText() { @@ -99,7 +91,10 @@ public function saveAsText() { $raw_data = $this->getRawData(); $this->setDataType(self::DATATYPE_TEXT); - $this->setData($raw_data); + + list($format, $data) = $this->formatDataForStorage($raw_data); + $this->setDataFormat($format); + $this->setData($data); $result = $this->save(); @@ -118,8 +113,11 @@ public function saveAsFile() { $raw_data = $this->getRawData(); + list($format, $data) = $this->formatDataForStorage($raw_data); + $this->setDataFormat($format); + $file = PhabricatorFile::newFromFileData( - $raw_data, + $data, array( 'name' => 'differential-hunk', 'mime-type' => 'application/octet-stream', From 23867c14879805c190f5351ec8053c9472a33edf Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Sep 2017 12:54:14 -0700 Subject: [PATCH 179/865] Add a "Draft" state for revisions, and action bucket support Summary: Ref T2543. There's no way to put revisions into this state yet, but start adding support for when there is. Adds the status constant, plus support for bucketing them. Test Plan: - Manually put a revision in "Draft" state by updating the database directly. - Verified my drafts showed up in a "Drafts" section on the bucket view. - Verified others' drafts did not appear on the action bucket view. - Viewed revisions, queried for "Draft" revisions, etc (stuff we get for free). {F5186781} {F5186782} {F5186783} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18625 --- .../constants/DifferentialRevisionStatus.php | 15 +++++++ ...tialRevisionRequiredActionResultBucket.php | 40 +++++++++++++++++++ .../storage/DifferentialRevision.php | 4 ++ 3 files changed, 59 insertions(+) diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index b4b291f9c4..70f0e33b28 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -8,6 +8,7 @@ final class DifferentialRevisionStatus extends Phobject { const ACCEPTED = 'accepted'; const PUBLISHED = 'published'; const ABANDONED = 'abandoned'; + const DRAFT = 'draft'; private $key; private $spec = array(); @@ -76,6 +77,10 @@ public function isChangePlanned() { return ($this->key === self::CHANGES_PLANNED); } + public function isDraft() { + return ($this->key === self::DRAFT); + } + public static function newForStatus($status) { $result = new self(); @@ -163,6 +168,16 @@ private static function getMap() { 'color.tag' => 'indigo', 'color.ansi' => null, ), + self::DRAFT => array( + 'name' => pht('Draft'), + // For legacy clients, treat this as though it is "Needs Review". + 'legacy' => 0, + 'icon' => 'fa-file-text-o', + 'closed' => false, + 'color.icon' => 'grey', + 'color.tag' => 'grey', + 'color.ansi' => null, + ), ); } diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index 195a430b1c..f3971f8571 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -37,6 +37,9 @@ protected function buildResultGroups( // other project or package reviewers which they have authority over. $this->filterResigned($phids); + // We also throw away draft revisions which you aren't the author of. + $this->filterOtherDrafts($phids); + $groups = array(); $groups[] = $this->newGroup() @@ -61,6 +64,11 @@ protected function buildResultGroups( ->setNoDataString(pht('No revisions are waiting for updates.')) ->setObjects($this->filterShouldUpdate($phids)); + $groups[] = $this->newGroup() + ->setName(pht('Drafts')) + ->setNoDataString(pht('You have no draft revisions.')) + ->setObjects($this->filterDrafts($phids)); + $groups[] = $this->newGroup() ->setName(pht('Waiting on Review')) ->setNoDataString(pht('None of your revisions are waiting on review.')) @@ -247,4 +255,36 @@ private function filterResigned(array $phids) { return $results; } + private function filterOtherDrafts(array $phids) { + $objects = $this->getRevisionsNotAuthored($this->objects, $phids); + + $results = array(); + foreach ($objects as $key => $object) { + if (!$object->isDraft()) { + continue; + } + + $results[$key] = $object; + unset($this->objects[$key]); + } + + return $results; + } + + private function filterDrafts(array $phids) { + $objects = $this->getRevisionsAuthored($this->objects, $phids); + + $results = array(); + foreach ($objects as $key => $object) { + if (!$object->isDraft()) { + continue; + } + + $results[$key] = $object; + unset($this->objects[$key]); + } + + return $results; + } + } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 3615c6e78b..03602b49d9 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -653,6 +653,10 @@ public function isPublished() { return $this->getStatusObject()->isPublished(); } + public function isDraft() { + return $this->getStatusObject()->isDraft(); + } + public function getStatusIcon() { return $this->getStatusObject()->getIcon(); } From 03e5d69817c91afde03615a475086044a7db7a33 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 19 Sep 2017 09:22:10 -0700 Subject: [PATCH 180/865] Fix an error in Diffusion when the Owners application is uninstalled Summary: See . When Owners is not installed, Diffusion can fatal with a bad `$view`. Test Plan: - Uninstall Owners. - View the content of any file in Diffusion. - Before: fatal on `$view` undefined. - After: Valid page with no owners information. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18629 --- .../controller/DiffusionBrowseController.php | 120 +++++++++--------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 076e667dc4..d3223e64fd 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -799,75 +799,79 @@ private function buildButtonBar( } private function buildOwnersList(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - $repository = $drequest->getRepository(); - $owners = 'PhabricatorOwnersApplication'; - if (PhabricatorApplication::isClassInstalled($owners)) { - $package_query = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) - ->withControl( - $repository->getPHID(), - array( - $drequest->getPath(), - )); + $have_owners = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorOwnersApplication', + $viewer); + if (!$have_owners) { + return null; + } - $package_query->execute(); + $repository = $drequest->getRepository(); - $packages = $package_query->getControllingPackagesForPath( + $package_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl( $repository->getPHID(), - $drequest->getPath()); - - $ownership = id(new PHUIObjectItemListView()) - ->setUser($viewer) - ->setNoDataString(pht('No Owners')); - - if ($packages) { - foreach ($packages as $package) { - $item = id(new PHUIObjectItemView()) - ->setObject($package) - ->setObjectName($package->getMonogram()) - ->setHeader($package->getName()) - ->setHref($package->getURI()); - - $owners = $package->getOwners(); - if ($owners) { - $owner_list = $viewer->renderHandleList( - mpull($owners, 'getUserPHID')); - } else { - $owner_list = phutil_tag('em', array(), pht('None')); - } - $item->addAttribute(pht('Owners: %s', $owner_list)); - - $auto = $package->getAutoReview(); - $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); - $spec = idx($autoreview_map, $auto, array()); - $name = idx($spec, 'name', $auto); - $item->addIcon('fa-code', $name); - - if ($package->getAuditingEnabled()) { - $item->addIcon('fa-check', pht('Auditing Enabled')); - } else { - $item->addIcon('fa-ban', pht('No Auditing')); - } + array( + $drequest->getPath(), + )); - if ($package->isArchived()) { - $item->setDisabled(true); - } + $package_query->execute(); + + $packages = $package_query->getControllingPackagesForPath( + $repository->getPHID(), + $drequest->getPath()); - $ownership->addItem($item); + $ownership = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setNoDataString(pht('No Owners')); + + if ($packages) { + foreach ($packages as $package) { + $item = id(new PHUIObjectItemView()) + ->setObject($package) + ->setObjectName($package->getMonogram()) + ->setHeader($package->getName()) + ->setHref($package->getURI()); + + $owners = $package->getOwners(); + if ($owners) { + $owner_list = $viewer->renderHandleList( + mpull($owners, 'getUserPHID')); + } else { + $owner_list = phutil_tag('em', array(), pht('None')); } - } + $item->addAttribute(pht('Owners: %s', $owner_list)); - $view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Owner Packages')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addClass('diffusion-mobile-view') - ->setObjectList($ownership); + $auto = $package->getAutoReview(); + $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); + $spec = idx($autoreview_map, $auto, array()); + $name = idx($spec, 'name', $auto); + $item->addIcon('fa-code', $name); + + if ($package->getAuditingEnabled()) { + $item->addIcon('fa-check', pht('Auditing Enabled')); + } else { + $item->addIcon('fa-ban', pht('No Auditing')); + } + + if ($package->isArchived()) { + $item->setDisabled(true); + } + + $ownership->addItem($item); + } } + $view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Owner Packages')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') + ->setObjectList($ownership); + return $view; } From 52fff638ca9bb0f7c48791475930a51ae61a1016 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 19 Sep 2017 09:22:10 -0700 Subject: [PATCH 181/865] (stable) Fix an error in Diffusion when the Owners application is uninstalled Summary: See . When Owners is not installed, Diffusion can fatal with a bad `$view`. Test Plan: - Uninstall Owners. - View the content of any file in Diffusion. - Before: fatal on `$view` undefined. - After: Valid page with no owners information. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18629 --- .../controller/DiffusionBrowseController.php | 120 +++++++++--------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 076e667dc4..d3223e64fd 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -799,75 +799,79 @@ private function buildButtonBar( } private function buildOwnersList(DiffusionRequest $drequest) { - $viewer = $this->getViewer(); - $repository = $drequest->getRepository(); - $owners = 'PhabricatorOwnersApplication'; - if (PhabricatorApplication::isClassInstalled($owners)) { - $package_query = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) - ->withControl( - $repository->getPHID(), - array( - $drequest->getPath(), - )); + $have_owners = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorOwnersApplication', + $viewer); + if (!$have_owners) { + return null; + } - $package_query->execute(); + $repository = $drequest->getRepository(); - $packages = $package_query->getControllingPackagesForPath( + $package_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl( $repository->getPHID(), - $drequest->getPath()); - - $ownership = id(new PHUIObjectItemListView()) - ->setUser($viewer) - ->setNoDataString(pht('No Owners')); - - if ($packages) { - foreach ($packages as $package) { - $item = id(new PHUIObjectItemView()) - ->setObject($package) - ->setObjectName($package->getMonogram()) - ->setHeader($package->getName()) - ->setHref($package->getURI()); - - $owners = $package->getOwners(); - if ($owners) { - $owner_list = $viewer->renderHandleList( - mpull($owners, 'getUserPHID')); - } else { - $owner_list = phutil_tag('em', array(), pht('None')); - } - $item->addAttribute(pht('Owners: %s', $owner_list)); - - $auto = $package->getAutoReview(); - $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); - $spec = idx($autoreview_map, $auto, array()); - $name = idx($spec, 'name', $auto); - $item->addIcon('fa-code', $name); - - if ($package->getAuditingEnabled()) { - $item->addIcon('fa-check', pht('Auditing Enabled')); - } else { - $item->addIcon('fa-ban', pht('No Auditing')); - } + array( + $drequest->getPath(), + )); - if ($package->isArchived()) { - $item->setDisabled(true); - } + $package_query->execute(); + + $packages = $package_query->getControllingPackagesForPath( + $repository->getPHID(), + $drequest->getPath()); - $ownership->addItem($item); + $ownership = id(new PHUIObjectItemListView()) + ->setUser($viewer) + ->setNoDataString(pht('No Owners')); + + if ($packages) { + foreach ($packages as $package) { + $item = id(new PHUIObjectItemView()) + ->setObject($package) + ->setObjectName($package->getMonogram()) + ->setHeader($package->getName()) + ->setHref($package->getURI()); + + $owners = $package->getOwners(); + if ($owners) { + $owner_list = $viewer->renderHandleList( + mpull($owners, 'getUserPHID')); + } else { + $owner_list = phutil_tag('em', array(), pht('None')); } - } + $item->addAttribute(pht('Owners: %s', $owner_list)); - $view = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Owner Packages')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->addClass('diffusion-mobile-view') - ->setObjectList($ownership); + $auto = $package->getAutoReview(); + $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); + $spec = idx($autoreview_map, $auto, array()); + $name = idx($spec, 'name', $auto); + $item->addIcon('fa-code', $name); + + if ($package->getAuditingEnabled()) { + $item->addIcon('fa-check', pht('Auditing Enabled')); + } else { + $item->addIcon('fa-ban', pht('No Auditing')); + } + + if ($package->isArchived()) { + $item->setDisabled(true); + } + + $ownership->addItem($item); + } } + $view = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Owner Packages')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('diffusion-mobile-view') + ->setObjectList($ownership); + return $view; } From 3fbad684c1423c724bc64a6299361f268e26ef2a Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 20 Sep 2017 10:31:01 -0700 Subject: [PATCH 182/865] More completely explain why we're refusing to send reset mail to an unverified address Summary: See PHI78. The user was getting this message and (reasonably) interpreted it to mean "reset mail can never be sent to unverified addresses". Reword it to be more clear, albeit an entire paragraph long. I don't really have a good solution in these cases where we'd need a whole page to explain what's happening (this, plus "we can't tell you which address you should use because an attacker could get information if we did" and "this rule defuses the risk that an opportunistic attacker may try to compromise your account after you add an email you don't own by mistake"). We could write it up separately and link to it, but I feel like that stuff tends to get out of date. Just land somewhere in the middle. Test Plan: {F5189105} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18630 --- .../auth/controller/PhabricatorEmailLoginController.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorEmailLoginController.php b/src/applications/auth/controller/PhabricatorEmailLoginController.php index 92accc7494..f57a29b11a 100644 --- a/src/applications/auth/controller/PhabricatorEmailLoginController.php +++ b/src/applications/auth/controller/PhabricatorEmailLoginController.php @@ -71,8 +71,11 @@ public function handleRequest(AphrontRequest $request) { $target_email->getUserPHID()); if ($verified_addresses) { $errors[] = pht( - 'That email address is not verified. You can only send '. - 'password reset links to a verified address.'); + 'That email address is not verified, but the account it is '. + 'connected to has at least one other verified address. When an '. + 'account has at least one verified address, you can only send '. + 'password reset links to one of the verified addresses. Try '. + 'a verified address instead.'); $e_email = pht('Unverified'); } } From 5112dac491a870ac4f87fb3869be5bbe40308999 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 20 Sep 2017 14:34:06 -0700 Subject: [PATCH 183/865] Update an old SSH redirect URI when editing a bot's SSH keys Summary: See PHI79. When you edit another user's SSH keys (normally, for a bot account) we currently redirect you to an older URI. Test Plan: - Viewed a bot's profile page. - Clicked "Edit Settings" on the Manage page. - Went to "SSH Keys". - Uploaded an SSH key. - Before: redirected to a 404 after finishing the workflow. - After: redirected to the same page after the workflow. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18633 --- src/applications/people/storage/PhabricatorUser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index a3ec42213a..166feaa237 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -1390,7 +1390,7 @@ public function getSSHPublicKeyManagementURI(PhabricatorUser $viewer) { return '/settings/panel/ssh/'; } else { // Otherwise, take them to the administrative panel for this user. - return '/settings/'.$this->getID().'/panel/ssh/'; + return '/settings/user/'.$this->getUsername().'/page/ssh/'; } } From f089f41e50c626267a70ad21f001ab5ce03560d3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 20 Sep 2017 14:34:06 -0700 Subject: [PATCH 184/865] (stable) Update an old SSH redirect URI when editing a bot's SSH keys Summary: See PHI79. When you edit another user's SSH keys (normally, for a bot account) we currently redirect you to an older URI. Test Plan: - Viewed a bot's profile page. - Clicked "Edit Settings" on the Manage page. - Went to "SSH Keys". - Uploaded an SSH key. - Before: redirected to a 404 after finishing the workflow. - After: redirected to the same page after the workflow. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18633 --- src/applications/people/storage/PhabricatorUser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index a3ec42213a..166feaa237 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -1390,7 +1390,7 @@ public function getSSHPublicKeyManagementURI(PhabricatorUser $viewer) { return '/settings/panel/ssh/'; } else { // Otherwise, take them to the administrative panel for this user. - return '/settings/'.$this->getID().'/panel/ssh/'; + return '/settings/user/'.$this->getUsername().'/page/ssh/'; } } From c7af663523e556ca08a6b6df30e6fd0932f8dd46 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Sep 2017 14:14:52 -0700 Subject: [PATCH 185/865] Align most revision actions to the new "Draft" state Summary: Ref T2543. Most actions are not available for drafts. Authors can "Request Review" (move out of draft to become a normal revision) or "Abandon". Non-authors can't do anything (maybe we'll let them do something later -- like "Commandeer"? -- if there's a good reason). Test Plan: Viewed a draft revision as an author and non-author, saw fewer actions available. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18626 --- .../xaction/DifferentialRevisionAbandonTransaction.php | 3 ++- .../xaction/DifferentialRevisionAcceptTransaction.php | 3 ++- .../xaction/DifferentialRevisionActionTransaction.php | 5 +++-- .../xaction/DifferentialRevisionCloseTransaction.php | 3 ++- .../DifferentialRevisionCommandeerTransaction.php | 8 +++++++- .../DifferentialRevisionPlanChangesTransaction.php | 8 +++++++- .../xaction/DifferentialRevisionReclaimTransaction.php | 3 ++- .../xaction/DifferentialRevisionRejectTransaction.php | 8 +++++++- .../xaction/DifferentialRevisionReopenTransaction.php | 3 ++- .../DifferentialRevisionRequestReviewTransaction.php | 9 +++++++-- .../xaction/DifferentialRevisionResignTransaction.php | 8 +++++++- 11 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php index b7b0e7f123..6232594aae 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht('Abandon Revision'); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('This revision will be abandoned and closed.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index f01ce4b487..d52e2dbeb7 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht("Accept Revision \xE2\x9C\x94"); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('These changes will be approved.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index ba769c2be5..a1e66379f7 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -52,7 +52,8 @@ protected function getRevisionActionGroupKey() { return DifferentialRevisionEditEngine::ACTIONGROUP_REVISION; } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return null; } @@ -103,7 +104,7 @@ public function newEditField( if ($label !== null) { $field->setCommentActionLabel($label); - $description = $this->getRevisionActionDescription(); + $description = $this->getRevisionActionDescription($revision); $field->setActionDescription($description); $group_key = $this->getRevisionActionGroupKey(); diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index d71b30950a..b1c796dca4 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht('Close Revision'); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('This revision will be closed.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php index 34cedcbb73..4169c3e4dc 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht('Commandeer Revision'); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('You will take control of this revision and become its author.'); } @@ -65,6 +66,11 @@ protected function validateAction($object, PhabricatorUser $viewer) { 'been closed. You can only commandeer open revisions.')); } + if ($object->isDraft()) { + throw new Exception( + pht('You can not commandeer a draft revision.')); + } + if ($this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index 6e584ceb36..231adc5bf8 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht('Plan Changes'); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht( 'This revision will be removed from review queues until it is revised.'); } @@ -55,6 +56,11 @@ public function applyInternalEffects($object, $value) { } protected function validateAction($object, PhabricatorUser $viewer) { + if ($object->isDraft()) { + throw new Exception( + pht('You can not plan changes to a draft revision.')); + } + if ($object->isChangePlanned()) { throw new Exception( pht( diff --git a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php index 4767924445..4a4744e2e0 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht('Reclaim Revision'); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('This revision will be reclaimed and reopened.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php index b4c71209aa..a15ca0641b 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht("Request Changes \xE2\x9C\x98"); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('This revision will be returned to the author for updates.'); } @@ -72,6 +73,11 @@ protected function validateAction($object, PhabricatorUser $viewer) { 'not own.')); } + if ($object->isDraft()) { + throw new Exception( + pht('You can not request changes to a draft revision.')); + } + if ($this->isViewerFullyRejected($object, $viewer)) { throw new Exception( pht( diff --git a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php index 1d28433429..4fc9f3a6f1 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht('Reopen Revision'); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('This revision will be reopened for review.'); } diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index d102b0c0c2..465b5234d5 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -10,8 +10,13 @@ protected function getRevisionActionLabel() { return pht('Request Review'); } - protected function getRevisionActionDescription() { - return pht('This revision will be returned to reviewers for feedback.'); + protected function getRevisionActionDescription( + DifferentialRevision $revision) { + if ($revision->isDraft()) { + return pht('This revision will be submitted to reviewers for feedback.'); + } else { + return pht('This revision will be returned to reviewers for feedback.'); + } } public function getColor() { diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index c7805ad35f..40a512202d 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -10,7 +10,8 @@ protected function getRevisionActionLabel() { return pht('Resign as Reviewer'); } - protected function getRevisionActionDescription() { + protected function getRevisionActionDescription( + DifferentialRevision $revision) { return pht('You will resign as a reviewer for this change.'); } @@ -63,6 +64,11 @@ protected function validateAction($object, PhabricatorUser $viewer) { 'been closed. You can only resign from open revisions.')); } + if ($object->isDraft()) { + throw new Exception( + pht('You can not resign from a draft revision.')); + } + $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; if ($this->getViewerReviewerStatus($object, $viewer) == $resigned) { throw new Exception( From fca553f142a23448343747180fc1b59dba2954fe Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Sep 2017 14:40:36 -0700 Subject: [PATCH 186/865] Prepare revision mail for the "Draft" status Summary: Ref T2543. Currently, we always do some special things when a revision is created, mostly adding more stuff to the mail. With drafts, we want to suppress initial mail and send this big, rich mail only when the revision actually moves out of "draft". Prepare the code for this, with the actual methods hard-coded to the current behavior. This will probably take some tweaking but I think I got most of it. Test Plan: Banged around in Differential so it sent some mail, saw normal mail without anything new. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18627 --- ...ifferentialChangesSinceLastUpdateField.php | 2 +- .../customfield/DifferentialSummaryField.php | 2 +- .../customfield/DifferentialTestPlanField.php | 2 +- .../editor/DifferentialTransactionEditor.php | 69 ++++++++++++++----- .../storage/DifferentialRevision.php | 8 +++ 5 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php b/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php index a7e3654d0d..6fd24fc0d7 100644 --- a/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php +++ b/src/applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php @@ -24,7 +24,7 @@ public function updateTransactionMailBody( PhabricatorApplicationTransactionEditor $editor, array $xactions) { - if ($editor->getIsNewObject()) { + if ($editor->isFirstBroadcast()) { return; } diff --git a/src/applications/differential/customfield/DifferentialSummaryField.php b/src/applications/differential/customfield/DifferentialSummaryField.php index a11bebca6e..eff2de6f12 100644 --- a/src/applications/differential/customfield/DifferentialSummaryField.php +++ b/src/applications/differential/customfield/DifferentialSummaryField.php @@ -67,7 +67,7 @@ public function updateTransactionMailBody( PhabricatorApplicationTransactionEditor $editor, array $xactions) { - if (!$editor->getIsNewObject()) { + if (!$editor->isFirstBroadcast()) { return; } diff --git a/src/applications/differential/customfield/DifferentialTestPlanField.php b/src/applications/differential/customfield/DifferentialTestPlanField.php index c774e81a08..f2fa9cd17f 100644 --- a/src/applications/differential/customfield/DifferentialTestPlanField.php +++ b/src/applications/differential/customfield/DifferentialTestPlanField.php @@ -71,7 +71,7 @@ public function updateTransactionMailBody( PhabricatorApplicationTransactionEditor $editor, array $xactions) { - if (!$editor->getIsNewObject()) { + if (!$editor->isFirstBroadcast()) { return; } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index c6270013aa..989b9c5547 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -26,6 +26,10 @@ public function getCreateObjectTitleForFeed($author, $object) { return pht('%s created %s.', $author, $object); } + public function isFirstBroadcast() { + return $this->getIsNewObject(); + } + public function getDiffUpdateTransaction(array $xactions) { $type_update = DifferentialTransaction::TYPE_UPDATE; @@ -600,24 +604,25 @@ protected function sortTransactions(array $xactions) { return array_values(array_merge($head, $tail)); } - protected function requireCapabilities( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) {} - - return parent::requireCapabilities($object, $xaction); - } - protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { + + if (!$object->shouldBroadcast()) { + return false; + } + return true; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { + + if (!$object->shouldBroadcast()) { + return false; + } + return true; } @@ -633,14 +638,25 @@ protected function getMailTo(PhabricatorLiskDAO $object) { protected function getMailAction( PhabricatorLiskDAO $object, array $xactions) { - $action = parent::getMailAction($object, $xactions); - $strongest = $this->getStrongestAction($object, $xactions); - switch ($strongest->getTransactionType()) { - case DifferentialTransaction::TYPE_UPDATE: - $count = new PhutilNumber($object->getLineCount()); - $action = pht('%s, %s line(s)', $action, $count); - break; + $show_lines = false; + if ($this->isFirstBroadcast()) { + $action = pht('Request'); + + $show_lines = true; + } else { + $action = parent::getMailAction($object, $xactions); + + $strongest = $this->getStrongestAction($object, $xactions); + $type_update = DifferentialTransaction::TYPE_UPDATE; + if ($strongest->getTransactionType() == $type_update) { + $show_lines = true; + } + } + + if ($show_lines) { + $count = new PhutilNumber($object->getLineCount()); + $action = pht('%s, %s line(s)', $action, $count); } return $action; @@ -679,6 +695,16 @@ protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { + $viewer = $this->requireActor(); + + // If this is the first time we're sending mail about this revision, we + // generate mail for all prior transactions, not just whatever is being + // applied now. This gets the "added reviewers" lines and other relevant + // information into the mail. + if ($this->isFirstBroadcast()) { + $xactions = $this->loadUnbroadcastTransactions($object); + } + $body = new PhabricatorMetaMTAMailBody(); $body->setViewer($this->requireActor()); @@ -1491,4 +1517,15 @@ private function markReviewerComments($object, array $xactions) { $acting_phid); } + private function loadUnbroadcastTransactions($object) { + $viewer = $this->requireActor(); + + $xactions = id(new DifferentialTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->execute(); + + return array_reverse($xactions); + } + } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 03602b49d9..7bcd178a41 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -694,6 +694,14 @@ public function attachHasDraft(PhabricatorUser $viewer, $has_draft) { return $this; } + public function shouldBroadcast() { + if (!$this->isDraft()) { + return true; + } + + return false; + } + /* -( HarbormasterBuildableInterface )------------------------------------- */ From 36df39761ea562c270dab13af50e44428e93bee3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Sep 2017 15:28:18 -0700 Subject: [PATCH 187/865] Create revisions into "Draft", publish them when builds finish Summary: Ref T2543. This doesn't stand alone since mail still goes out normally, but gets this piece working: new revisions start as "Draft", then after updates if there are no builds they go into "Needs Review". This should work in general because builds update revisions when they complete, to publish a "Harbormaster finished build yada yada" transaction. So either we'll un-draft immediately, or un-draft after the last build finishes. I'll hold this until the mail and some other stuff (like UI hints) are in slightly better shape since I think it's probably too rough on its own. Test Plan: Created revisions locally, saw them un-draft after builds. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18628 --- .../config/editor/PhabricatorConfigEditor.php | 4 +- .../editor/DifferentialTransactionEditor.php | 98 +++++++++++++++++++ ...habricatorApplicationTransactionEditor.php | 6 +- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/applications/config/editor/PhabricatorConfigEditor.php b/src/applications/config/editor/PhabricatorConfigEditor.php index dc1b7b8c56..f776c3ec0c 100644 --- a/src/applications/config/editor/PhabricatorConfigEditor.php +++ b/src/applications/config/editor/PhabricatorConfigEditor.php @@ -107,9 +107,11 @@ protected function transactionHasEffect( return parent::transactionHasEffect($object, $xaction); } - protected function didApplyTransactions(array $xactions) { + protected function didApplyTransactions($object, array $xactions) { // Force all the setup checks to run on the next page load. PhabricatorSetupCheck::deleteSetupCheckCache(); + + return $xactions; } public static function storeNewValue( diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 989b9c5547..253d49c1ec 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1528,4 +1528,102 @@ private function loadUnbroadcastTransactions($object) { return array_reverse($xactions); } + + protected function didApplyTransactions($object, array $xactions) { + // If a draft revision has no outstanding builds and we're automatically + // making drafts public after builds finish, make the revision public. + $auto_undraft = true; + + if ($object->isDraft() && $auto_undraft) { + $active_builds = $this->hasActiveBuilds($object); + if (!$active_builds) { + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType( + DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE) + ->setOldValue(false) + ->setNewValue(true); + + $xaction = $this->populateTransaction($object, $xaction); + + // If we're creating this revision and immediately moving it out of + // the draft state, mark this as a create transaction so it gets + // hidden in the timeline and mail, since it isn't interesting: it + // is as though the draft phase never happened. + if ($this->getIsNewObject()) { + $xaction->setIsCreateTransaction(true); + } + + $object->openTransaction(); + $object + ->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW) + ->save(); + + $xaction->save(); + $object->saveTransaction(); + + $xactions[] = $xaction; + } + } + + return $xactions; + } + + private function hasActiveBuilds($object) { + $viewer = $this->requireActor(); + $diff = $object->getActiveDiff(); + + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withContainerPHIDs(array($object->getPHID())) + ->withBuildablePHIDs(array($diff->getPHID())) + ->withManualBuildables(false) + ->execute(); + if (!$buildables) { + return false; + } + + $builds = id(new HarbormasterBuildQuery()) + ->setViewer($viewer) + ->withBuildablePHIDs(mpull($buildables, 'getPHID')) + ->withBuildStatuses( + array( + HarbormasterBuildStatus::STATUS_INACTIVE, + HarbormasterBuildStatus::STATUS_PENDING, + HarbormasterBuildStatus::STATUS_BUILDING, + HarbormasterBuildStatus::STATUS_FAILED, + HarbormasterBuildStatus::STATUS_ABORTED, + HarbormasterBuildStatus::STATUS_ERROR, + HarbormasterBuildStatus::STATUS_PAUSED, + HarbormasterBuildStatus::STATUS_DEADLOCKED, + )) + ->needBuildTargets(true) + ->execute(); + if (!$builds) { + return false; + } + + $active = array(); + foreach ($builds as $key => $build) { + foreach ($build->getBuildTargets() as $target) { + if ($target->isAutotarget()) { + // Ignore autotargets when looking for active of failed builds. If + // local tests fail and you continue anyway, you don't need to + // double-confirm them. + continue; + } + + // This build has at least one real target that's doing something. + $active[$key] = $build; + break; + } + } + + if (!$active) { + return false; + } + + return true; + } + + } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 21e082b4ed..07edf5b0c8 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1105,7 +1105,7 @@ final public function applyTransactions( $this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs(); } - $this->didApplyTransactions($xactions); + $xactions = $this->didApplyTransactions($object, $xactions); if ($object instanceof PhabricatorCustomFieldInterface) { // Maybe this makes more sense to move into the search index itself? For @@ -1234,9 +1234,9 @@ public function publishTransactions( return $xactions; } - protected function didApplyTransactions(array $xactions) { + protected function didApplyTransactions($object, array $xactions) { // Hook for subclasses. - return; + return $xactions; } From 1ac52c09e757e4f6ffc51a3dc24108b32970346e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 22 Sep 2017 08:10:12 -0700 Subject: [PATCH 188/865] Improve search highlighting for CJK and substring queries Summary: Fixes T12995. Currently, the result highlighter (which shows //where// terms matched) only works in "term" mode, not in "substring" mode. Provide better feedback and behvaior: - When a term is a substring term, color it a little differently and add a tooltip. (This is partly to make it easier to debug/diagnose things, probably not enormously valuable to users.) - When a term is a substring term, highlight it anywhere in the results. Test Plan: Queried for latin and CJK terms. Here is CJK being highlighted: {F5192195} Here is substring vs non-substring implicit behavior: {F5192196} Here's ONLY terms being highlighted: {F5192198} Here's terms and substrings, since the query now has a substring: {F5192201} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12995 Differential Revision: https://secure.phabricator.com/D18635 --- .../search/query/PhabricatorFulltextToken.php | 4 + ...abricatorSearchApplicationSearchEngine.php | 2 +- .../view/PhabricatorSearchResultView.php | 150 +++++++++++------- 3 files changed, 101 insertions(+), 55 deletions(-) diff --git a/src/applications/search/query/PhabricatorFulltextToken.php b/src/applications/search/query/PhabricatorFulltextToken.php index 4edeb098a9..8dc2cee3ca 100644 --- a/src/applications/search/query/PhabricatorFulltextToken.php +++ b/src/applications/search/query/PhabricatorFulltextToken.php @@ -56,6 +56,10 @@ public function newTag() { $shade = PHUITagView::COLOR_RED; $icon = 'fa-minus'; break; + case PhutilSearchQueryCompiler::OPERATOR_SUBSTRING: + $tip = pht('Substring Search'); + $shade = PHUITagView::COLOR_VIOLET; + break; default: $shade = PHUITagView::COLOR_BLUE; break; diff --git a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php index d944d61964..3fcf0a8f7a 100644 --- a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php +++ b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php @@ -261,7 +261,7 @@ protected function renderResultList( foreach ($results as $phid => $handle) { $view = id(new PhabricatorSearchResultView()) ->setHandle($handle) - ->setQuery($query) + ->setTokens($fulltext_tokens) ->setObject(idx($objects, $phid)) ->render(); $list->addItem($view); diff --git a/src/applications/search/view/PhabricatorSearchResultView.php b/src/applications/search/view/PhabricatorSearchResultView.php index 63219b3252..8819f41869 100644 --- a/src/applications/search/view/PhabricatorSearchResultView.php +++ b/src/applications/search/view/PhabricatorSearchResultView.php @@ -3,16 +3,17 @@ final class PhabricatorSearchResultView extends AphrontView { private $handle; - private $query; private $object; + private $tokens; public function setHandle(PhabricatorObjectHandle $handle) { $this->handle = $handle; return $this; } - public function setQuery(PhabricatorSavedQuery $query) { - $this->query = $query; + public function setTokens(array $tokens) { + assert_instances_of($tokens, 'PhabricatorFulltextToken'); + $this->tokens = $tokens; return $this; } @@ -56,88 +57,129 @@ public function render() { * matched their query. */ private function emboldenQuery($str) { - $query = $this->query->getParameter('query'); + $tokens = $this->tokens; - if (!strlen($query) || !strlen($str)) { + if (!$tokens) { return $str; } - // This algorithm is safe but not especially fast, so don't bother if - // we're dealing with a lot of data. This mostly prevents silly/malicious - // queries from doing anything bad. - if (strlen($query) + strlen($str) > 2048) { + if (count($tokens) > 16) { return $str; } - // Keep track of which characters we're going to make bold. This is - // byte oriented, but we'll make sure we don't put a bold in the middle - // of a character later. - $bold = array_fill(0, strlen($str), false); + if (!strlen($str)) { + return $str; + } - // Split the query into words. - $parts = preg_split('/ +/', $query); + if (strlen($str) > 2048) { + return $str; + } - // Find all occurrences of each word, and mark them to be emboldened. - foreach ($parts as $part) { - $part = trim($part); - $part = trim($part, '"+'); - if (!strlen($part)) { - continue; + $patterns = array(); + foreach ($tokens as $token) { + $raw_token = $token->getToken(); + $operator = $raw_token->getOperator(); + + $value = $raw_token->getValue(); + + switch ($operator) { + case PhutilSearchQueryCompiler::OPERATOR_SUBSTRING: + $patterns[] = '(('.preg_quote($value).'))ui'; + break; + case PhutilSearchQueryCompiler::OPERATOR_AND: + $patterns[] = '((?<=\W|^)('.preg_quote($value).')(?=\W|\z))ui'; + break; + default: + // Don't highlight anything else, particularly "NOT". + break; } + } + // Find all matches for all query terms in the document title, then reduce + // them to a map from offsets to highlighted sequence lengths. If two terms + // match at the same position, we choose the longer one. + $all_matches = array(); + foreach ($patterns as $pattern) { $matches = null; - $has_matches = preg_match_all( - '/(?:^|\b)('.preg_quote($part, '/').')/i', + $ok = preg_match_all( + $pattern, $str, $matches, PREG_OFFSET_CAPTURE); - - if (!$has_matches) { + if (!$ok) { continue; } - // Flag the matching part of the range for boldening. foreach ($matches[1] as $match) { - $offset = $match[1]; - for ($ii = 0; $ii < strlen($match[0]); $ii++) { - $bold[$offset + $ii] = true; + $match_text = $match[0]; + $match_offset = $match[1]; + + if (!isset($all_matches[$match_offset])) { + $all_matches[$match_offset] = 0; } + + $all_matches[$match_offset] = max( + $all_matches[$match_offset], + strlen($match_text)); } } - // Split the string into ranges, applying bold styling as required. - $out = array(); - $buf = ''; - $pos = 0; - $is_bold = false; - - // Make sure this is UTF8 because phutil_utf8v() will explode if it isn't. - $str = phutil_utf8ize($str); - foreach (phutil_utf8v($str) as $chr) { - if ($bold[$pos] != $is_bold) { - if (strlen($buf)) { - if ($is_bold) { - $out[] = phutil_tag('strong', array(), $buf); - } else { - $out[] = $buf; - } - $buf = ''; + // Go through the string one display glyph at a time. If a glyph starts + // on a highlighted byte position, turn on highlighting for the nubmer + // of matching bytes. If a query searches for "e" and the document contains + // an "e" followed by a bunch of combining marks, this will correctly + // highlight the entire glyph. + $parts = array(); + $highlight = 0; + $offset = 0; + foreach (phutil_utf8v_combined($str) as $character) { + $length = strlen($character); + + if (isset($all_matches[$offset])) { + $highlight = $all_matches[$offset]; + } + + if ($highlight > 0) { + $is_highlighted = true; + $highlight -= $length; + } else { + $is_highlighted = false; + } + + $parts[] = array( + 'text' => $character, + 'highlighted' => $is_highlighted, + ); + + $offset += $length; + } + + // Combine all the sequences together so we aren't emitting a tag around + // every individual character. + $last = null; + foreach ($parts as $key => $part) { + if ($last !== null) { + if ($part['highlighted'] == $parts[$last]['highlighted']) { + $parts[$last]['text'] .= $part['text']; + unset($parts[$key]); + continue; } - $is_bold = !$is_bold; } - $buf .= $chr; - $pos += strlen($chr); + + $last = $key; } - if (strlen($buf)) { - if ($is_bold) { - $out[] = phutil_tag('strong', array(), $buf); + // Finally, add tags. + $result = array(); + foreach ($parts as $part) { + if ($part['highlighted']) { + $result[] = phutil_tag('strong', array(), $part['text']); } else { - $out[] = $buf; + $result[] = $part['text']; } } - return $out; + return $result; } } From 9f11f310f87209e50c48a852312cd442d0c373b4 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Mon, 25 Sep 2017 19:56:22 +0000 Subject: [PATCH 189/865] Make PHUITwoColumnView a little more printable Summary: Hide navbar, and make curtain behave like on a phone, when printing. Test Plan: {F5197340} Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18583 --- resources/celerity/map.php | 18 ++++++------ webroot/rsrc/css/core/core.css | 5 ++++ webroot/rsrc/css/phui/phui-action-list.css | 5 ++++ webroot/rsrc/css/phui/phui-curtain-view.css | 28 +++++++++++++++++++ .../rsrc/css/phui/phui-two-column-view.css | 28 +++++++++++++++++++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 552fe22a68..6c7d2db90c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => 'e9473020', + 'core.pkg.css' => '87a9a59b', 'core.pkg.js' => '28552e58', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -114,7 +114,7 @@ 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '1760853c', + 'rsrc/css/core/core.css' => '62fa3ace', 'rsrc/css/core/remarkup.css' => 'cad18339', 'rsrc/css/core/syntax.css' => 'cae95e89', 'rsrc/css/core/z-index.css' => '9d8f7c4b', @@ -137,7 +137,7 @@ 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', - 'rsrc/css/phui/phui-action-list.css' => 'e7eba156', + 'rsrc/css/phui/phui-action-list.css' => 'f7f61a34', 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', 'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3', @@ -148,7 +148,7 @@ 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', 'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad', 'rsrc/css/phui/phui-crumbs-view.css' => '6ece3bbb', - 'rsrc/css/phui/phui-curtain-view.css' => 'ca363f15', + 'rsrc/css/phui/phui-curtain-view.css' => '2bdaf026', 'rsrc/css/phui/phui-document-pro.css' => '8af7ea27', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => 'c32e8dec', @@ -177,7 +177,7 @@ 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', 'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca', - 'rsrc/css/phui/phui-two-column-view.css' => '1ade9c5f', + 'rsrc/css/phui/phui-two-column-view.css' => '44ec4951', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', 'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92', @@ -765,11 +765,11 @@ 'path-typeahead' => 'f7fc67ec', 'people-picture-menu-item-css' => 'a06f7f34', 'people-profile-css' => '4df76faf', - 'phabricator-action-list-view-css' => 'e7eba156', + 'phabricator-action-list-view-css' => 'f7f61a34', 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '1760853c', + 'phabricator-core-css' => '62fa3ace', 'phabricator-countdown-css' => '16c52f5c', 'phabricator-darklog' => 'c8e1ffe3', 'phabricator-darkmessage' => 'c48cccdd', @@ -833,7 +833,7 @@ 'phui-comment-form-css' => 'ac68149f', 'phui-comment-panel-css' => 'f50152ad', 'phui-crumbs-view-css' => '6ece3bbb', - 'phui-curtain-view-css' => 'ca363f15', + 'phui-curtain-view-css' => '2bdaf026', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => 'c32e8dec', 'phui-document-view-pro-css' => '8af7ea27', @@ -872,7 +872,7 @@ 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', 'phui-timeline-view-css' => 'f21db7ca', - 'phui-two-column-view-css' => '1ade9c5f', + 'phui-two-column-view-css' => '44ec4951', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', 'phui-workcard-view-css' => 'cca5fa92', diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index 9d471477ed..cc14d7ab5a 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -61,6 +61,11 @@ body { overflow-y: scroll; } +body.printable, +!print body { + background: none; +} + textarea { font: inherit; } diff --git a/webroot/rsrc/css/phui/phui-action-list.css b/webroot/rsrc/css/phui/phui-action-list.css index a7ad662d33..5e32a1ea0a 100644 --- a/webroot/rsrc/css/phui/phui-action-list.css +++ b/webroot/rsrc/css/phui/phui-action-list.css @@ -7,6 +7,11 @@ display: none; } +!print .phabricator-action-list-view { + padding: 4px 0; + display: none; +} + .device .phuix-dropdown-menu .phabricator-action-list-view { /* When an action list view appears inside a dropdown menu, don't hide it by default. */ diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index 8bd8a331c4..3facf0fe37 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -21,6 +21,26 @@ border-top: 1px solid {$greybackground}; } +!print .phui-curtain-panel + .phui-curtain-panel { + padding: 8px 0; + border-top: none; +} + +!print .device-desktop .phabricator-action-list-view + .phui-curtain-panel { + padding: 8px 0; + border-top: none; +} + +!print .device-desktop .phui-curtain-panel + .phui-curtain-panel { + padding: 8px 0; + border-top: none; +} + +!print .phabricator-action-list-view + .phui-curtain-panel { + padding: 8px 0; + border-top: none; +} + .phui-curtain-panel-header { padding: 0 0 4px; color: {$bluetext}; @@ -37,6 +57,10 @@ padding: 0; } +!print .phui-curtain-panel-body { + padding: 0; +} + /* Project tags */ .phui-curtain-panel-body .phabricator-handle-tag-list-item { @@ -50,3 +74,7 @@ .device .curtain-no-panels { display: none; } + +!print .curtain-no-panels { + display: none; +} diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 2fb0eccee2..f9f86d950a 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -79,6 +79,30 @@ width: 300px; } +!print .phui-two-column-view .phui-main-column { + float: unset; + width: unset; + margin-bottom: 20px; +} + +!print .phui-two-column-view .phui-side-column { + float: unset; + width: unset; + margin-bottom: 20px; +} + +!print .device-desktop .phui-two-column-view .phui-main-column { + float: unset; + width: unset; + margin-bottom: 20px; +} + +!print .device-desktop .phui-two-column-view .phui-side-column { + float: unset; + width: unset; + margin-bottom: 20px; +} + .device-desktop .phui-two-column-view.phui-side-column-left .phui-main-column { float: right; width: calc(100% - 280px); @@ -307,3 +331,7 @@ .phui-mobile-menu { display: block; } + +!print .phabricator-side-menu { + display: none !important; +} From a1d9a2389db4a8c54d90560695993f4e38398593 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 25 Sep 2017 19:11:35 -0700 Subject: [PATCH 190/865] Improve Ferret engine indexing performance for large blocks of text Summary: See PHI87. Ref T12974. Currently, we do a lot more work here than we need to: we call `phutil_utf8_strtolower()` on each token, but can do it once at the beginning on the whole block. Additionally, since ngrams don't care about order, we only need to convert unique tokens into ngrams. This saves us some `phutil_utf8v()`. These calls can be slow for large inputs. Test Plan: - Created a ~4MB task description. - Ran `bin/search index Txxx --profile ...` to profile indexing performance before and after the change. - Saw total runtime drop form 38s to 9s. - Before: - After: Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18647 --- .../search/ferret/PhabricatorFerretEngine.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index 3c8098c54f..7d1d03a8b1 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -88,16 +88,23 @@ public function getSubstringNgramsFromString($string) { } private function getNgramsFromString($value, $as_term) { + $value = phutil_utf8_strtolower($value); $tokens = $this->tokenizeString($value); - $ngrams = array(); + // First, extract unique tokens from the string. This reduces the number + // of `phutil_utf8v()` calls we need to make if we are indexing a large + // corpus with redundant terms. + $unique_tokens = array(); foreach ($tokens as $token) { - $token = phutil_utf8_strtolower($token); - if ($as_term) { $token = ' '.$token.' '; } + $unique_tokens[$token] = true; + } + + $ngrams = array(); + foreach ($unique_tokens as $token => $ignored) { $token_v = phutil_utf8v($token); $len = (count($token_v) - 2); for ($ii = 0; $ii < $len; $ii++) { From 9288cad0edc83bb319a2565a597699a6faa0c714 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 25 Sep 2017 19:11:35 -0700 Subject: [PATCH 191/865] (stable) Improve Ferret engine indexing performance for large blocks of text Summary: See PHI87. Ref T12974. Currently, we do a lot more work here than we need to: we call `phutil_utf8_strtolower()` on each token, but can do it once at the beginning on the whole block. Additionally, since ngrams don't care about order, we only need to convert unique tokens into ngrams. This saves us some `phutil_utf8v()`. These calls can be slow for large inputs. Test Plan: - Created a ~4MB task description. - Ran `bin/search index Txxx --profile ...` to profile indexing performance before and after the change. - Saw total runtime drop form 38s to 9s. - Before: - After: Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18647 --- .../search/ferret/PhabricatorFerretEngine.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index 3c8098c54f..7d1d03a8b1 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -88,16 +88,23 @@ public function getSubstringNgramsFromString($string) { } private function getNgramsFromString($value, $as_term) { + $value = phutil_utf8_strtolower($value); $tokens = $this->tokenizeString($value); - $ngrams = array(); + // First, extract unique tokens from the string. This reduces the number + // of `phutil_utf8v()` calls we need to make if we are indexing a large + // corpus with redundant terms. + $unique_tokens = array(); foreach ($tokens as $token) { - $token = phutil_utf8_strtolower($token); - if ($as_term) { $token = ' '.$token.' '; } + $unique_tokens[$token] = true; + } + + $ngrams = array(); + foreach ($unique_tokens as $token => $ignored) { $token_v = phutil_utf8v($token); $len = (count($token_v) - 2); for ($ii = 0; $ii < $len; $ii++) { From 086a125ad5ee5d3de3d5b6bc718975387a2b211b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 Sep 2017 09:16:42 -0700 Subject: [PATCH 192/865] Improve performance of Ferret engine ngram extraction, particularly for large input strings Summary: See PHI87. Ref T12974. The `array_slice()` method of splitting the string apart can perform poorly for large input strings. I think this is mostly just the large number of calls plus building and returning an array being not entirely trivial. We can just use `substr()` instead, as long as we're a little bit careful about keeping track of where we're slicing the string if it has UTF8 characters. Test Plan: - Created a task with a single, unbroken blob of base64 encoded data as the description, roughly 100KB long. - Saw indexing performance improve from ~6s to ~1.5s after patch. - Before: https://secure.phabricator.com/xhprof/profile/PHID-FILE-nrxs4lwdvupbve5lhl6u/ - After: https://secure.phabricator.com/xhprof/profile/PHID-FILE-6vs2akgjj5nbqt7yo7ul/ Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18649 --- .../search/ferret/PhabricatorFerretEngine.php | 22 +++++++++++--- .../PhabricatorFerretEngineTestCase.php | 30 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index 7d1d03a8b1..7cd06ae549 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -106,11 +106,25 @@ private function getNgramsFromString($value, $as_term) { $ngrams = array(); foreach ($unique_tokens as $token => $ignored) { $token_v = phutil_utf8v($token); - $len = (count($token_v) - 2); - for ($ii = 0; $ii < $len; $ii++) { - $ngram = array_slice($token_v, $ii, 3); - $ngram = implode('', $ngram); + $length = count($token_v); + + // NOTE: We're being somewhat clever here to micro-optimize performance, + // especially for very long strings. See PHI87. + + $token_l = array(); + for ($ii = 0; $ii < $length; $ii++) { + $token_l[$ii] = strlen($token_v[$ii]); + } + + $ngram_count = $length - 2; + $cursor = 0; + for ($ii = 0; $ii < $ngram_count; $ii++) { + $ngram_l = $token_l[$ii] + $token_l[$ii + 1] + $token_l[$ii + 2]; + + $ngram = substr($token, $cursor, $ngram_l); $ngrams[$ngram] = $ngram; + + $cursor += $token_l[$ii]; } } diff --git a/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php b/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php index a8690f1d8b..ea65a559ff 100644 --- a/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php +++ b/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php @@ -24,4 +24,34 @@ public function testTermsCorpus() { } } + public function testTermNgramExtraction() { + $snowman = "\xE2\x98\x83"; + + $map = array( + 'a' => array(' a '), + 'ab' => array(' ab', 'ab '), + 'abcdef' => array(' ab', 'abc', 'bcd', 'cde', 'def', 'ef '), + "{$snowman}" => array(" {$snowman} "), + "x{$snowman}y" => array( + " x{$snowman}", + "x{$snowman}y", + "{$snowman}y ", + ), + "{$snowman}{$snowman}" => array( + " {$snowman}{$snowman}", + "{$snowman}{$snowman} ", + ), + ); + + $engine = new ManiphestTaskFerretEngine(); + + foreach ($map as $input => $expect) { + $actual = $engine->getTermNgramsFromString($input); + $this->assertEqual( + $actual, + $expect, + pht('Term ngrams for: %s.', $input)); + } + } + } From c034306320b55eca68dcdd94a1065b7e34db5689 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 Sep 2017 09:16:42 -0700 Subject: [PATCH 193/865] (stable) Improve performance of Ferret engine ngram extraction, particularly for large input strings Summary: See PHI87. Ref T12974. The `array_slice()` method of splitting the string apart can perform poorly for large input strings. I think this is mostly just the large number of calls plus building and returning an array being not entirely trivial. We can just use `substr()` instead, as long as we're a little bit careful about keeping track of where we're slicing the string if it has UTF8 characters. Test Plan: - Created a task with a single, unbroken blob of base64 encoded data as the description, roughly 100KB long. - Saw indexing performance improve from ~6s to ~1.5s after patch. - Before: https://secure.phabricator.com/xhprof/profile/PHID-FILE-nrxs4lwdvupbve5lhl6u/ - After: https://secure.phabricator.com/xhprof/profile/PHID-FILE-6vs2akgjj5nbqt7yo7ul/ Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18649 --- .../search/ferret/PhabricatorFerretEngine.php | 22 +++++++++++--- .../PhabricatorFerretEngineTestCase.php | 30 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index 7d1d03a8b1..7cd06ae549 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -106,11 +106,25 @@ private function getNgramsFromString($value, $as_term) { $ngrams = array(); foreach ($unique_tokens as $token => $ignored) { $token_v = phutil_utf8v($token); - $len = (count($token_v) - 2); - for ($ii = 0; $ii < $len; $ii++) { - $ngram = array_slice($token_v, $ii, 3); - $ngram = implode('', $ngram); + $length = count($token_v); + + // NOTE: We're being somewhat clever here to micro-optimize performance, + // especially for very long strings. See PHI87. + + $token_l = array(); + for ($ii = 0; $ii < $length; $ii++) { + $token_l[$ii] = strlen($token_v[$ii]); + } + + $ngram_count = $length - 2; + $cursor = 0; + for ($ii = 0; $ii < $ngram_count; $ii++) { + $ngram_l = $token_l[$ii] + $token_l[$ii + 1] + $token_l[$ii + 2]; + + $ngram = substr($token, $cursor, $ngram_l); $ngrams[$ngram] = $ngram; + + $cursor += $token_l[$ii]; } } diff --git a/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php b/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php index a8690f1d8b..ea65a559ff 100644 --- a/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php +++ b/src/applications/search/ferret/__tests__/PhabricatorFerretEngineTestCase.php @@ -24,4 +24,34 @@ public function testTermsCorpus() { } } + public function testTermNgramExtraction() { + $snowman = "\xE2\x98\x83"; + + $map = array( + 'a' => array(' a '), + 'ab' => array(' ab', 'ab '), + 'abcdef' => array(' ab', 'abc', 'bcd', 'cde', 'def', 'ef '), + "{$snowman}" => array(" {$snowman} "), + "x{$snowman}y" => array( + " x{$snowman}", + "x{$snowman}y", + "{$snowman}y ", + ), + "{$snowman}{$snowman}" => array( + " {$snowman}{$snowman}", + "{$snowman}{$snowman} ", + ), + ); + + $engine = new ManiphestTaskFerretEngine(); + + foreach ($map as $input => $expect) { + $actual = $engine->getTermNgramsFromString($input); + $this->assertEqual( + $actual, + $expect, + pht('Term ngrams for: %s.', $input)); + } + } + } From 9875af739fb5d522817c849e20a9f13fec3b798e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Sep 2017 11:37:10 -0700 Subject: [PATCH 194/865] When we purge the request cache, also force PHP to collect cycles Summary: Ref T12997. See that task for more details. Briefly, an unusual dataset (where commits are mentioned hundreds of times by other commits) is causing some weird memory behavior in the daemons. Forcing PHP to GC cycles explicitly after each task completes seems to help with this, by cleaning up some of the memory between tasks. A more thorough fix might be to untangle the `$xactions` structure, but that's significantly more involved. Test Plan: - Did this locally in a controlled environment, saw an immediate collection of a 500MB `$xactions` cycle. - Put a similar change in production, memory usage seemed less to improve. It's hard to tell for sure that this does anything, but it shouldn't hurt. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12997 Differential Revision: https://secure.phabricator.com/D18657 --- src/applications/cache/PhabricatorCaches.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php index 1314aa4ccc..9c22bde672 100644 --- a/src/applications/cache/PhabricatorCaches.php +++ b/src/applications/cache/PhabricatorCaches.php @@ -51,6 +51,12 @@ public static function getRequestCache() { */ public static function destroyRequestCache() { self::$requestCache = null; + + // See T12997. Force the GC to run when the request cache is destroyed to + // clean up any cycles which may still be hanging around. + if (function_exists('gc_collect_cycles')) { + gc_collect_cycles(); + } } From 0d2065e76c0eefaaaa184e7702d380c35ec0c115 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Sep 2017 11:37:10 -0700 Subject: [PATCH 195/865] (stable) When we purge the request cache, also force PHP to collect cycles Summary: Ref T12997. See that task for more details. Briefly, an unusual dataset (where commits are mentioned hundreds of times by other commits) is causing some weird memory behavior in the daemons. Forcing PHP to GC cycles explicitly after each task completes seems to help with this, by cleaning up some of the memory between tasks. A more thorough fix might be to untangle the `$xactions` structure, but that's significantly more involved. Test Plan: - Did this locally in a controlled environment, saw an immediate collection of a 500MB `$xactions` cycle. - Put a similar change in production, memory usage seemed less to improve. It's hard to tell for sure that this does anything, but it shouldn't hurt. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12997 Differential Revision: https://secure.phabricator.com/D18657 --- src/applications/cache/PhabricatorCaches.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php index 1314aa4ccc..9c22bde672 100644 --- a/src/applications/cache/PhabricatorCaches.php +++ b/src/applications/cache/PhabricatorCaches.php @@ -51,6 +51,12 @@ public static function getRequestCache() { */ public static function destroyRequestCache() { self::$requestCache = null; + + // See T12997. Force the GC to run when the request cache is destroyed to + // clean up any cycles which may still be hanging around. + if (function_exists('gc_collect_cycles')) { + gc_collect_cycles(); + } } From b75a4151c8996c5dfb1c8c14378fe3259666eac2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Sep 2017 12:48:19 -0700 Subject: [PATCH 196/865] Allow the fulltext index to select only transactions with comments Summary: Ref T12997. Although we can't query by transaction type (since we can't easily enumerate all possible types which may have comments -- inline types may also have comments), we //can// just check if there's a comment row or not. This reduces the amount of garbage we need to load to rebuild indexes for unusual objects with hundreds and hundreds of mentions. Test Plan: - Used batch editor to mention a task 700 times. - Indexed it before and after this change, saw index time drop from {nav 1600ms > 160ms}. - Made some new comments on it, verified that they still indexed/queried properly. - Browsed around, made normal transactions, made inline comments. - Added a unique word to an inline comment, indexed revision, searched for word, found revision. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12997 Differential Revision: https://secure.phabricator.com/D18660 --- ...torTransactionsFulltextEngineExtension.php | 1 + ...PhabricatorApplicationTransactionQuery.php | 93 ++++++++++++------- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php index 701ad34ca7..c9f43f49b2 100644 --- a/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php +++ b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php @@ -25,6 +25,7 @@ public function enrichFulltextObject( $xactions = $query ->setViewer($this->getViewer()) ->withObjectPHIDs(array($object->getPHID())) + ->withComments(true) ->needComments(true) ->execute(); diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index da8454c7fc..abe1498fc0 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -7,6 +7,7 @@ abstract class PhabricatorApplicationTransactionQuery private $objectPHIDs; private $authorPHIDs; private $transactionTypes; + private $withComments; private $needComments = true; private $needHandles = true; @@ -34,10 +35,6 @@ final public static function newQueryForObject( abstract public function getTemplateApplicationTransaction(); - protected function buildMoreWhereClauses(AphrontDatabaseConnection $conn_r) { - return array(); - } - public function withPHIDs(array $phids) { $this->phids = $phids; return $this; @@ -58,6 +55,11 @@ public function withTransactionTypes(array $transaction_types) { return $this; } + public function withComments($with_comments) { + $this->withComments = $with_comments; + return $this; + } + public function needComments($need) { $this->needComments = $need; return $this; @@ -70,17 +72,8 @@ public function needHandles($need) { protected function loadPage() { $table = $this->getTemplateApplicationTransaction(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T x %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - $xactions = $table->loadAllFromArray($data); + $xactions = $this->loadStandardPage($table); foreach ($xactions as $xaction) { $xaction->attachViewer($this->getViewer()); @@ -161,50 +154,86 @@ protected function willFilterPage(array $xactions) { return $xactions; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, - 'phid IN (%Ls)', + $conn, + 'x.phid IN (%Ls)', $this->phids); } - if ($this->objectPHIDs) { + if ($this->objectPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'objectPHID IN (%Ls)', + $conn, + 'x.objectPHID IN (%Ls)', $this->objectPHIDs); } - if ($this->authorPHIDs) { + if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'authorPHID IN (%Ls)', + $conn, + 'x.authorPHID IN (%Ls)', $this->authorPHIDs); } - if ($this->transactionTypes) { + if ($this->transactionTypes !== null) { $where[] = qsprintf( - $conn_r, - 'transactionType IN (%Ls)', + $conn, + 'x.transactionType IN (%Ls)', $this->transactionTypes); } - foreach ($this->buildMoreWhereClauses($conn_r) as $clause) { - $where[] = $clause; + if ($this->withComments !== null) { + if (!$this->withComments) { + $where[] = qsprintf( + $conn, + 'c.id IS NULL'); + } } - $where[] = $this->buildPagingClause($conn_r); + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->withComments !== null) { + $xaction = $this->getTemplateApplicationTransaction(); + $comment = $xaction->getApplicationTransactionCommentObject(); + + if ($this->withComments) { + $joins[] = qsprintf( + $conn, + 'JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } else { + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } + } - return $this->formatWhereClause($where); + return $joins; } + protected function shouldGroupQueryResultRows() { + if ($this->withComments !== null) { + return true; + } + + return parent::shouldGroupQueryResultRows(); + } public function getQueryApplicationClass() { // TODO: Sort this out? return null; } + protected function getPrimaryTableAlias() { + return 'x'; + } + } From 5e276df9c00e818bc4fe605dadb245e49b03cd1a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 28 Sep 2017 12:48:19 -0700 Subject: [PATCH 197/865] (stable) Allow the fulltext index to select only transactions with comments Summary: Ref T12997. Although we can't query by transaction type (since we can't easily enumerate all possible types which may have comments -- inline types may also have comments), we //can// just check if there's a comment row or not. This reduces the amount of garbage we need to load to rebuild indexes for unusual objects with hundreds and hundreds of mentions. Test Plan: - Used batch editor to mention a task 700 times. - Indexed it before and after this change, saw index time drop from {nav 1600ms > 160ms}. - Made some new comments on it, verified that they still indexed/queried properly. - Browsed around, made normal transactions, made inline comments. - Added a unique word to an inline comment, indexed revision, searched for word, found revision. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12997 Differential Revision: https://secure.phabricator.com/D18660 --- ...torTransactionsFulltextEngineExtension.php | 1 + ...PhabricatorApplicationTransactionQuery.php | 93 ++++++++++++------- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php index 701ad34ca7..c9f43f49b2 100644 --- a/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php +++ b/src/applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php @@ -25,6 +25,7 @@ public function enrichFulltextObject( $xactions = $query ->setViewer($this->getViewer()) ->withObjectPHIDs(array($object->getPHID())) + ->withComments(true) ->needComments(true) ->execute(); diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index da8454c7fc..abe1498fc0 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -7,6 +7,7 @@ abstract class PhabricatorApplicationTransactionQuery private $objectPHIDs; private $authorPHIDs; private $transactionTypes; + private $withComments; private $needComments = true; private $needHandles = true; @@ -34,10 +35,6 @@ final public static function newQueryForObject( abstract public function getTemplateApplicationTransaction(); - protected function buildMoreWhereClauses(AphrontDatabaseConnection $conn_r) { - return array(); - } - public function withPHIDs(array $phids) { $this->phids = $phids; return $this; @@ -58,6 +55,11 @@ public function withTransactionTypes(array $transaction_types) { return $this; } + public function withComments($with_comments) { + $this->withComments = $with_comments; + return $this; + } + public function needComments($need) { $this->needComments = $need; return $this; @@ -70,17 +72,8 @@ public function needHandles($need) { protected function loadPage() { $table = $this->getTemplateApplicationTransaction(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T x %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - $xactions = $table->loadAllFromArray($data); + $xactions = $this->loadStandardPage($table); foreach ($xactions as $xaction) { $xaction->attachViewer($this->getViewer()); @@ -161,50 +154,86 @@ protected function willFilterPage(array $xactions) { return $xactions; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, - 'phid IN (%Ls)', + $conn, + 'x.phid IN (%Ls)', $this->phids); } - if ($this->objectPHIDs) { + if ($this->objectPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'objectPHID IN (%Ls)', + $conn, + 'x.objectPHID IN (%Ls)', $this->objectPHIDs); } - if ($this->authorPHIDs) { + if ($this->authorPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'authorPHID IN (%Ls)', + $conn, + 'x.authorPHID IN (%Ls)', $this->authorPHIDs); } - if ($this->transactionTypes) { + if ($this->transactionTypes !== null) { $where[] = qsprintf( - $conn_r, - 'transactionType IN (%Ls)', + $conn, + 'x.transactionType IN (%Ls)', $this->transactionTypes); } - foreach ($this->buildMoreWhereClauses($conn_r) as $clause) { - $where[] = $clause; + if ($this->withComments !== null) { + if (!$this->withComments) { + $where[] = qsprintf( + $conn, + 'c.id IS NULL'); + } } - $where[] = $this->buildPagingClause($conn_r); + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->withComments !== null) { + $xaction = $this->getTemplateApplicationTransaction(); + $comment = $xaction->getApplicationTransactionCommentObject(); + + if ($this->withComments) { + $joins[] = qsprintf( + $conn, + 'JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } else { + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } + } - return $this->formatWhereClause($where); + return $joins; } + protected function shouldGroupQueryResultRows() { + if ($this->withComments !== null) { + return true; + } + + return parent::shouldGroupQueryResultRows(); + } public function getQueryApplicationClass() { // TODO: Sort this out? return null; } + protected function getPrimaryTableAlias() { + return 'x'; + } + } From a3a6c4ed2edfa835c1e071f8a433380f2dfe0941 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 29 Sep 2017 09:01:12 -0700 Subject: [PATCH 198/865] Fix fatal when searching for "r matey prepare to be boarded" Summary: See . The Ferret engine replaced `withNameContains()`, but I missed this obscure callsite. Test Plan: - Searched for `r matey prepare to be boarded`. - Before: fatal. - After: no fatal. - Also searched for `r `, got repository. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18661 --- .../engine/PhabricatorJumpNavHandler.php | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/applications/search/engine/PhabricatorJumpNavHandler.php b/src/applications/search/engine/PhabricatorJumpNavHandler.php index 6f6ea3d0a9..ddade36d84 100644 --- a/src/applications/search/engine/PhabricatorJumpNavHandler.php +++ b/src/applications/search/engine/PhabricatorJumpNavHandler.php @@ -50,17 +50,35 @@ public static function getJumpResponse(PhabricatorUser $viewer, $jump) { return id(new AphrontRedirectResponse()) ->setURI("/diffusion/symbol/$symbol/?jump=true$context"); case 'find-repository': - $name = $matches[1]; + $raw_query = $matches[1]; + + $engine = id(new PhabricatorRepository()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + $fulltext_tokens[] = $fulltext_token; + } + $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withNameContains($name) + ->withFerretConstraint($engine, $fulltext_tokens) ->execute(); if (count($repositories) == 1) { // Just one match, jump to repository. $uri = head($repositories)->getURI(); } else { // More than one match, jump to search. - $uri = urisprintf('/diffusion/?order=name&name=%s', $name); + $uri = urisprintf( + '/diffusion/?order=name&query=%s', + $raw_query); } return id(new AphrontRedirectResponse())->setURI($uri); default: From a39f5e11139ae5631eb990fd2d5ed528724ae00e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 29 Sep 2017 14:48:38 -0700 Subject: [PATCH 199/865] Correct bad context path when doing pattern search inside a repository Summary: Ref PHI101. It looks like this was maybe copy/pasted by mistake in recent design refactoring. We need to pass the full path, not the `basename()` of the path, to the search form. Test Plan: Searched inside `scripts/test/`, found results inside `scripts/test/`. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18664 --- .../diffusion/controller/DiffusionBrowseController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index d3223e64fd..b4c0352d53 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1557,7 +1557,8 @@ protected function buildHeaderView(DiffusionRequest $drequest) { $commit_tag = $this->renderCommitHashTag($drequest); - $path = nonempty(basename($drequest->getPath()), '/'); + $path = nonempty($drequest->getPath(), '/'); + $search = $this->renderSearchForm($path); $header = id(new PHUIHeaderView()) From fe646ec328289afd6c52ef38659b92cc8d6bd211 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 29 Sep 2017 13:25:55 -0700 Subject: [PATCH 200/865] Mark Owners package reviewers which own nothing in the current diff Summary: Ref PHI91. When Owners (or Herald, or manual user action) adds package reviewers to a revision, later updates to the revision make some of them less relevant or irrelevant. Provide a hint when a package reviewer doesn't own any of the paths that a diff changes. Humans can then decide if the reviewer is obsolete/irrelevant or not. This is a rough cut to get the feature working, design could probably use some tweaking if it sticks. Test Plan: {F5204309} Reviewers: amckinley Reviewed By: amckinley Subscribers: jboning Differential Revision: https://secure.phabricator.com/D18663 --- .../controller/DifferentialController.php | 130 ++++++++++++------ .../DifferentialDiffViewController.php | 2 + .../DifferentialRevisionViewController.php | 10 ++ .../storage/DifferentialReviewer.php | 10 ++ .../view/DifferentialReviewersView.php | 6 + 5 files changed, 118 insertions(+), 40 deletions(-) diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 2258c4fdcb..fb8a6249a7 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -2,6 +2,10 @@ abstract class DifferentialController extends PhabricatorController { + private $packageChangesetMap; + private $pathPackageMap; + private $authorityPackages; + public function buildSideNavView($for_app = false) { $viewer = $this->getRequest()->getUser(); @@ -21,48 +25,98 @@ public function buildApplicationMenu() { return $this->buildSideNavView(true)->getMenu(); } - protected function buildTableOfContents( - array $changesets, - array $visible_changesets, - array $coverage) { - $viewer = $this->getViewer(); + protected function buildPackageMaps(array $changesets) { + assert_instances_of($changesets, 'DifferentialChangeset'); - $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setViewer($viewer) - ->setBare(true); + $this->packageChangesetMap = array(); + $this->pathPackageMap = array(); + $this->authorityPackages = array(); + + if (!$changesets) { + return; + } + + $viewer = $this->getViewer(); $have_owners = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorOwnersApplication', $viewer); - if ($have_owners) { - $repository_phid = null; - if ($changesets) { - $changeset = head($changesets); - $diff = $changeset->getDiff(); - $repository_phid = $diff->getRepositoryPHID(); - } + if (!$have_owners) { + return; + } - if (!$repository_phid) { - $have_owners = false; - } else { - if ($viewer->getPHID()) { - $packages = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) - ->withAuthorityPHIDs(array($viewer->getPHID())) - ->execute(); - $toc_view->setAuthorityPackages($packages); - } - - $paths = mpull($changesets, 'getOwnersFilename'); - - $control_query = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) - ->withControl($repository_phid, $paths); - $control_query->execute(); + $changeset = head($changesets); + $diff = $changeset->getDiff(); + $repository_phid = $diff->getRepositoryPHID(); + if (!$repository_phid) { + return; + } + + if ($viewer->getPHID()) { + $packages = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withAuthorityPHIDs(array($viewer->getPHID())) + ->execute(); + $this->authorityPackages = $packages; + } + + $paths = mpull($changesets, 'getOwnersFilename'); + + $control_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) + ->withControl($repository_phid, $paths); + $control_query->execute(); + + foreach ($changesets as $changeset) { + $changeset_path = $changeset->getOwnersFilename(); + + $packages = $control_query->getControllingPackagesForPath( + $repository_phid, + $changeset_path); + + $this->pathPackageMap[$changeset_path] = $packages; + foreach ($packages as $package) { + $this->packageChangesetMap[$package->getPHID()][] = $changeset; } } + } + + protected function getAuthorityPackages() { + if ($this->authorityPackages === null) { + throw new PhutilInvalidStateException('buildPackageMaps'); + } + return $this->authorityPackages; + } + + protected function getChangesetPackages(DifferentialChangeset $changeset) { + if ($this->pathPackageMap === null) { + throw new PhutilInvalidStateException('buildPackageMaps'); + } + + $path = $changeset->getOwnersFilename(); + return idx($this->pathPackageMap, $path, array()); + } + + protected function getPackageChangesets($package_phid) { + if ($this->packageChangesetMap === null) { + throw new PhutilInvalidStateException('buildPackageMaps'); + } + + return idx($this->packageChangesetMap, $package_phid, array()); + } + + protected function buildTableOfContents( + array $changesets, + array $visible_changesets, + array $coverage) { + $viewer = $this->getViewer(); + + $toc_view = id(new PHUIDiffTableOfContentsListView()) + ->setViewer($viewer) + ->setBare(true) + ->setAuthorityPackages($this->getAuthorityPackages()); foreach ($changesets as $changeset_id => $changeset) { $is_visible = isset($visible_changesets[$changeset_id]); @@ -78,12 +132,8 @@ protected function buildTableOfContents( ->setCoverage(idx($coverage, $filename)) ->setCoverageID($coverage_id); - if ($have_owners) { - $packages = $control_query->getControllingPackagesForPath( - $repository_phid, - $changeset->getOwnersFilename()); - $item->setPackages($packages); - } + $packages = $this->getChangesetPackages($changeset); + $item->setPackages($packages); $toc_view->addItem($item); } diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index 7340687bc4..e56eb27d99 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -118,6 +118,8 @@ public function handleRequest(AphrontRequest $request) { $changesets = $diff->loadChangesets(); $changesets = msort($changesets, 'getSortKey'); + $this->buildPackageMaps($changesets); + $table_of_contents = $this->buildTableOfContents( $changesets, $changesets, diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 06d8cf0a3f..e1da5ed32e 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -326,11 +326,21 @@ public function handleRequest(AphrontRequest $request) { $other_view = $this->renderOtherRevisions($other_revisions); } + $this->buildPackageMaps($changesets); + $toc_view = $this->buildTableOfContents( $changesets, $visible_changesets, $target->loadCoverageMap($viewer)); + // Attach changesets to each reviewer so we can show which Owners package + // reviewers own no files. + foreach ($revision->getReviewers() as $reviewer) { + $reviewer_phid = $reviewer->getReviewerPHID(); + $reviewer_changesets = $this->getPackageChangesets($reviewer_phid); + $reviewer->attachChangesets($reviewer_changesets); + } + $tab_group = id(new PHUITabGroupView()) ->addTab( id(new PHUITabView()) diff --git a/src/applications/differential/storage/DifferentialReviewer.php b/src/applications/differential/storage/DifferentialReviewer.php index 3aa9bf6362..9df149e788 100644 --- a/src/applications/differential/storage/DifferentialReviewer.php +++ b/src/applications/differential/storage/DifferentialReviewer.php @@ -12,6 +12,7 @@ final class DifferentialReviewer protected $voidedPHID; private $authority = array(); + private $changesets = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -54,6 +55,15 @@ public function hasAuthority(PhabricatorUser $viewer) { return $this->assertAttachedKey($this->authority, $cache_fragment); } + public function attachChangesets(array $changesets) { + $this->changesets = $changesets; + return $this; + } + + public function getChangesets() { + return $this->assertAttached($this->changesets); + } + public function isResigned() { $status_resigned = DifferentialReviewerStatus::STATUS_RESIGNED; return ($this->getReviewerStatus() == $status_resigned); diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 3fcb1c0ccf..33aad25289 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -150,6 +150,12 @@ public function render() { $item->setIcon($icon, $color, $label); $item->setTarget($handle->renderHovercardLink()); + if ($reviewer->isPackage()) { + if (!$reviewer->getChangesets()) { + $item->setNote(pht('(Owns No Changed Paths)')); + } + } + $view->addItem($item); } From 5c73c169fd31d029df764086cab24d19a6317d76 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 29 Sep 2017 16:20:42 -0700 Subject: [PATCH 201/865] Rough cut of "Move tasks to column..." Summary: Ref T5523. See PHI50. See PHI40. This isn't perfect, but should improve things. Add a "Move tasks to column..." action to workboards which moves all visible tasks in a column to another column, either on the same board or on a different board. This is a two-step process so that I don't have to write Javascript, and because I'm not 100% sure this is what users actually want/need. If it sticks, the UI could be refined later. - The first dialog asks you to choose a project, defaulting ot the current project. - The second dialog asks you to choose a column on that project's board. Test Plan: - Moved tasks on the same board. - Moved tasks to a different board. - Tried to move tasks to the starting column, got a sensible error. - Tried to move tasks to no project, got a sensible error. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T5523 Differential Revision: https://secure.phabricator.com/D18665 --- .../PhabricatorProjectBoardViewController.php | 205 +++++++++++++++++- 1 file changed, 204 insertions(+), 1 deletion(-) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 8405ec677f..87cb5bc417 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -34,7 +34,9 @@ public function handleRequest(AphrontRequest $request) { ->setBaseURI($board_uri) ->setIsBoardView(true); - if ($request->isFormPost() && !$request->getBool('initialize')) { + if ($request->isFormPost() + && !$request->getBool('initialize') + && !$request->getStr('move')) { $saved = $search_engine->buildSavedQueryFromRequest($request); $search_engine->saveQuery($saved); $filter_form = id(new AphrontFormView()) @@ -238,6 +240,199 @@ public function handleRequest(AphrontRequest $request) { ->setURI($batch_uri); } + $move_id = $request->getStr('move'); + if (strlen($move_id)) { + $column_id_map = mpull($columns, null, 'getID'); + $move_column = idx($column_id_map, $move_id); + if (!$move_column) { + return new Aphront404Response(); + } + + $move_task_phids = $layout_engine->getColumnObjectPHIDs( + $board_phid, + $move_column->getPHID()); + + foreach ($move_task_phids as $key => $move_task_phid) { + if (empty($task_can_edit_map[$move_task_phid])) { + unset($move_task_phids[$key]); + } + } + + $move_tasks = array_select_keys($tasks, $move_task_phids); + $cancel_uri = $this->getURIWithState($board_uri); + + if (!$move_tasks) { + return $this->newDialog() + ->setTitle(pht('No Movable Tasks')) + ->appendParagraph( + pht( + 'The selected column contains no visible tasks which you '. + 'have permission to move.')) + ->addCancelButton($cancel_uri); + } + + $move_project_phid = $project->getPHID(); + $move_column_phid = null; + $move_project = null; + $move_column = null; + $columns = null; + $errors = array(); + + if ($request->isFormPost()) { + $move_project_phid = head($request->getArr('moveProjectPHID')); + if (!$move_project_phid) { + $move_project_phid = $request->getStr('moveProjectPHID'); + } + + if (!$move_project_phid) { + if ($request->getBool('hasProject')) { + $errors[] = pht('Choose a project to move tasks to.'); + } + } else { + $target_project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($move_project_phid)) + ->executeOne(); + if (!$target_project) { + $errors[] = pht('You must choose a valid project.'); + } else if (!$project->getHasWorkboard()) { + $errors[] = pht( + 'You must choose a project with a workboard.'); + } else { + $move_project = $target_project; + } + } + + if ($move_project) { + $move_engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs(array($move_project->getPHID())) + ->setFetchAllBoards(true) + ->executeLayout(); + + $columns = $move_engine->getColumns($move_project->getPHID()); + $columns = mpull($columns, null, 'getPHID'); + + $move_column_phid = $request->getStr('moveColumnPHID'); + if (!$move_column_phid) { + if ($request->getBool('hasColumn')) { + $errors[] = pht('Choose a column to move tasks to.'); + } + } else { + if (empty($columns[$move_column_phid])) { + $errors[] = pht( + 'Choose a valid column on the target workboard to move '. + 'tasks to.'); + } else if ($columns[$move_column_phid]->getID() == $move_id) { + $errors[] = pht( + 'You can not move tasks from a column to itself.'); + } else { + $move_column = $columns[$move_column_phid]; + } + } + } + } + + if ($move_column && $move_project) { + foreach ($move_tasks as $move_task) { + $xactions = array(); + + // If we're switching projects, get out of the old project first + // and move to the new project. + if ($move_project->getID() != $project->getID()) { + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setNewValue( + array( + '-' => array( + $project->getPHID() => $project->getPHID(), + ), + '+' => array( + $move_project->getPHID() => $move_project->getPHID(), + ), + )); + } + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) + ->setNewValue( + array( + array( + 'columnPHID' => $move_column->getPHID(), + ), + )); + + $editor = id(new ManiphestTransactionEditor()) + ->setActor($viewer) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request); + + $editor->applyTransactions($move_task, $xactions); + } + + return id(new AphrontRedirectResponse()) + ->setURI($cancel_uri); + } + + if ($move_project) { + $column_form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl( + id(new AphrontFormSelectControl()) + ->setName('moveColumnPHID') + ->setLabel(pht('Move to Column')) + ->setValue($move_column_phid) + ->setOptions(mpull($columns, 'getDisplayName', 'getPHID'))); + + return $this->newDialog() + ->setTitle(pht('Move Tasks')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setErrors($errors) + ->addHiddenInput('move', $move_id) + ->addHiddenInput('moveProjectPHID', $move_project->getPHID()) + ->addHiddenInput('hasColumn', true) + ->addHiddenInput('hasProject', true) + ->appendParagraph( + pht( + 'Choose a column on the %s workboard to move tasks to:', + $viewer->renderHandle($move_project->getPHID()))) + ->appendForm($column_form) + ->addSubmitButton(pht('Move Tasks')) + ->addCancelButton($cancel_uri); + } + + if ($move_project_phid) { + $move_project_phid_value = array($move_project_phid); + } else { + $move_project_phid_value = array(); + } + + $project_form = id(new AphrontFormView()) + ->setViewer($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setName('moveProjectPHID') + ->setLimit(1) + ->setLabel(pht('Move to Project')) + ->setValue($move_project_phid_value) + ->setDatasource(new PhabricatorProjectDatasource())); + + return $this->newDialog() + ->setTitle(pht('Move Tasks')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setErrors($errors) + ->addHiddenInput('move', $move_id) + ->addHiddenInput('hasProject', true) + ->appendForm($project_form) + ->addSubmitButton(pht('Continue')) + ->addCancelButton($cancel_uri); + } + + $board_id = celerity_generate_unique_node_id(); $board = id(new PHUIWorkboardView()) @@ -851,6 +1046,14 @@ private function buildColumnMenu( ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); + $batch_move_uri = $request->getRequestURI(); + $batch_move_uri->setQueryParam('move', $column->getID()); + $column_items[] = id(new PhabricatorActionView()) + ->setIcon('fa-arrow-right') + ->setName(pht('Move Tasks to Column...')) + ->setHref($batch_move_uri) + ->setWorkflow(true); + // Column Related Actions Below // $edit_uri = 'board/'.$this->id.'/edit/'.$column->getID().'/'; From cd14194a329788d5fff6365bcade278fd18f3612 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 Oct 2017 07:21:38 -0700 Subject: [PATCH 202/865] Fix transaction queries using withComments() for transactions with no comments Summary: See . Some object types (for example, Passphrase Credentials) support indexing but not commenting. Make `withComments(...)` work properly if the transaction type does not support comments. Test Plan: Indexed a credential (no comments) and a revision (comments) with `bin/search index --trace ...`. Before, credential fataled. After, credetial succeeds, and skips the transaction query. Before and after, the revision queries the transaction table. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18667 --- ...PhabricatorApplicationTransactionQuery.php | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index abe1498fc0..f15522a087 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -203,16 +203,29 @@ protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $xaction = $this->getTemplateApplicationTransaction(); $comment = $xaction->getApplicationTransactionCommentObject(); - if ($this->withComments) { - $joins[] = qsprintf( - $conn, - 'JOIN %T c ON x.phid = c.transactionPHID', - $comment->getTableName()); + // Not every transaction type has comments, so we may be able to + // implement this constraint trivially. + + if (!$comment) { + if ($this->withComments) { + throw new PhabricatorEmptyQueryException(); + } else { + // If we're querying for transactions with no comments and the + // transaction type does not support comments, we don't need to + // do anything. + } } else { - $joins[] = qsprintf( - $conn, - 'LEFT JOIN %T c ON x.phid = c.transactionPHID', - $comment->getTableName()); + if ($this->withComments) { + $joins[] = qsprintf( + $conn, + 'JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } else { + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } } } From 8297f7a18288b5858674f65de17570abd2c139e9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 Oct 2017 07:21:38 -0700 Subject: [PATCH 203/865] (stable) Fix transaction queries using withComments() for transactions with no comments Summary: See . Some object types (for example, Passphrase Credentials) support indexing but not commenting. Make `withComments(...)` work properly if the transaction type does not support comments. Test Plan: Indexed a credential (no comments) and a revision (comments) with `bin/search index --trace ...`. Before, credential fataled. After, credetial succeeds, and skips the transaction query. Before and after, the revision queries the transaction table. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18667 --- ...PhabricatorApplicationTransactionQuery.php | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index abe1498fc0..f15522a087 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -203,16 +203,29 @@ protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $xaction = $this->getTemplateApplicationTransaction(); $comment = $xaction->getApplicationTransactionCommentObject(); - if ($this->withComments) { - $joins[] = qsprintf( - $conn, - 'JOIN %T c ON x.phid = c.transactionPHID', - $comment->getTableName()); + // Not every transaction type has comments, so we may be able to + // implement this constraint trivially. + + if (!$comment) { + if ($this->withComments) { + throw new PhabricatorEmptyQueryException(); + } else { + // If we're querying for transactions with no comments and the + // transaction type does not support comments, we don't need to + // do anything. + } } else { - $joins[] = qsprintf( - $conn, - 'LEFT JOIN %T c ON x.phid = c.transactionPHID', - $comment->getTableName()); + if ($this->withComments) { + $joins[] = qsprintf( + $conn, + 'JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } else { + $joins[] = qsprintf( + $conn, + 'LEFT JOIN %T c ON x.phid = c.transactionPHID', + $comment->getTableName()); + } } } From f9110b87abf337dd1e7714d755775e53cffd4db9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 Oct 2017 11:36:50 -0700 Subject: [PATCH 204/865] In "Move Tasks to Column...", show only visible columns Summary: See PHI94. I considered this initially but wasn't sure about it. However, PHI94 brings up the good point that we already use a similar rule in Maniphest. For consistency, only show visible columns here too. Test Plan: Used "Move tasks to column..." on a board with visible and hidden columns, only saw visbile columns offered in the dropdown. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18668 --- .../controller/PhabricatorProjectBoardViewController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 87cb5bc417..793fe5603d 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -313,6 +313,12 @@ public function handleRequest(AphrontRequest $request) { $columns = $move_engine->getColumns($move_project->getPHID()); $columns = mpull($columns, null, 'getPHID'); + foreach ($columns as $key => $column) { + if ($column->isHidden()) { + unset($columns[$key]); + } + } + $move_column_phid = $request->getStr('moveColumnPHID'); if (!$move_column_phid) { if ($request->getBool('hasColumn')) { From 4390a274c1d83eb71f3d6d6211fd58644a6059f4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 Oct 2017 11:36:50 -0700 Subject: [PATCH 205/865] (stable) In "Move Tasks to Column...", show only visible columns Summary: See PHI94. I considered this initially but wasn't sure about it. However, PHI94 brings up the good point that we already use a similar rule in Maniphest. For consistency, only show visible columns here too. Test Plan: Used "Move tasks to column..." on a board with visible and hidden columns, only saw visbile columns offered in the dropdown. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18668 --- .../controller/PhabricatorProjectBoardViewController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 87cb5bc417..793fe5603d 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -313,6 +313,12 @@ public function handleRequest(AphrontRequest $request) { $columns = $move_engine->getColumns($move_project->getPHID()); $columns = mpull($columns, null, 'getPHID'); + foreach ($columns as $key => $column) { + if ($column->isHidden()) { + unset($columns[$key]); + } + } + $move_column_phid = $request->getStr('moveColumnPHID'); if (!$move_column_phid) { if ($request->getBool('hasColumn')) { From 89fe84f9789629bbd7166965811e6d33c635f51b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 3 Oct 2017 12:50:01 -0700 Subject: [PATCH 206/865] Add a "/source/..." URI for Diffusion commits which redirects Summary: See PHI112. The install presumably wants to generate links to Diffusion commits from an external tool, but only knows the short name of the repository. Provide a `/source/phabricator/commit/abcdef908273` URI which redirects to the canonical URI for the commit. Test Plan: - Visited `/source/` URI for a commit, got a redirect. - Visited normal URI for a commit, got a commit page. - Visited `/branches/` and `/tags/` for a `/source/` repository, got proper pages. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18676 --- .../PhabricatorDiffusionApplication.php | 9 +++++---- .../controller/DiffusionCommitController.php | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index cf9c14aa8b..5a426ec299 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -69,10 +69,11 @@ public function getRoutes() { 'branches/(?P.*)' => 'DiffusionBranchTableController', 'refs/(?P.*)' => 'DiffusionRefTableController', 'lint/(?P.*)' => 'DiffusionLintController', - 'commit/(?P[a-z0-9]+)/branches/' - => 'DiffusionCommitBranchesController', - 'commit/(?P[a-z0-9]+)/tags/' - => 'DiffusionCommitTagsController', + 'commit/(?P[a-z0-9]+)' => array( + '/?' => 'DiffusionCommitController', + '/branches/' => 'DiffusionCommitBranchesController', + '/tags/' => 'DiffusionCommitTagsController', + ), 'compare/' => 'DiffusionCompareController', 'manage/(?:(?P[^/]+)/)?' => 'DiffusionRepositoryManagePanelsController', diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 59a132b17d..38c94e8a92 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -22,17 +22,27 @@ public function handleRequest(AphrontRequest $request) { $drequest = $this->getDiffusionRequest(); $viewer = $request->getUser(); + $repository = $drequest->getRepository(); + $commit_identifier = $drequest->getCommit(); + + // If this page is being accessed via "/source/xyz/commit/...", redirect + // to the canonical URI. + $has_callsign = strlen($request->getURIData('repositoryCallsign')); + $has_id = strlen($request->getURIData('repositoryID')); + if (!$has_callsign && !$has_id) { + $canonical_uri = $repository->getCommitURI($commit_identifier); + return id(new AphrontRedirectResponse()) + ->setURI($canonical_uri); + } if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); } - $repository = $drequest->getRepository(); - $commit = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withRepository($repository) - ->withIdentifiers(array($drequest->getCommit())) + ->withIdentifiers(array($commit_identifier)) ->needCommitData(true) ->needAuditRequests(true) ->executeOne(); From 1de130c9f5b241cf1e1353f5d87968e7aa5eec1f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 Oct 2017 15:27:02 -0700 Subject: [PATCH 207/865] Allow the Ferret engine to remove "common" ngrams from the index Summary: Ref T13000. This adds support for tracking "common" ngrams, which occur in too many documents to be useful as part of the ngram index. If an ngram is listed in the "common" table, it won't be written when indexing documents, or queried for when searching for them. In this change, nothing actually writes to the "common" table. I'll start writing to the table in a followup change. Specifically, I plan to do this: - A new GC process updates the "common" table periodically, by writing ngrams which appear in more than X% of documents to it, for some value of X, if there are at least a minimum number of documents (maybe like 4,000). - A new GC process deletes ngrams that have been added to the common table from the existing indexes. Hopefully, this will pare down the ngrams index to something reasonable over time without requiring any manual tuning. Test Plan: - Ran some queries and indexes. - Manually inserted ngrams `xxx` and `yyy` into the ngrams table, searched and indexed, saw them ignored as viable ngrams for search/index. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18672 --- .../20171002.cngram.01.maniphest.sql | 7 +++ .../autopatches/20171002.cngram.02.event.sql | 7 +++ .../20171002.cngram.03.revision.sql | 7 +++ .../autopatches/20171002.cngram.04.fund.sql | 7 +++ .../autopatches/20171002.cngram.05.owners.sql | 7 +++ .../20171002.cngram.06.passphrase.sql | 7 +++ .../autopatches/20171002.cngram.07.blog.sql | 7 +++ .../autopatches/20171002.cngram.08.post.sql | 7 +++ .../autopatches/20171002.cngram.09.pholio.sql | 7 +++ .../20171002.cngram.10.phriction.sql | 7 +++ .../20171002.cngram.11.project.sql | 7 +++ .../autopatches/20171002.cngram.12.user.sql | 7 +++ .../20171002.cngram.13.repository.sql | 7 +++ .../autopatches/20171002.cngram.14.commit.sql | 7 +++ .../schema/PhabricatorConfigSchemaSpec.php | 6 +++ ...abricatorFerretFulltextEngineExtension.php | 49 ++++++++++++++----- .../search/ferret/PhabricatorFerretEngine.php | 31 ++++++++++++ ...PhabricatorCursorPagedPolicyAwareQuery.php | 28 +++++++++++ 18 files changed, 200 insertions(+), 12 deletions(-) create mode 100644 resources/sql/autopatches/20171002.cngram.01.maniphest.sql create mode 100644 resources/sql/autopatches/20171002.cngram.02.event.sql create mode 100644 resources/sql/autopatches/20171002.cngram.03.revision.sql create mode 100644 resources/sql/autopatches/20171002.cngram.04.fund.sql create mode 100644 resources/sql/autopatches/20171002.cngram.05.owners.sql create mode 100644 resources/sql/autopatches/20171002.cngram.06.passphrase.sql create mode 100644 resources/sql/autopatches/20171002.cngram.07.blog.sql create mode 100644 resources/sql/autopatches/20171002.cngram.08.post.sql create mode 100644 resources/sql/autopatches/20171002.cngram.09.pholio.sql create mode 100644 resources/sql/autopatches/20171002.cngram.10.phriction.sql create mode 100644 resources/sql/autopatches/20171002.cngram.11.project.sql create mode 100644 resources/sql/autopatches/20171002.cngram.12.user.sql create mode 100644 resources/sql/autopatches/20171002.cngram.13.repository.sql create mode 100644 resources/sql/autopatches/20171002.cngram.14.commit.sql diff --git a/resources/sql/autopatches/20171002.cngram.01.maniphest.sql b/resources/sql/autopatches/20171002.cngram.01.maniphest.sql new file mode 100644 index 0000000000..9b275f5b45 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.01.maniphest.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_maniphest.maniphest_task_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.02.event.sql b/resources/sql/autopatches/20171002.cngram.02.event.sql new file mode 100644 index 0000000000..a071fdcd19 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.02.event.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_event_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.03.revision.sql b/resources/sql/autopatches/20171002.cngram.03.revision.sql new file mode 100644 index 0000000000..40c2450598 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.03.revision.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_differential.differential_revision_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.04.fund.sql b/resources/sql/autopatches/20171002.cngram.04.fund.sql new file mode 100644 index 0000000000..34975ce4fb --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.04.fund.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_fund.fund_initiative_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.05.owners.sql b/resources/sql/autopatches/20171002.cngram.05.owners.sql new file mode 100644 index 0000000000..e98d29f87c --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.05.owners.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_package_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.06.passphrase.sql b/resources/sql/autopatches/20171002.cngram.06.passphrase.sql new file mode 100644 index 0000000000..f9afa9ad87 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.06.passphrase.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_passphrase.passphrase_credential_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.07.blog.sql b/resources/sql/autopatches/20171002.cngram.07.blog.sql new file mode 100644 index 0000000000..34001c3608 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.07.blog.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_blog_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.08.post.sql b/resources/sql/autopatches/20171002.cngram.08.post.sql new file mode 100644 index 0000000000..9a9c70867e --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.08.post.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_phame.phame_post_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.09.pholio.sql b/resources/sql/autopatches/20171002.cngram.09.pholio.sql new file mode 100644 index 0000000000..6e8b8f8dcc --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.09.pholio.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_pholio.pholio_mock_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.10.phriction.sql b/resources/sql/autopatches/20171002.cngram.10.phriction.sql new file mode 100644 index 0000000000..ed31dc30ba --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.10.phriction.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_phriction.phriction_document_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.11.project.sql b/resources/sql/autopatches/20171002.cngram.11.project.sql new file mode 100644 index 0000000000..9c11235ba7 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.11.project.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_project.project_project_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.12.user.sql b/resources/sql/autopatches/20171002.cngram.12.user.sql new file mode 100644 index 0000000000..3e8499aaa6 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.12.user.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_user.user_user_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.13.repository.sql b/resources/sql/autopatches/20171002.cngram.13.repository.sql new file mode 100644 index 0000000000..e406c44edf --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.13.repository.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_repository_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171002.cngram.14.commit.sql b/resources/sql/autopatches/20171002.cngram.14.commit.sql new file mode 100644 index 0000000000..48c1a02594 --- /dev/null +++ b/resources/sql/autopatches/20171002.cngram.14.commit.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_commit_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index b24adf27cd..971f5b828f 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -73,6 +73,12 @@ protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) { $engine->getNgramsTableName(), $engine->getNgramsSchemaColumns(), $engine->getNgramsSchemaKeys()); + + $this->buildRawSchema( + $engine->getApplicationName(), + $engine->getCommonNgramsTableName(), + $engine->getCommonNgramsSchemaColumns(), + $engine->getCommonNgramsSchemaKeys()); } protected function buildRawSchema( diff --git a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php index 6903072345..fe50518f3f 100644 --- a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php @@ -165,21 +165,46 @@ public function indexFulltextObject( $ferret_field['normalCorpus']); } - $sql = array(); - foreach ($ngrams as $ngram) { - $sql[] = qsprintf( + if ($ngrams) { + $common = queryfx_all( $conn, - '(%d, %s)', - $document_id, - $ngram); + 'SELECT ngram FROM %T WHERE ngram IN (%Ls)', + $engine->getCommonNgramsTableName(), + $ngrams); + $common = ipull($common, 'ngram', 'ngram'); + + foreach ($ngrams as $key => $ngram) { + if (isset($common[$ngram])) { + unset($ngrams[$key]); + continue; + } + + // NOTE: MySQL discards trailing whitespace in CHAR(X) columns. + $trim_ngram = rtrim($ngram, ' '); + if (isset($common[$ngram])) { + unset($ngrams[$key]); + continue; + } + } } - foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { - queryfx( - $conn, - 'INSERT INTO %T (documentID, ngram) VALUES %Q', - $engine->getNgramsTableName(), - $chunk); + if ($ngrams) { + $sql = array(); + foreach ($ngrams as $ngram) { + $sql[] = qsprintf( + $conn, + '(%d, %s)', + $document_id, + $ngram); + } + + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { + queryfx( + $conn, + 'INSERT INTO %T (documentID, ngram) VALUES %Q', + $engine->getNgramsTableName(), + $chunk); + } } } catch (Exception $ex) { $object->killTransaction(); diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php index 7cd06ae549..ee6ca57ec3 100644 --- a/src/applications/search/ferret/PhabricatorFerretEngine.php +++ b/src/applications/search/ferret/PhabricatorFerretEngine.php @@ -295,4 +295,35 @@ public function getNgramsSchemaKeys() { ); } + public function getCommonNgramsTableName() { + $application = $this->getApplicationName(); + $scope = $this->getScopeName(); + + return "{$application}_{$scope}_fngrams_common"; + } + + public function getCommonNgramsSchemaColumns() { + return array( + 'id' => 'auto', + 'ngram' => 'char3', + 'needsCollection' => 'bool', + ); + } + + public function getCommonNgramsSchemaKeys() { + return array( + 'PRIMARY' => array( + 'columns' => array('id'), + 'unique' => true, + ), + 'key_ngram' => array( + 'columns' => array('ngram'), + 'unique' => true, + ), + 'key_collect' => array( + 'columns' => array('needsCollection'), + ), + ); + } + } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 09cb3e499c..c2b8ce04e0 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1700,6 +1700,34 @@ protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) { } } + // Remove common ngrams, like "the", which occur too frequently in + // documents to be useful in constraining the query. The best ngrams + // are obscure sequences which occur in very few documents. + + if ($flat) { + $common_ngrams = queryfx_all( + $conn, + 'SELECT ngram FROM %T WHERE ngram IN (%Ls)', + $engine->getCommonNgramsTableName(), + ipull($flat, 'ngram')); + $common_ngrams = ipull($common_ngrams, 'ngram', 'ngram'); + + foreach ($flat as $key => $spec) { + $ngram = $spec['ngram']; + if (isset($common_ngrams[$ngram])) { + unset($flat[$key]); + continue; + } + + // NOTE: MySQL discards trailing whitespace in CHAR(X) columns. + $trim_ngram = rtrim($ngram, ' '); + if (isset($common_ngrams[$trim_ngram])) { + unset($flat[$key]); + continue; + } + } + } + // MySQL only allows us to join a maximum of 61 tables per query. Each // ngram is going to cost us a join toward that limit, so if the user // specified a very long query string, just pick 16 of the ngrams From 3e589cdd73ba40c3e408dabd482ee7fc6165ace1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 Oct 2017 16:14:46 -0700 Subject: [PATCH 208/865] Add a workflow for populating (or depopulating) the common ngrams table Summary: Depends on D18672. Ref T13000. This does an on-demand build of the common ngrams table. Plan here is: - Push to `secure`. - Build the common ngrams table here. - See if stuff breaks? If it looks okay on this dataset, we can build out the GC support and try it in production. Test Plan: - Locally, my dataset has a bunch of `bin/lipsum` tasks with similar, common words. - Verified that ipsum terms now skip ngrams. For "lorem ipsum" search performance actually IMPROVED by skipping the ngrams table (12s to 9s). - Queried for normal terms, got very fast results using the ngram table, as normal. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18673 --- src/__phutil_library_map__.php | 2 + ...bricatorSearchManagementNgramsWorkflow.php | 106 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c535d46322..dd2bcd8dfe 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3948,6 +3948,7 @@ 'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchIndexVersionDestructionEngineExtension.php', 'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php', 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', + 'PhabricatorSearchManagementNgramsWorkflow' => 'applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php', 'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php', 'PhabricatorSearchNgrams' => 'applications/search/ngrams/PhabricatorSearchNgrams.php', 'PhabricatorSearchNgramsDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php', @@ -9528,6 +9529,7 @@ 'PhabricatorSearchIndexVersionDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', + 'PhabricatorSearchManagementNgramsWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSearchNgrams' => 'PhabricatorSearchDAO', 'PhabricatorSearchNgramsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', diff --git a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php new file mode 100644 index 0000000000..d1a6e0bdff --- /dev/null +++ b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php @@ -0,0 +1,106 @@ +setName('ngrams') + ->setSynopsis(pht('Recompute common ngrams.')) + ->setArguments( + array( + array( + 'name' => 'reset', + 'help' => pht('Reset all common ngram records.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $is_reset = $args->getArg('reset'); + + $all_objects = id(new PhutilClassMapQuery()) + ->setAncestorClass('PhabricatorFerretInterface') + ->execute(); + + $min_documents = 4096; + $threshold = 0.15; + + foreach ($all_objects as $object) { + $engine = $object->newFerretEngine(); + $conn = $object->establishConnection('w'); + $display_name = get_class($object); + + if ($is_reset) { + echo tsprintf( + "%s\n", + pht( + 'Resetting common ngrams for "%s".', + $display_name)); + + queryfx( + $conn, + 'DELETE FROM %T', + $engine->getCommonNgramsTableName()); + continue; + } + + $document_count = queryfx_one( + $conn, + 'SELECT COUNT(*) N FROM %T', + $engine->getDocumentTableName()); + $document_count = $document_count['N']; + + if ($document_count < $min_documents) { + echo tsprintf( + "%s\n", + pht( + 'Too few documents of type "%s" for any ngrams to be common.', + $display_name)); + continue; + } + + $min_frequency = (int)ceil($document_count * $threshold); + $common_ngrams = queryfx_all( + $conn, + 'SELECT ngram, COUNT(*) N FROM %T + GROUP BY ngram + HAVING N >= %d', + $engine->getNgramsTableName(), + $min_frequency); + + if (!$common_ngrams) { + echo tsprintf( + "%s\n", + pht( + 'No new common ngrams exist for "%s".', + $display_name)); + continue; + } + + $sql = array(); + foreach ($common_ngrams as $ngram) { + $sql[] = qsprintf( + $conn, + '(%s, 1)', + $ngram['ngram']); + } + + foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { + queryfx( + $conn, + 'INSERT IGNORE INTO %T (ngram, needsCollection) + VALUES %Q', + $engine->getCommonNgramsTableName(), + $chunk); + } + + echo tsprintf( + "%s\n", + pht( + 'Updated common ngrams for "%s".', + $display_name)); + } + } + +} From bc9de7eceef01c972c00cadfdff2e65cb996ab7e Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 3 Oct 2017 12:19:34 -0700 Subject: [PATCH 209/865] Typo fix Summary: vive la resistance Test Plan: doitlive Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18674 --- src/docs/user/cluster/cluster_notifications.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/user/cluster/cluster_notifications.diviner b/src/docs/user/cluster/cluster_notifications.diviner index 3cdeec3c39..79c89769fc 100644 --- a/src/docs/user/cluster/cluster_notifications.diviner +++ b/src/docs/user/cluster/cluster_notifications.diviner @@ -14,7 +14,7 @@ are: - performance and capacity may improve. This configuration is relatively simple, but has a small impact on availability -and does nothing to increase resitance to data loss. +and does nothing to increase resistance to data loss. Clustering Design Goals From b9fd5262509d1f8ace3f46c9782f8ee0638a3f51 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 Oct 2017 07:16:33 -0700 Subject: [PATCH 210/865] Fix a fatal on user email settings when `account.editable` is disabled Summary: If `account.editable` is set to false, we try to add a `null` button and fatal: > Argument 1 passed to PHUIHeaderView::addActionLink() must be an instance of PHUIButtonView, null given, called in /srv/phabricator/phabricator/src/applications/settings/panel/PhabricatorSettingsPanel.php on line 290 Instead, don't try to render `null` as a button. Test Plan: - Configured `account.editable` false. - Viewed email address settings. - Before: fatal. - After: page works, no button is provided. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18677 --- .../panel/PhabricatorEmailAddressesSettingsPanel.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php index 1bdb95f568..cd1bfba540 100644 --- a/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php @@ -138,9 +138,9 @@ public function processRequest(AphrontRequest $request) { $editable, )); - $button = null; + $buttons = array(); if ($editable) { - $button = id(new PHUIButtonView()) + $buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-plus') ->setText(pht('Add New Address')) @@ -149,7 +149,7 @@ public function processRequest(AphrontRequest $request) { ->setColor(PHUIButtonView::GREY); } - return $this->newBox(pht('Email Addresses'), $table, array($button)); + return $this->newBox(pht('Email Addresses'), $table, $buttons); } private function returnNewAddressResponse( From 0ea5d668d1d40c5e605fa838545fd88a636d15ba Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 Oct 2017 10:54:42 -0700 Subject: [PATCH 211/865] Enable hovercards for the "Task Graph" UI in Maniphest Summary: See PHI118. Enables hovercards to support peeking at tags and other details if you, e.g., create numerous identical subtasks of each task. Test Plan: {F5210816} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18681 --- src/infrastructure/graph/ManiphestTaskGraph.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php index 24e463e273..99191760dd 100644 --- a/src/infrastructure/graph/ManiphestTaskGraph.php +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -27,6 +27,8 @@ protected function isClosed($object) { protected function newTableRow($phid, $object, $trace) { $viewer = $this->getViewer(); + Javelin::initBehavior('phui-hovercards'); + if ($object) { $status = $object->getStatus(); $priority = $object->getPriority(); @@ -51,15 +53,16 @@ protected function newTableRow($phid, $object, $trace) { $assigned = phutil_tag('em', array(), pht('None')); } - $full_title = $object->getTitle(); - - $link = phutil_tag( + $link = javelin_tag( 'a', array( 'href' => $object->getURI(), - 'title' => $full_title, + 'sigil' => 'hovercard', + 'meta' => array( + 'hoverPHID' => $object->getPHID(), + ), ), - $full_title); + $object->getTitle()); $link = array( phutil_tag( From 02e1440ef2605312940620b5e96764b282fb48cd Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 Oct 2017 09:10:18 -0700 Subject: [PATCH 212/865] Dump tables one at a time, rather than all at once Summary: Ref T13000. This allows us to be more selective about which tables we dump data for, to reduce the size of backups and exports. The immediate goal is to make large `ngrams` tables more manageable in the cluster, but this generally makes all backups and exports faster and easier. Here, tables are dumped one at a time. A followup change will sometimes add the `--no-data` flag, to skip dumping readthrough caches and (optionally) rebuildable indexes. Test Plan: Compared a dump from `master` and from this branch, found them to be essentially identical. The new dump has a little more header information in each section. Verified each contains the same number of `CREATE TABLE` statements. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18679 --- ...abricatorStorageManagementDumpWorkflow.php | 120 +++++++++++------- 1 file changed, 76 insertions(+), 44 deletions(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index 4dc5c64042..f491133c67 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -62,7 +62,24 @@ public function didExecute(PhutilArgumentParser $args) { return 1; } - $databases = $api->getDatabaseList($patches, true); + $ref = $api->getRef(); + $ref_key = $ref->getRefKey(); + + $schemata_map = id(new PhabricatorConfigSchemaQuery()) + ->setAPIs(array($api)) + ->setRefs(array($ref)) + ->loadActualSchemata(); + $schemata = $schemata_map[$ref_key]; + + $targets = array(); + foreach ($schemata->getDatabases() as $database_name => $database) { + foreach ($database->getTables() as $table_name => $table) { + $targets[] = array( + 'database' => $database_name, + 'table' => $table_name, + ); + } + } list($host, $port) = $this->getBareHostAndPort($api->getHost()); @@ -126,35 +143,42 @@ public function didExecute(PhutilArgumentParser $args) { $argv[] = $port; } - $argv[] = '--databases'; - foreach ($databases as $database) { - $argv[] = $database; - } - + $commands = array(); + foreach ($targets as $target) { + $target_argv = $argv; + + if ($has_password) { + $commands[] = csprintf( + 'mysqldump -p%P %Ls -- %R %R', + $password, + $target_argv, + $target['database'], + $target['table']); + } else { + $command = csprintf( + 'mysqldump %Ls -- %R %R', + $target_argv, + $target['database'], + $target['table']); + } - if ($has_password) { - $command = csprintf('mysqldump -p%P %Ls', $password, $argv); - } else { - $command = csprintf('mysqldump %Ls', $argv); + $commands[] = $command; } + // Decrease the CPU priority of this process so it doesn't contend with // other more important things. if (function_exists('proc_nice')) { proc_nice(19); } - - // If we aren't writing to a file, just passthru the command. - if ($output_file === null) { - return phutil_passthru('%C', $command); - } - // If we are writing to a file, stream the command output to disk. This // mode makes sure the whole command fails if there's an error (commonly, // a full disk). See T6996 for discussion. - if ($is_compress) { + if ($output_file === null) { + $file = null; + } else if ($is_compress) { $file = gzopen($output_file, 'wb1'); } else { $file = fopen($output_file, 'wb'); @@ -167,41 +191,47 @@ public function didExecute(PhutilArgumentParser $args) { $file)); } - $future = new ExecFuture('%C', $command); - try { - $iterator = id(new FutureIterator(array($future))) - ->setUpdateInterval(0.100); - foreach ($iterator as $ready) { - list($stdout, $stderr) = $future->read(); - $future->discardBuffers(); - - if (strlen($stderr)) { - fwrite(STDERR, $stderr); - } + foreach ($commands as $command) { + $future = new ExecFuture('%C', $command); + + $iterator = id(new FutureIterator(array($future))) + ->setUpdateInterval(0.100); + foreach ($iterator as $ready) { + list($stdout, $stderr) = $future->read(); + $future->discardBuffers(); - if (strlen($stdout)) { - if ($is_compress) { - $ok = gzwrite($file, $stdout); - } else { - $ok = fwrite($file, $stdout); + if (strlen($stderr)) { + fwrite(STDERR, $stderr); } - if ($ok !== strlen($stdout)) { - throw new Exception( - pht( - 'Failed to write %d byte(s) to file "%s".', - new PhutilNumber(strlen($stdout)), - $output_file)); + if (strlen($stdout)) { + if (!$file) { + $ok = fwrite(STDOUT, $stdout); + } else if ($is_compress) { + $ok = gzwrite($file, $stdout); + } else { + $ok = fwrite($file, $stdout); + } + + if ($ok !== strlen($stdout)) { + throw new Exception( + pht( + 'Failed to write %d byte(s) to file "%s".', + new PhutilNumber(strlen($stdout)), + $output_file)); + } } - } - if ($ready !== null) { - $ready->resolvex(); + if ($ready !== null) { + $ready->resolvex(); + } } } - if ($is_compress) { + if (!$file) { + $ok = true; + } else if ($is_compress) { $ok = gzclose($file); } else { $ok = fclose($file); @@ -218,7 +248,9 @@ public function didExecute(PhutilArgumentParser $args) { // we don't leave any confusing artifacts laying around. try { - Filesystem::remove($output_file); + if ($file !== null) { + Filesystem::remove($output_file); + } } catch (Exception $ex) { // Ignore any errors we hit. } From c767c971ca796e22253726fe72135af6ba485e67 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 Oct 2017 10:51:29 -0700 Subject: [PATCH 213/865] Add "persistence" types (data, cache, or index) to tables, and tweak what "storage dump" dumps Summary: Ref T13000. This marks each table as either "data" (normal data), "cache" (automatically rebuilt, no need to ever dump) or "index" (can be manually rebuilt). By default, `bin/storage dump` dumps data and index tables, but not cache tables. With `--no-indexes`, it dumps only data tables. Indexes can be rebuilt after a restore with `bin/search index --all ...`. Test Plan: - Ran `--no-indexes` and normal dumps with `--trace`, verified that cache and index (former case) or cache only (latter case) tables were dumped with `--no-data`. - Verified dump has the same number of `CREATE TABLE` statements as before the changes. - Reviewed persistence tags in the web UI (note Ferret engine tables are "Index"): {F5210886} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18682 --- .../storage/PhabricatorCacheSchemaSpec.php | 3 + ...bricatorConfigDatabaseStatusController.php | 3 + .../schema/PhabricatorConfigSchemaQuery.php | 2 + .../schema/PhabricatorConfigSchemaSpec.php | 31 ++++++++-- .../schema/PhabricatorConfigTableSchema.php | 26 ++++++++ .../storage/DifferentialSchemaSpec.php | 3 + .../configuration/configuring_backups.diviner | 18 ++++++ ...abricatorStorageManagementDumpWorkflow.php | 61 +++++++++++++++++-- 8 files changed, 138 insertions(+), 9 deletions(-) diff --git a/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php b/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php index 97d6e1ac81..c9e78af0bc 100644 --- a/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php +++ b/src/applications/cache/storage/PhabricatorCacheSchemaSpec.php @@ -30,6 +30,9 @@ public function buildSchemata() { 'key_ttl' => array( 'columns' => array('cacheExpires'), ), + ), + array( + 'persistence' => PhabricatorConfigTableSchema::PERSISTENCE_CACHE, )); } diff --git a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php index a67c57e6f9..760317ae80 100644 --- a/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php +++ b/src/applications/config/controller/PhabricatorConfigDatabaseStatusController.php @@ -261,6 +261,7 @@ private function renderDatabase( $this->renderAttr( $table->getCollation(), $table->hasIssue($collation_issue)), + $table->getPersistenceTypeDisplayName(), ); } @@ -270,12 +271,14 @@ private function renderDatabase( null, pht('Table'), pht('Collation'), + pht('Persistence'), )) ->setColumnClasses( array( null, 'wide pri', null, + null, )); $title = $database_name; diff --git a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php index 6feda7363c..2cb7763110 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaQuery.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaQuery.php @@ -338,6 +338,8 @@ private function buildComparisonSchemaForServer( $comp_table->addKey($comp_key); } + $comp_table->setPersistenceType($expect_table->getPersistenceType()); + $comp_database->addTable($comp_table); } $comp_server->addDatabase($comp_database); diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index 971f5b828f..c451658682 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -56,36 +56,52 @@ protected function buildLiskObjectSchema(PhabricatorLiskDAO $object) { } protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) { + $index_options = array( + 'persistence' => PhabricatorConfigTableSchema::PERSISTENCE_INDEX, + ); + $this->buildRawSchema( $engine->getApplicationName(), $engine->getDocumentTableName(), $engine->getDocumentSchemaColumns(), - $engine->getDocumentSchemaKeys()); + $engine->getDocumentSchemaKeys(), + $index_options); $this->buildRawSchema( $engine->getApplicationName(), $engine->getFieldTableName(), $engine->getFieldSchemaColumns(), - $engine->getFieldSchemaKeys()); + $engine->getFieldSchemaKeys(), + $index_options); $this->buildRawSchema( $engine->getApplicationName(), $engine->getNgramsTableName(), $engine->getNgramsSchemaColumns(), - $engine->getNgramsSchemaKeys()); + $engine->getNgramsSchemaKeys(), + $index_options); $this->buildRawSchema( $engine->getApplicationName(), $engine->getCommonNgramsTableName(), $engine->getCommonNgramsSchemaColumns(), - $engine->getCommonNgramsSchemaKeys()); + $engine->getCommonNgramsSchemaKeys(), + $index_options); } protected function buildRawSchema( $database_name, $table_name, array $columns, - array $keys) { + array $keys, + array $options = array()) { + + PhutilTypeSpec::checkMap( + $options, + array( + 'persistence' => 'optional string', + )); + $database = $this->getDatabase($database_name); $table = $this->newTable($table_name); @@ -144,6 +160,11 @@ protected function buildRawSchema( $table->addKey($key); } + $persistence_type = idx($options, 'persistence'); + if ($persistence_type !== null) { + $table->setPersistenceType($persistence_type); + } + $database->addTable($table); } diff --git a/src/applications/config/schema/PhabricatorConfigTableSchema.php b/src/applications/config/schema/PhabricatorConfigTableSchema.php index 870c8ebddb..d6e5bfd9fa 100644 --- a/src/applications/config/schema/PhabricatorConfigTableSchema.php +++ b/src/applications/config/schema/PhabricatorConfigTableSchema.php @@ -7,6 +7,11 @@ final class PhabricatorConfigTableSchema private $engine; private $columns = array(); private $keys = array(); + private $persistenceType = self::PERSISTENCE_DATA; + + const PERSISTENCE_DATA = 'data'; + const PERSISTENCE_CACHE = 'cache'; + const PERSISTENCE_INDEX = 'index'; public function addColumn(PhabricatorConfigColumnSchema $column) { $key = $column->getName(); @@ -45,6 +50,27 @@ public function getKey($key) { return idx($this->getKeys(), $key); } + public function setPersistenceType($persistence_type) { + $this->persistenceType = $persistence_type; + return $this; + } + + public function getPersistenceType() { + return $this->persistenceType; + } + + public function getPersistenceTypeDisplayName() { + $map = array( + self::PERSISTENCE_DATA => pht('Data'), + self::PERSISTENCE_CACHE => pht('Cache'), + self::PERSISTENCE_INDEX => pht('Index'), + ); + + $type = $this->getPersistenceType(); + + return idx($map, $type, $type); + } + protected function getSubschemata() { // NOTE: Keys and columns may have the same name, so make sure we return // everything. diff --git a/src/applications/differential/storage/DifferentialSchemaSpec.php b/src/applications/differential/storage/DifferentialSchemaSpec.php index e376c7f4f8..7aa76fa822 100644 --- a/src/applications/differential/storage/DifferentialSchemaSpec.php +++ b/src/applications/differential/storage/DifferentialSchemaSpec.php @@ -21,6 +21,9 @@ public function buildSchemata() { 'dateCreated' => array( 'columns' => array('dateCreated'), ), + ), + array( + 'persistence' => PhabricatorConfigTableSchema::PERSISTENCE_CACHE, )); $this->buildRawSchema( diff --git a/src/docs/user/configuration/configuring_backups.diviner b/src/docs/user/configuration/configuring_backups.diviner index 9776448216..d32eebb0da 100644 --- a/src/docs/user/configuration/configuring_backups.diviner +++ b/src/docs/user/configuration/configuring_backups.diviner @@ -145,6 +145,24 @@ present a risk. If you restrict access to the Phabricator host or database, you should also restrict access to the backups. +Skipping Indexes +================ + +By default, `bin/storage dump` does not dump all of the data in the database: +it skips some caches which can be rebuilt automatically and do not need to be +backed up. Some of these caches are very large, so the size of the dump may +be significantly smaller than the size of the databases. + +If you have a large amount of data, you can specify `--no-indexes` when taking +a database dump to skip additional tables which contain search indexes. This +will reduce the size (and increase the speed) of the backup. This is an +advanced option which most installs will not benefit from. + +This index data can be rebuilt after a restore, but will not be rebuilt +automatically. If you choose to use this flag, you must manually rebuild +indexes after a restore (for details, see ((reindex))). + + Next Steps ========== diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index f491133c67..fecc0517e3 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -30,6 +30,13 @@ protected function didConstruct() { 'With __--output__, write a compressed file to disk instead '. 'of a plaintext file.'), ), + array( + 'name' => 'no-indexes', + 'help' => pht( + 'Do not dump data in rebuildable index tables. This means '. + 'backups are smaller and faster, but you will need to manually '. + 'rebuild indexes after performing a restore.'), + ), array( 'name' => 'overwrite', 'help' => pht( @@ -49,6 +56,8 @@ public function didExecute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); + $with_indexes = !$args->getArg('no-indexes'); + $applied = $api->getAppliedPatches(); if ($applied === null) { $namespace = $api->getNamespace(); @@ -65,18 +74,58 @@ public function didExecute(PhutilArgumentParser $args) { $ref = $api->getRef(); $ref_key = $ref->getRefKey(); - $schemata_map = id(new PhabricatorConfigSchemaQuery()) + $schemata_query = id(new PhabricatorConfigSchemaQuery()) ->setAPIs(array($api)) - ->setRefs(array($ref)) - ->loadActualSchemata(); - $schemata = $schemata_map[$ref_key]; + ->setRefs(array($ref)); + + $actual_map = $schemata_query->loadActualSchemata(); + $expect_map = $schemata_query->loadExpectedSchemata(); + + $schemata = $actual_map[$ref_key]; + $expect = $expect_map[$ref_key]; $targets = array(); foreach ($schemata->getDatabases() as $database_name => $database) { + $expect_database = $expect->getDatabase($database_name); foreach ($database->getTables() as $table_name => $table) { + + // NOTE: It's possible for us to find tables in these database which + // we don't expect to be there. For example, an older version of + // Phabricator may have had a table that was later dropped. We assume + // these are data tables and always dump them, erring on the side of + // caution. + + $persistence = PhabricatorConfigTableSchema::PERSISTENCE_DATA; + if ($expect_database) { + $expect_table = $expect_database->getTable($table_name); + if ($expect_table) { + $persistence = $expect_table->getPersistenceType(); + } + } + + switch ($persistence) { + case PhabricatorConfigTableSchema::PERSISTENCE_CACHE: + // When dumping tables, leave the data in cache tables in the + // database. This will be automatically rebuild after the data + // is restored and does not need to be persisted in backups. + $with_data = false; + break; + case PhabricatorConfigTableSchema::PERSISTENCE_INDEX: + // When dumping tables, leave index data behind of the caller + // specified "--no-indexes". These tables can be rebuilt manually + // from other tables, but do not rebuild automatically. + $with_data = $with_indexes; + break; + case PhabricatorConfigTableSchema::PERSISTENCE_DATA: + default: + $with_data = true; + break; + } + $targets[] = array( 'database' => $database_name, 'table' => $table_name, + 'data' => $with_data, ); } } @@ -147,6 +196,10 @@ public function didExecute(PhutilArgumentParser $args) { foreach ($targets as $target) { $target_argv = $argv; + if (!$target['data']) { + $target_argv[] = '--no-data'; + } + if ($has_password) { $commands[] = csprintf( 'mysqldump -p%P %Ls -- %R %R', From 66df5b149309ae261e335022429c458aac323570 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 Oct 2017 17:19:27 -0700 Subject: [PATCH 214/865] Add a garbage collector for common ngrams Summary: Ref T13000. After an ngram is marked as "common", we can delete it from the storage table. Currently, the only way to get ngrams marked as "common" is to manually run `bin/search ngrams`, so this has no impact on normal installs. Test Plan: Ran `bin/garbage collect`, saw it start chewing through my local Maniphest ngrams table and removing common ngrams. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18687 --- src/__phutil_library_map__.php | 2 + ...catorSearchFerretNgramGarbageCollector.php | 55 +++++++++++++++++++ ...bricatorSearchManagementNgramsWorkflow.php | 5 +- 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/applications/search/garbagecollector/PhabricatorSearchFerretNgramGarbageCollector.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index dd2bcd8dfe..aca6e09fdb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3941,6 +3941,7 @@ 'PhabricatorSearchEngineAttachment' => 'applications/search/engineextension/PhabricatorSearchEngineAttachment.php', 'PhabricatorSearchEngineExtension' => 'applications/search/engineextension/PhabricatorSearchEngineExtension.php', 'PhabricatorSearchEngineExtensionModule' => 'applications/search/engineextension/PhabricatorSearchEngineExtensionModule.php', + 'PhabricatorSearchFerretNgramGarbageCollector' => 'applications/search/garbagecollector/PhabricatorSearchFerretNgramGarbageCollector.php', 'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php', 'PhabricatorSearchHost' => 'infrastructure/cluster/search/PhabricatorSearchHost.php', 'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php', @@ -9522,6 +9523,7 @@ 'PhabricatorSearchEngineAttachment' => 'Phobject', 'PhabricatorSearchEngineExtension' => 'Phobject', 'PhabricatorSearchEngineExtensionModule' => 'PhabricatorConfigModule', + 'PhabricatorSearchFerretNgramGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorSearchField' => 'Phobject', 'PhabricatorSearchHost' => 'Phobject', 'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController', diff --git a/src/applications/search/garbagecollector/PhabricatorSearchFerretNgramGarbageCollector.php b/src/applications/search/garbagecollector/PhabricatorSearchFerretNgramGarbageCollector.php new file mode 100644 index 0000000000..f2c43743f6 --- /dev/null +++ b/src/applications/search/garbagecollector/PhabricatorSearchFerretNgramGarbageCollector.php @@ -0,0 +1,55 @@ +setAncestorClass('PhabricatorFerretInterface') + ->execute(); + + $did_collect = false; + foreach ($all_objects as $object) { + $engine = $object->newFerretEngine(); + $conn = $object->establishConnection('w'); + + $ngram_row = queryfx_one( + $conn, + 'SELECT ngram FROM %T WHERE needsCollection = 1 LIMIT 1', + $engine->getCommonNgramsTableName()); + if (!$ngram_row) { + continue; + } + + $ngram = $ngram_row['ngram']; + + queryfx( + $conn, + 'DELETE FROM %T WHERE ngram = %s', + $engine->getNgramsTableName(), + $ngram); + + queryfx( + $conn, + 'UPDATE %T SET needsCollection = 0 WHERE ngram = %s', + $engine->getCommonNgramsTableName(), + $ngram); + + $did_collect = true; + break; + } + + return $did_collect; + } + +} diff --git a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php index d1a6e0bdff..4b983fe0f9 100644 --- a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php @@ -6,7 +6,10 @@ final class PhabricatorSearchManagementNgramsWorkflow protected function didConstruct() { $this ->setName('ngrams') - ->setSynopsis(pht('Recompute common ngrams.')) + ->setSynopsis( + pht( + 'Recompute common ngrams. This is an advanced workflow that '. + 'can harm search quality if used improperly.')) ->setArguments( array( array( From 17e83b53d53e2f1b08021993e143db891a51b26f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 6 Oct 2017 07:48:25 -0700 Subject: [PATCH 215/865] Add "bin/search query" for debugging query execution Summary: Ref T13000. Currently, queries can only be executed from the web UI, which requires logging in as a user. I really want to avoid doing that wherever we can, but being able to execute queries on an instance (and, particularly, see the ngrams and timings on the underlying lookups) would have been helpful in several cases. Improve tooling a bit in advance of the "common ngrams" stuff going out since it seems likely that it will be useful if issues arise. Test Plan: Ran `bin/search query --query ...`, got useful minimal output. Ran with `--trace` to get internals. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18690 --- src/__phutil_library_map__.php | 2 + ...abricatorSearchManagementQueryWorkflow.php | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/applications/search/management/PhabricatorSearchManagementQueryWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index aca6e09fdb..a650e33fcf 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3950,6 +3950,7 @@ 'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php', 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', 'PhabricatorSearchManagementNgramsWorkflow' => 'applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php', + 'PhabricatorSearchManagementQueryWorkflow' => 'applications/search/management/PhabricatorSearchManagementQueryWorkflow.php', 'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php', 'PhabricatorSearchNgrams' => 'applications/search/ngrams/PhabricatorSearchNgrams.php', 'PhabricatorSearchNgramsDestructionEngineExtension' => 'applications/search/engineextension/PhabricatorSearchNgramsDestructionEngineExtension.php', @@ -9532,6 +9533,7 @@ 'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementNgramsWorkflow' => 'PhabricatorSearchManagementWorkflow', + 'PhabricatorSearchManagementQueryWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSearchNgrams' => 'PhabricatorSearchDAO', 'PhabricatorSearchNgramsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', diff --git a/src/applications/search/management/PhabricatorSearchManagementQueryWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementQueryWorkflow.php new file mode 100644 index 0000000000..8e40162a98 --- /dev/null +++ b/src/applications/search/management/PhabricatorSearchManagementQueryWorkflow.php @@ -0,0 +1,55 @@ +setName('query') + ->setSynopsis( + pht('Run a search query. Intended for debugging and development.')) + ->setArguments( + array( + array( + 'name' => 'query', + 'param' => 'query', + 'help' => pht('Raw query to execute.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + $raw_query = $args->getArg('query'); + if (!strlen($raw_query)) { + throw new PhutilArgumentUsageException( + pht('Specify a query with --query.')); + } + + $engine = id(new PhabricatorSearchApplicationSearchEngine()) + ->setViewer($viewer); + + $saved = $engine->newSavedQuery(); + $saved->setParameter('query', $raw_query); + + $query = $engine->buildQueryFromSavedQuery($saved); + $pager = $engine->newPagerForSavedQuery($saved); + + $results = $engine->executeQuery($query, $pager); + if ($results) { + foreach ($results as $result) { + echo tsprintf( + "%s\t%s\n", + $result->getPHID(), + $result->getName()); + } + } else { + echo tsprintf( + "%s\n", + pht('No results.')); + } + + return 0; + } + +} From 85011a46d0aa1a5da241cb91620d929adc6414a9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 6 Oct 2017 11:47:35 -0700 Subject: [PATCH 216/865] Bail out of PhabricatorRepositoryGraphCache more aggressively after cache fills Summary: Ref PHI109. Ref T11786. We currently test elapsed time every 64 iterations (since iterations are normally very fast), but at least one install is seeing the page timeout after 30 seconds. One reason could be that cache fills may occur, and are likely to be much slower than normal iterations. In an extreme case, we could do 64 cache fills before checking the time. Tweak thing so that we always check the time after doing a cache fill, regardless of how many iterations have elapsed since the last attempt. Additionally, this API method currently accepts an arbitrary number of paths, but implicitly limits each cache query to 500ms. If more than 60 paths are passed, this may exceed 30s. Only let the cache churn for a maximum of 10s across all paths. If this is more the latter issue than the former, this might replace the GraphCache timeouts with `git` timeouts, but at least our understanding of what's going on here will improve. Test Plan: This is difficult to test convincingly locally, since I can't reproduce the original issue. It still works after these changes, but it worked fine before these changes too. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T11786 Differential Revision: https://secure.phabricator.com/D18692 --- ...ffusionLastModifiedQueryConduitAPIMethod.php | 14 +++++++++++++- .../PhabricatorRepositoryGraphCache.php | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php index 30b436cfff..1135420a6e 100644 --- a/src/applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php @@ -115,6 +115,10 @@ private function loadCommitsFromCache(array $map) { $graph_cache = new PhabricatorRepositoryGraphCache(); $results = array(); + + // Spend no more than this many total seconds trying to satisfy queries + // via the graph cache. + $remaining_time = 10.0; foreach ($map as $path => $commit) { $path_id = idx($path_map, $path); if (!$path_id) { @@ -125,13 +129,21 @@ private function loadCommitsFromCache(array $map) { continue; } + $t_start = microtime(true); $cache_result = $graph_cache->loadLastModifiedCommitID( $commit_id, - $path_id); + $path_id, + $remaining_time); + $t_end = microtime(true); if ($cache_result !== false) { $results[$path] = $cache_result; } + + $remaining_time -= ($t_end - $t_start); + if ($remaining_time <= 0) { + break; + } } if ($results) { diff --git a/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php b/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php index d52182fcc9..c4adc61ab0 100644 --- a/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php +++ b/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php @@ -102,6 +102,10 @@ public function loadLastModifiedCommitID($commit_id, $path_id, $time = 0.5) { } // Otherwise, the rebuild gave us the data, so we can keep going. + + $did_fill = true; + } else { + $did_fill = false; } // Sanity check so we can survive and recover from bad data. @@ -147,12 +151,17 @@ public function loadLastModifiedCommitID($commit_id, $path_id, $time = 0.5) { $commit_id = $parent_id; // Periodically check if we've spent too long looking for a result - // in the cache, and return so we can fall back to a VCS operation. This - // keeps us from having a degenerate worst case if, e.g., the cache - // is cold and we need to inspect a very large number of blocks + // in the cache, and return so we can fall back to a VCS operation. + // This keeps us from having a degenerate worst case if, e.g., the + // cache is cold and we need to inspect a very large number of blocks // to satisfy the query. - if (((++$iterations) % 64) === 0) { + ++$iterations; + + // If we performed a cache fill in this cycle, always check the time + // limit, since cache fills may take a significant amount of time. + + if ($did_fill || ($iterations % 64 === 0)) { $t_end = microtime(true); if (($t_end - $t_start) > $time) { return false; From 1ee7b3ab8cbb49613339b0f8b02b9f91a069a102 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 7 Oct 2017 04:59:29 -0700 Subject: [PATCH 217/865] Correct "bin/storage dump" command construction with passwords Fixes T13004. This should mirror the other branch. --- .../workflow/PhabricatorStorageManagementDumpWorkflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index fecc0517e3..dced62eee9 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -201,7 +201,7 @@ public function didExecute(PhutilArgumentParser $args) { } if ($has_password) { - $commands[] = csprintf( + $command = csprintf( 'mysqldump -p%P %Ls -- %R %R', $password, $target_argv, From 0361591da0d9ad18095660be6309c2c51129570c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 7 Oct 2017 04:59:29 -0700 Subject: [PATCH 218/865] (stable) Correct "bin/storage dump" command construction with passwords Fixes T13004. This should mirror the other branch. --- .../workflow/PhabricatorStorageManagementDumpWorkflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index fecc0517e3..dced62eee9 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -201,7 +201,7 @@ public function didExecute(PhutilArgumentParser $args) { } if ($has_password) { - $commands[] = csprintf( + $command = csprintf( 'mysqldump -p%P %Ls -- %R %R', $password, $target_argv, From 4fd9d2d4bbdb8fb7013b5bea5e0a5ec121db526f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 7 Oct 2017 13:23:18 -0700 Subject: [PATCH 219/865] Fix "bin/storage dump" with no "--output" Ref T13004. (I distinctly remember testing this, but must have tweaked things afterward.) --- .../workflow/PhabricatorStorageManagementDumpWorkflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index dced62eee9..17491d55e9 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -237,7 +237,7 @@ public function didExecute(PhutilArgumentParser $args) { $file = fopen($output_file, 'wb'); } - if (!$file) { + if (($output_file !== null) && !$file) { throw new Exception( pht( 'Failed to open file "%s" for writing.', From 8edd04aff365927a7b0018e2b20076518e4e6a0e Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 7 Oct 2017 13:23:18 -0700 Subject: [PATCH 220/865] (stable) Fix "bin/storage dump" with no "--output" Ref T13004. (I distinctly remember testing this, but must have tweaked things afterward.) --- .../workflow/PhabricatorStorageManagementDumpWorkflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index dced62eee9..17491d55e9 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -237,7 +237,7 @@ public function didExecute(PhutilArgumentParser $args) { $file = fopen($output_file, 'wb'); } - if (!$file) { + if (($output_file !== null) && !$file) { throw new Exception( pht( 'Failed to open file "%s" for writing.', From 9bd6a3705559b16c75595a06d9d726274bd5be6d Mon Sep 17 00:00:00 2001 From: Dmitri Iouchtchenko Date: Mon, 9 Oct 2017 10:48:01 -0700 Subject: [PATCH 221/865] Fix spelling Summary: Noticed a couple of typos in the docs, and then things got out of hand. Test Plan: - Stared at the words until my eyes watered and the letters began to swim on the screen. - Consulted a dictionary. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley, yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18693 --- scripts/setup/manage_garbage.php | 2 +- .../httpparametertype/AphrontHTTPParameterType.php | 2 +- .../almanac/editor/AlmanacDeviceEditor.php | 2 +- .../audit/view/PhabricatorAuditListView.php | 2 +- .../auth/controller/PhabricatorAuthController.php | 2 +- .../controller/PhabricatorAuthRegisterController.php | 2 +- .../auth/editor/PhabricatorAuthSSHKeyEditor.php | 2 +- .../factor/__tests__/PhabricatorAuthInviteTestCase.php | 2 +- .../PhabricatorAuthManagementRevokeWorkflow.php | 2 +- .../auth/provider/PhabricatorOAuth1AuthProvider.php | 2 +- src/applications/base/PhabricatorApplication.php | 2 +- .../base/controller/PhabricatorController.php | 2 +- .../editor/PhabricatorCalendarImportEditEngine.php | 2 +- .../calendar/icon/PhabricatorCalendarIconSet.php | 2 +- src/applications/calendar/util/CalendarTimeUtil.php | 2 +- .../PhabricatorCalendarEventFrequencyTransaction.php | 4 ++-- .../PhabricatorCalendarEventInviteTransaction.php | 4 ++-- .../PhabricatorCalendarImportFrequencyTransaction.php | 2 +- .../config/check/PhabricatorStorageSetupCheck.php | 2 +- .../conpherence/query/ConpherenceThreadQuery.php | 2 +- .../PhabricatorDashboardPanelTabsCustomField.php | 2 +- .../differential/controller/DifferentialController.php | 2 +- .../customfield/DifferentialHarbormasterField.php | 2 +- .../differential/editor/DifferentialDiffEditor.php | 2 +- .../parser/DifferentialChangesetParser.php | 6 +++--- .../differential/storage/DifferentialModernHunk.php | 2 +- .../differential/storage/DifferentialRevision.php | 4 ++-- ...DifferentialResponsibleViewerFunctionDatasource.php | 2 +- .../diffusion/controller/DiffusionCommitController.php | 2 +- .../diffusion/editor/DiffusionRepositoryEditEngine.php | 6 +++--- .../diffusion/engine/DiffusionCommitHookEngine.php | 2 +- .../gitlfs/DiffusionGitLFSAuthenticateWorkflow.php | 2 +- .../diffusion/herald/HeraldCommitAdapter.php | 2 +- .../diffusion/protocol/DiffusionCommandEngine.php | 2 +- .../protocol/DiffusionMercurialWireProtocol.php | 2 +- .../protocol/DiffusionRepositoryClusterEngine.php | 4 ++-- .../query/DiffusionCachedResolveRefsQuery.php | 2 +- .../diffusion/query/DiffusionCommitQuery.php | 2 +- .../xaction/DiffusionCommitConcernTransaction.php | 2 +- .../xaction/DiffusionCommitVerifyTransaction.php | 2 +- .../blueprint/DrydockBlueprintImplementation.php | 4 ++-- .../drydock/storage/DrydockAuthorization.php | 2 +- .../drydock/worker/DrydockLeaseUpdateWorker.php | 6 +++--- src/applications/drydock/worker/DrydockWorker.php | 2 +- .../files/controller/PhabricatorFileDataController.php | 2 +- .../PhabricatorFilesManagementCatWorkflow.php | 2 +- .../fund/editor/FundInitiativeEditEngine.php | 2 +- .../HarbormasterSendMessageConduitAPIMethod.php | 4 ++-- .../storage/configuration/HarbormasterBuildStep.php | 2 +- .../controller/LegalpadDocumentSignController.php | 2 +- .../maniphest/controller/ManiphestReportController.php | 4 ++-- .../maniphest/editor/ManiphestTransactionEditor.php | 2 +- .../PhabricatorMailManagementUnverifyWorkflow.php | 2 +- .../metamta/receiver/PhabricatorMailReceiver.php | 2 +- .../__tests__/PhabricatorOwnersPackageTestCase.php | 2 +- .../credentialtype/PassphraseCredentialType.php | 2 +- .../PassphraseSSHPrivateKeyTextCredentialType.php | 2 +- .../passphrase/view/PassphraseCredentialControl.php | 2 +- .../people/markup/PhabricatorMentionRemarkupRule.php | 2 +- .../people/query/PhabricatorPeopleQuery.php | 4 ++-- .../people/query/PhabricatorPeopleSearchEngine.php | 2 +- src/applications/people/storage/PhabricatorUser.php | 6 +++--- .../phid/handle/pool/PhabricatorHandleList.php | 2 +- .../phid/resolver/PhabricatorPHIDResolver.php | 2 +- .../provider/PhortuneStripePaymentProvider.php | 2 +- .../phrequent/storage/PhrequentTimeBlock.php | 2 +- .../phriction/editor/PhrictionTransactionEditor.php | 2 +- .../policy/filter/PhabricatorPolicyFilter.php | 2 +- .../project/query/PhabricatorProjectColumnQuery.php | 2 +- .../project/remarkup/ProjectRemarkupRule.php | 2 +- .../DifferentialReleephRequestFieldSpecification.php | 2 +- .../field/selector/ReleephDefaultFieldSelector.php | 2 +- .../daemon/PhabricatorMercurialGraphStream.php | 2 +- .../data/PhabricatorRepositoryURINormalizer.php | 4 ++-- .../engine/PhabricatorRepositoryDiscoveryEngine.php | 2 +- .../PhabricatorRepositoryManagementThawWorkflow.php | 2 +- .../repository/storage/PhabricatorRepository.php | 4 ++-- .../repository/storage/PhabricatorRepositoryURI.php | 2 +- .../PhabricatorRepositoryWorkingCopyVersion.php | 2 +- ...atorRepositoryMercurialCommitChangeParserWorker.php | 2 +- .../engine/PhabricatorApplicationSearchEngine.php | 2 +- .../management/PhabricatorSearchManagementWorkflow.php | 2 +- .../menuitem/PhabricatorMotivatorProfileMenuItem.php | 4 ++-- .../settings/panel/PhabricatorSettingsPanel.php | 2 +- .../settings/query/PhabricatorUserPreferencesQuery.php | 2 +- .../settings/setting/PhabricatorTimezoneSetting.php | 2 +- .../PhabricatorSubscriptionsEditController.php | 2 +- .../transactions/editengine/PhabricatorEditEngine.php | 4 ++-- .../editor/PhabricatorApplicationTransactionEditor.php | 2 +- .../datasource/PhabricatorTypeaheadDatasource.php | 2 +- .../uiexample/examples/PhabricatorStatusUIExample.php | 2 +- src/docs/contributor/cla.diviner | 6 +++--- src/docs/contributor/describing_problems.diviner | 2 +- src/docs/contributor/internationalization.diviner | 2 +- src/docs/contributor/reproduction_steps.diviner | 4 ++-- src/docs/user/cluster/cluster.diviner | 2 +- src/docs/user/cluster/cluster_databases.diviner | 2 +- src/docs/user/cluster/cluster_devices.diviner | 2 +- src/docs/user/cluster/cluster_partitioning.diviner | 6 +++--- src/docs/user/cluster/cluster_repositories.diviner | 2 +- src/docs/user/cluster/cluster_search.diviner | 4 ++-- .../user/configuration/configuration_guide.diviner | 4 ++-- .../user/configuration/configuring_backups.diviner | 2 +- .../user/configuration/configuring_encryption.diviner | 4 ++-- src/docs/user/configuration/managing_garbage.diviner | 2 +- src/docs/user/field/conduit_changes.diviner | 2 +- src/docs/user/field/darkconsole.diviner | 2 +- src/docs/user/field/performance.diviner | 4 ++-- src/docs/user/field/repository_imports.diviner | 8 ++++---- src/docs/user/field/xhprof.diviner | 2 +- src/docs/user/userguide/almanac.diviner | 2 +- src/docs/user/userguide/arcanist.diviner | 2 +- src/docs/user/userguide/arcanist_lint.diviner | 2 +- src/docs/user/userguide/calendar_exports.diviner | 2 +- src/docs/user/userguide/calendar_imports.diviner | 2 +- src/docs/user/userguide/conduit_edit.diviner | 2 +- src/docs/user/userguide/diffusion_api.diviner | 2 +- src/docs/user/userguide/diffusion_hosting.diviner | 2 +- src/docs/user/userguide/diffusion_managing.diviner | 10 +++++----- src/docs/user/userguide/diffusion_uris.diviner | 4 ++-- src/docs/user/userguide/drydock.diviner | 2 +- src/docs/user/userguide/drydock_blueprints.diviner | 2 +- src/docs/user/userguide/drydock_security.diviner | 2 +- src/docs/user/userguide/forms.diviner | 2 +- src/docs/user/userguide/herald.diviner | 2 +- src/docs/user/userguide/jump.diviner | 2 +- src/docs/user/userguide/owners.diviner | 2 +- src/docs/user/userguide/projects.diviner | 2 +- src/docs/user/userguide/remarkup.diviner | 2 +- src/docs/user/userguide/search.diviner | 2 +- src/docs/user/userguide/spaces.diviner | 2 +- src/docs/user/userguide/users.diviner | 2 +- .../customfield/field/PhabricatorCustomFieldList.php | 2 +- .../workers/query/PhabricatorWorkerTriggerQuery.php | 4 ++-- src/infrastructure/diff/view/PHUIDiffGraphView.php | 2 +- .../diff/view/PHUIDiffInlineCommentTableScaffold.php | 2 +- src/infrastructure/env/PhabricatorEnv.php | 2 +- .../translation/PhabricatorUSEnglishTranslation.php | 6 +++--- .../policy/PhabricatorCursorPagedPolicyAwareQuery.php | 4 ++-- src/infrastructure/storage/lisk/LiskDAO.php | 2 +- .../storage/lisk/__tests__/LiskFixtureTestCase.php | 2 +- .../workflow/PhabricatorStorageManagementWorkflow.php | 4 ++-- src/infrastructure/util/PhabricatorHash.php | 2 +- .../util/password/PhabricatorPasswordHasher.php | 8 ++++---- src/view/AphrontView.php | 2 +- src/view/page/PhabricatorStandardPageView.php | 2 +- src/view/phui/PHUIHeaderView.php | 2 +- webroot/rsrc/css/phui/phui-document.css | 4 ++-- webroot/rsrc/css/phui/phui-header-view.css | 2 +- .../rsrc/js/application/conpherence/behavior-menu.js | 2 +- .../conpherence/behavior-participant-pane.js | 2 +- .../application/diffusion/DiffusionLocateFileSource.js | 2 +- .../js/application/pholio/behavior-pholio-mock-view.js | 2 +- webroot/rsrc/js/core/Prefab.js | 4 ++-- webroot/rsrc/js/core/behavior-device.js | 2 +- webroot/rsrc/js/phuix/PHUIXAutocomplete.js | 2 +- webroot/rsrc/js/phuix/PHUIXDropdownMenu.js | 2 +- 157 files changed, 204 insertions(+), 204 deletions(-) diff --git a/scripts/setup/manage_garbage.php b/scripts/setup/manage_garbage.php index ba727eab60..326730375e 100755 --- a/scripts/setup/manage_garbage.php +++ b/scripts/setup/manage_garbage.php @@ -5,7 +5,7 @@ require_once $root.'/scripts/__init_script__.php'; $args = new PhutilArgumentParser($argv); -$args->setTagline(pht('manage garbage colletors')); +$args->setTagline(pht('manage garbage collectors')); $args->setSynopsis(<<isImported()) { diff --git a/src/applications/auth/controller/PhabricatorAuthController.php b/src/applications/auth/controller/PhabricatorAuthController.php index c5b3c1ce99..0ed86e3056 100644 --- a/src/applications/auth/controller/PhabricatorAuthController.php +++ b/src/applications/auth/controller/PhabricatorAuthController.php @@ -134,7 +134,7 @@ protected function loadAccountForRegistrationOrLinking($account_key) { // checks later on to make sure this account is valid for the intended // operation. This requires edit permission for completeness and consistency // but it won't actually be meaningfully checked because we're using the - // ominpotent user. + // omnipotent user. $account = id(new PhabricatorExternalAccountQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php index a68e12c801..5a792dc864 100644 --- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php +++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php @@ -79,7 +79,7 @@ public function handleRequest(AphrontRequest $request) { } if ($default_email !== null) { - // We should bypass policy here becase e.g. limiting an application use + // We should bypass policy here because e.g. limiting an application use // to a subset of users should not allow the others to overwrite // configured application emails. $application_email = id(new PhabricatorMetaMTAApplicationEmailQuery()) diff --git a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php index 0962b7b56a..1c40af5795 100644 --- a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php @@ -226,7 +226,7 @@ protected function buildMailTemplate(PhabricatorLiskDAO $object) { ->addHeader('Thread-Topic', $phid); // The primary value of this mail is alerting users to account compromises, - // so force delivery. In particular, this mail should still be delievered + // so force delivery. In particular, this mail should still be delivered // even if "self mail" is disabled. $mail->setForceDelivery(true); diff --git a/src/applications/auth/factor/__tests__/PhabricatorAuthInviteTestCase.php b/src/applications/auth/factor/__tests__/PhabricatorAuthInviteTestCase.php index 3906c5eabe..ce9151bcef 100644 --- a/src/applications/auth/factor/__tests__/PhabricatorAuthInviteTestCase.php +++ b/src/applications/auth/factor/__tests__/PhabricatorAuthInviteTestCase.php @@ -51,7 +51,7 @@ public function testDuplicateInvite() { $caught = $ex; } - // This first time should accept the invite and verify the addresss. + // This first time should accept the invite and verify the address. $this->assertTrue( ($caught instanceof PhabricatorAuthInviteRegisteredException)); diff --git a/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php index bb94cfca62..758761b2ca 100644 --- a/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php +++ b/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php @@ -74,7 +74,7 @@ public function execute(PhutilArgumentParser $args) { if (!strlen($from) && !$is_everywhere) { throw new PhutilArgumentUsageException( pht( - 'Specify the target to revoke credentals from with "--from" or '. + 'Specify the target to revoke credentials from with "--from" or '. 'specify "--everywhere".')); } else if (strlen($from) && $is_everywhere) { throw new PhutilArgumentUsageException( diff --git a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php index 530bf30583..c62983de2f 100644 --- a/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php +++ b/src/applications/auth/provider/PhabricatorOAuth1AuthProvider.php @@ -270,7 +270,7 @@ private function getTemporaryTokenType($core_type) { } private function getHandshakeTokenKeyFromClientCode($client_code) { - // NOTE: This is very slightly coersive since the TemporaryToken table + // NOTE: This is very slightly coercive since the TemporaryToken table // expects an "objectPHID" as an identifier, but nothing about the storage // is bound to PHIDs. diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 72b577771f..c25612408c 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -108,7 +108,7 @@ public function isUnlisted() { * * Launchable applications can be pinned to the home page, and show up in the * "Launcher" view of the Applications application. Making an application - * unlauncahble prevents pinning and hides it from this view. + * unlaunchable prevents pinning and hides it from this view. * * Usually, an application should be marked unlaunchable if: * diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 1ecfdaf557..f923eb7b73 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -552,7 +552,7 @@ protected function buildTransactionTimeline( public function buildApplicationCrumbsForEditEngine() { - // TODO: This is kind of gross, I'm bascially just making this public so + // TODO: This is kind of gross, I'm basically just making this public so // I can use it in EditEngine. We could do this without making it public // by using controller delegation, or make it properly public. return $this->buildApplicationCrumbs(); diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php index c3dc29bb51..90b4962ca9 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php @@ -134,7 +134,7 @@ protected function buildCustomEditFields($object) { $fields[] = id(new PhabricatorSelectEditField()) ->setKey('frequency') ->setLabel(pht('Update Automatically')) - ->setDescription(pht('Configure an automatic update frequncy.')) + ->setDescription(pht('Configure an automatic update frequency.')) ->setTransactionType( PhabricatorCalendarImportFrequencyTransaction::TRANSACTIONTYPE) ->setConduitDescription(pht('Set the automatic update frequency.')) diff --git a/src/applications/calendar/icon/PhabricatorCalendarIconSet.php b/src/applications/calendar/icon/PhabricatorCalendarIconSet.php index 22d5b0e4f5..ab6d2126ed 100644 --- a/src/applications/calendar/icon/PhabricatorCalendarIconSet.php +++ b/src/applications/calendar/icon/PhabricatorCalendarIconSet.php @@ -16,7 +16,7 @@ protected function newIcons() { 'fa-plane' => pht('Travel'), 'fa-plus-square' => pht('Health / Appointment'), - 'fa-rocket' => pht('Sabatical / Leave'), + 'fa-rocket' => pht('Sabbatical / Leave'), 'fa-home' => pht('Working From Home'), 'fa-tree' => pht('Holiday'), 'fa-gamepad' => pht('Staycation'), diff --git a/src/applications/calendar/util/CalendarTimeUtil.php b/src/applications/calendar/util/CalendarTimeUtil.php index 781f6adf7a..d3276654e4 100644 --- a/src/applications/calendar/util/CalendarTimeUtil.php +++ b/src/applications/calendar/util/CalendarTimeUtil.php @@ -8,7 +8,7 @@ * calendar views - one for the conpherence calendar widget and one for the * user profile calendar view. These have slight differences such as * conpherence showing both a three day "today 'til 2 days from now" *and* - * a Sunday -> Saturday list, whilest the profile view shows a more simple + * a Sunday -> Saturday list, whilst the profile view shows a more simple * seven day rolling list of events. */ final class CalendarTimeUtil extends Phobject { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php index 6fb8ae8f31..69f2e9e8a6 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php @@ -23,7 +23,7 @@ public function applyInternalEffects($object, $value) { // month, it means "the 30th of every month" as far as the RRULE is // concerned. Such an event will not occur on months with fewer days. - // This is surprising, and proably not what the user wants. Instead, + // This is surprising, and probably not what the user wants. Instead, // schedule these events relative to the end of the month: on the "-1st", // "-2nd" or "-3rd" day of the month. For example, a monthly event on // the 31st of a 31-day month translates to "every month, on the last @@ -66,7 +66,7 @@ public function validateTransactions($object, array $xactions) { if (!isset($valid[$value])) { $errors[] = $this->newInvalidError( pht( - 'Event frequency "%s" is not valid. Valid frequences are: %s.', + 'Event frequency "%s" is not valid. Valid frequencies are: %s.', $value, implode(', ', $valid)), $xaction); diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php index f1da2c1d77..841a066320 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarEventInviteTransaction.php @@ -132,7 +132,7 @@ public function getTitle() { $this->renderHandleList($rem)); } else { return pht( - '%s invited %s attendee(s): %s; uninvinted %s attendee(s): %s.', + '%s invited %s attendee(s): %s; uninvited %s attendee(s): %s.', $this->renderAuthor(), phutil_count($add), $this->renderHandleList($add), @@ -161,7 +161,7 @@ public function getTitleForFeed() { } else { return pht( '%s updated the invite list for %s, invited %s: %s; '. - 'uninvinted %s: %s.', + 'uninvited %s: %s.', $this->renderAuthor(), $this->renderObject(), phutil_count($add), diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php index 177adbbf0b..9fc68a3497 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php @@ -32,7 +32,7 @@ public function validateTransactions($object, array $xactions) { if (!isset($valid[$value])) { $errors[] = $this->newInvalidError( pht( - 'Import frequency "%s" is not valid. Valid frequences are: %s.', + 'Import frequency "%s" is not valid. Valid frequencies are: %s.', $value, implode(', ', $valid)), $xaction); diff --git a/src/applications/config/check/PhabricatorStorageSetupCheck.php b/src/applications/config/check/PhabricatorStorageSetupCheck.php index 09cecfe6d0..cc74cce2ea 100644 --- a/src/applications/config/check/PhabricatorStorageSetupCheck.php +++ b/src/applications/config/check/PhabricatorStorageSetupCheck.php @@ -85,7 +85,7 @@ protected function executeChecks() { 'be buffered into memory before being written to permanent '. 'storage. Phabricator needs memory available to store these '. 'chunks while they are uploaded, but PHP is currently configured '. - 'to severly limit the available memory.'. + 'to severely limit the available memory.'. "\n\n". 'PHP processes currently have very little free memory available '. '(%s). To work well, processes should have at least %s.'. diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 1bb96537dc..5cd6489d65 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -290,7 +290,7 @@ private function loadTransactionsAndHandles(array $conpherences) { ->withObjectPHIDs(array_keys($conpherences)) ->needHandles(true); - // We have to flip these for the underyling query class. The semantics of + // We have to flip these for the underlying query class. The semantics of // paging are tricky business. if ($this->afterTransactionID) { $query->setBeforeID($this->afterTransactionID); diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php index 96566db54a..07bbb67ae2 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php +++ b/src/applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php @@ -78,7 +78,7 @@ public function getApplicationTransactionTitle( public function renderEditControl(array $handles) { // NOTE: This includes archived panels so we don't mutate the tabs - // when saving a tab panel that includes archied panels. This whole UI is + // when saving a tab panel that includes archived panels. This whole UI is // hopefully temporary anyway. $value = $this->getFieldValue(); diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index fb8a6249a7..1860a07745 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -217,7 +217,7 @@ protected function loadHarbormasterData(array $diffs) { // by default and let the user toggle the rest. With modern messages, // we can send the user to the Harbormaster detail page. Just show // "a lot" of messages in legacy cases to try to strike a balance - // between implementation simplicitly and compatibility. + // between implementation simplicity and compatibility. $legacy_messages = array_slice($legacy_messages, 0, 100); $messages = array(); diff --git a/src/applications/differential/customfield/DifferentialHarbormasterField.php b/src/applications/differential/customfield/DifferentialHarbormasterField.php index c9b573bda2..cc13be3aa7 100644 --- a/src/applications/differential/customfield/DifferentialHarbormasterField.php +++ b/src/applications/differential/customfield/DifferentialHarbormasterField.php @@ -45,7 +45,7 @@ public function renderDiffPropertyViewValue(DifferentialDiff $diff) { // by default and let the user toggle the rest. With modern messages, // we can send the user to the Harbormaster detail page. Just show // "a lot" of messages in legacy cases to try to strike a balance - // between implementation simplicitly and compatibility. + // between implementation simplicity and compatibility. $legacy_messages = array_slice($legacy_messages, 0, 100); foreach ($legacy_messages as $message) { diff --git a/src/applications/differential/editor/DifferentialDiffEditor.php b/src/applications/differential/editor/DifferentialDiffEditor.php index 0058485e63..261f6f1598 100644 --- a/src/applications/differential/editor/DifferentialDiffEditor.php +++ b/src/applications/differential/editor/DifferentialDiffEditor.php @@ -106,7 +106,7 @@ protected function applyFinalEffects( * We run Herald as part of transaction validation because Herald can * block diff creation for Differential diffs. Its important to do this * separately so no Herald logs are saved; these logs could expose - * information the Herald rules are inteneded to block. + * information the Herald rules are intended to block. */ protected function validateTransaction( PhabricatorLiskDAO $object, diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index 88303cf420..1eeb4c8452 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -699,7 +699,7 @@ private function process() { $hunk_parser->parseHunksForLineData($changeset->getHunks()); // Depending on the whitespace mode, we may need to compute a different - // set of changes than the set of changes in the hunk data (specificaly, + // set of changes than the set of changes in the hunk data (specifically, // we might want to consider changed lines which have only whitespace // changes as unchanged). if ($ignore_all) { @@ -1192,11 +1192,11 @@ public function render( * Mask - compute the actual lines that need to be shown (because they * are near changes lines, near inline comments, or the request has * explicitly asked for them, i.e. resulting from the user clicking - * "show more"). The $mask returned is a sparesely populated dictionary + * "show more"). The $mask returned is a sparsely populated dictionary * of $visible_line_number => true. * * Depths - compute how indented any given line is. The $depths returned - * is a sparesely populated dictionary of $visible_line_number => $depth. + * is a sparsely populated dictionary of $visible_line_number => $depth. * * This function also has the side effect of modifying member variable * new such that tabs are normalized to spaces for each line of the diff. diff --git a/src/applications/differential/storage/DifferentialModernHunk.php b/src/applications/differential/storage/DifferentialModernHunk.php index cde6f29329..c3675c8adf 100644 --- a/src/applications/differential/storage/DifferentialModernHunk.php +++ b/src/applications/differential/storage/DifferentialModernHunk.php @@ -128,7 +128,7 @@ public function saveAsFile() { $this->setData($file->getPHID()); // NOTE: Because hunks don't have a PHID and we just load hunk data with - // the ominipotent viewer, we do not need to attach the file to anything. + // the omnipotent viewer, we do not need to attach the file to anything. $result = $this->save(); diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 7bcd178a41..e29db36b14 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -442,7 +442,7 @@ private function newReviewerForceAcceptMap(PhabricatorUser $viewer) { // For each path which the viewer owns a package for, find other packages // which that authority can be used to force-accept. Once we find a way to - // force-accept a package, we don't need to keep loooking. + // force-accept a package, we don't need to keep looking. $has_control = array(); foreach ($force_map as $path => $spec) { $path_fragments = PhabricatorOwnersPackage::splitPath($path); @@ -891,7 +891,7 @@ public function destroyObjectPermanently( self::TABLE_COMMIT, $this->getID()); - // we have to do paths a little differentally as they do not have + // we have to do paths a little differently as they do not have // an id or phid column for delete() to act on $dummy_path = new DifferentialAffectedPath(); queryfx( diff --git a/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php b/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php index 51eb2beb9a..d23ecff47d 100644 --- a/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php +++ b/src/applications/differential/typeahead/DifferentialResponsibleViewerFunctionDatasource.php @@ -22,7 +22,7 @@ public function getDatasourceFunctions() { 'summary' => pht('Use the current viewing user.'), 'description' => pht( 'Show revisions the current viewer is responsible for. This '. - 'function inclues revisions the viewer is responsible for through '. + 'function includes revisions the viewer is responsible for through '. 'membership in projects and packages.'), ), ); diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 38c94e8a92..d7734856f0 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -479,7 +479,7 @@ private function buildPropertyListView( // chains of events). This should be rare, but does not indicate a bug // or data issue. - // NOTE: We never query push logs in SVN because the commiter is always + // NOTE: We never query push logs in SVN because the committer is always // the pusher and the commit time is always the push time; the push log // is redundant and we save a query by skipping it. diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index 2459c8c559..b3baafbd06 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -338,7 +338,7 @@ protected function buildCustomEditFields($object) { ->setIsCopyable(true) ->setDescription(pht('Track only these branches.')) ->setConduitDescription(pht('Set the tracked branches.')) - ->setConduitTypeDescription(pht('New tracked branchs.')) + ->setConduitTypeDescription(pht('New tracked branches.')) ->setValue($track_value), id(new PhabricatorTextAreaEditField()) ->setIsStringList(true) @@ -349,7 +349,7 @@ protected function buildCustomEditFields($object) { ->setIsCopyable(true) ->setDescription(pht('Autoclose commits on only these branches.')) ->setConduitDescription(pht('Set the autoclose branches.')) - ->setConduitTypeDescription(pht('New default tracked branchs.')) + ->setConduitTypeDescription(pht('New default tracked branches.')) ->setValue($autoclose_value), id(new PhabricatorTextEditField()) ->setKey('importOnly') @@ -396,7 +396,7 @@ protected function buildCustomEditFields($object) { ->setConduitDescription( pht('Change symbol languages for this repository.')) ->setConduitTypeDescription( - pht('New symbol langauges.')) + pht('New symbol languages.')) ->setValue($object->getSymbolLanguages()), id(new PhabricatorDatasourceEditField()) ->setKey('symbolRepositoryPHIDs') diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 21df37d87d..7fe45834b3 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -1230,7 +1230,7 @@ private function isInitialImport(array $all_updates) { // also look at author data (do the commits come from multiple different // authors?) and commit date data (is the oldest commit more than 48 hours // old), but we don't have immediate access to those and this simple - // heruistic might be good enough. + // heuristic might be good enough. $commit_count = 0; $type_commit = PhabricatorRepositoryPushLog::REFTYPE_COMMIT; diff --git a/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php index aca7367f8c..41c009455d 100644 --- a/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php +++ b/src/applications/diffusion/gitlfs/DiffusionGitLFSAuthenticateWorkflow.php @@ -80,7 +80,7 @@ protected function executeRepositoryOperations() { $lfs_uri = $repository->getGitLFSURI('info/lfs'); - // Generate a temporary token to allow the user to acces LFS over HTTP. + // Generate a temporary token to allow the user to access LFS over HTTP. // This works even if normal HTTP repository operations are not available // on this host, and does not require the user to have a VCS password. diff --git a/src/applications/diffusion/herald/HeraldCommitAdapter.php b/src/applications/diffusion/herald/HeraldCommitAdapter.php index 4687028418..bfcd8e3ccb 100644 --- a/src/applications/diffusion/herald/HeraldCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldCommitAdapter.php @@ -112,7 +112,7 @@ public function getTriggerObjectPHIDs() { $phids[] = $repository_phid; // NOTE: This is projects for the repository, not for the commit. When - // Herald evalutes, commits normally can not have any project tags yet. + // Herald evaluates, commits normally can not have any project tags yet. $repository_project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $repository_phid, $project_type); diff --git a/src/applications/diffusion/protocol/DiffusionCommandEngine.php b/src/applications/diffusion/protocol/DiffusionCommandEngine.php index b787745a02..53a086db33 100644 --- a/src/applications/diffusion/protocol/DiffusionCommandEngine.php +++ b/src/applications/diffusion/protocol/DiffusionCommandEngine.php @@ -178,7 +178,7 @@ private function newCommonEnvironment() { if (!$device) { throw new Exception( pht( - 'Attempting to build a reposiory command (for repository "%s") '. + 'Attempting to build a repository command (for repository "%s") '. 'as device, but this host ("%s") is not configured as a cluster '. 'device.', $repository->getDisplayName(), diff --git a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php index 32a73867e5..251935a6dc 100644 --- a/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php +++ b/src/applications/diffusion/protocol/DiffusionMercurialWireProtocol.php @@ -101,7 +101,7 @@ public static function isReadOnlyBatchCommand($cmds) { /** If the server version is running 3.4+ it will respond * with 'bundle2' capability in the format of "bundle2=(url-encoding)". - * Until we maange to properly package up bundles to send back we + * Until we manage to properly package up bundles to send back we * disallow the client from knowing we speak bundle2 by removing it * from the capabilities listing. * diff --git a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php index e822bb76b0..35bb4d4acf 100644 --- a/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php +++ b/src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php @@ -231,7 +231,7 @@ public function synchronizeWorkingCopyBeforeRead() { // no way to tell which one has the "right" data. If we pick wrong, we // might erase some or all of the data in the repository. - // Since this is dangeorus, we refuse to guess unless there is only one + // Since this is dangerous, we refuse to guess unless there is only one // device. If we're the only device in the group, we obviously must be // a leader. @@ -252,7 +252,7 @@ public function synchronizeWorkingCopyBeforeRead() { 'Repository "%s" exists on more than one device, but no device '. 'has any repository version information. Phabricator can not '. 'guess which copy of the existing data is authoritative. Promote '. - 'a device or see "Ambigous Leaders" in the documentation.', + 'a device or see "Ambiguous Leaders" in the documentation.', $repository->getDisplayName())); } diff --git a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php index 22ff1d61f0..0217c97d58 100644 --- a/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php +++ b/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php @@ -9,7 +9,7 @@ * low-level query can. * * This class can resolve the most common refs (commits, branches, tags) and - * can do so cheapy (by examining the database, without needing to make calls + * can do so cheaply (by examining the database, without needing to make calls * to the VCS or the service host). */ final class DiffusionCachedResolveRefsQuery diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index 1521f5770c..da8937910e 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -69,7 +69,7 @@ public function withRepository(PhabricatorRepository $repository) { /** * Look up commits in a specific repository. Prefer - * @{method:withRepositoryIDs}; the underyling table is keyed by ID such + * @{method:withRepositoryIDs}; the underlying table is keyed by ID such * that this method requires a separate initial query to map PHID to ID. */ public function withRepositoryPHIDs(array $phids) { diff --git a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php index ff1cc9f3c5..30ec8d9f6d 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php @@ -53,7 +53,7 @@ protected function validateAction($object, PhabricatorUser $viewer) { } // Even if you've already raised a concern, you can raise again as long - // as the author requsted you verify. + // as the author requested you verify. $state_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION; if ($this->isViewerFullyRejected($object, $viewer)) { diff --git a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php index f9b3fc5161..a44261f7a4 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php @@ -53,7 +53,7 @@ protected function validateAction($object, PhabricatorUser $viewer) { throw new Exception( pht( 'You can not request verification of this commit because no '. - 'auditors have raised conerns with it.')); + 'auditors have raised concerns with it.')); } } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 76a81d7ef1..a050a859e3 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -64,7 +64,7 @@ public function getViewer() { * is a coarse compatibility check between a lease and a resource. * * @param DrydockBlueprint Concrete blueprint to allocate for. - * @param DrydockResource Candidiate resource to allocate the lease on. + * @param DrydockResource Candidate resource to allocate the lease on. * @param DrydockLease Pending lease that wants to allocate here. * @return bool True if the resource and lease are compatible. * @task lease @@ -76,7 +76,7 @@ abstract public function canAcquireLeaseOnResource( /** - * Acquire a lease. Allows resources to peform setup as leases are brought + * Acquire a lease. Allows resources to perform setup as leases are brought * online. * * If acquisition fails, throw an exception. diff --git a/src/applications/drydock/storage/DrydockAuthorization.php b/src/applications/drydock/storage/DrydockAuthorization.php index cfd186c9d3..32e694918c 100644 --- a/src/applications/drydock/storage/DrydockAuthorization.php +++ b/src/applications/drydock/storage/DrydockAuthorization.php @@ -100,7 +100,7 @@ public function isAuthorized() { } /** - * Apply external authorization effects after a user chagnes the value of a + * Apply external authorization effects after a user changes the value of a * blueprint selector control an object. * * @param PhabricatorUser User applying the change. diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index a41e57fbf8..d73506f286 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -241,7 +241,7 @@ private function executeAllocator(DrydockLease $lease) { // NOTE: We have not acquired the lease yet, so it is possible that the // resource we just built will be snatched up by some other lease before // we can acquire it. This is not problematic: we'll retry a little later - // and should suceed eventually. + // and should succeed eventually. } $resources = $this->rankResources($resources, $lease); @@ -261,7 +261,7 @@ private function executeAllocator(DrydockLease $lease) { if (!$allocated) { throw new PhutilAggregateException( pht( - 'Unable to acquire lease "%s" on any resouce.', + 'Unable to acquire lease "%s" on any resource.', $lease->getPHID()), $exceptions); } @@ -725,7 +725,7 @@ private function activateLease(DrydockLease $lease) { // performed the read above and now, the resource might have closed, so // we may activate leases on dead resources. At least for now, this seems // fine: a resource dying right before we activate a lease on it should not - // be distinguisahble from a resource dying right after we activate a lease + // be distinguishable from a resource dying right after we activate a lease // on it. We end up with an active lease on a dead resource either way, and // can not prevent resources dying from lightning strikes. diff --git a/src/applications/drydock/worker/DrydockWorker.php b/src/applications/drydock/worker/DrydockWorker.php index f167fef5d0..443780680d 100644 --- a/src/applications/drydock/worker/DrydockWorker.php +++ b/src/applications/drydock/worker/DrydockWorker.php @@ -199,7 +199,7 @@ protected function canReclaimResource(DrydockResource $resource) { $viewer = $this->getViewer(); // Don't reclaim a resource if it has been updated recently. If two - // leases are fighting, we don't want them to keep reclaming resources + // leases are fighting, we don't want them to keep reclaiming resources // from one another forever without making progress, so make resources // immune to reclamation for a little while after they activate or update. diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index c8bfcc488a..da438730cd 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -119,7 +119,7 @@ private function loadFile() { // make this logic simpler and more consistent. // Beyond making the policy check itself more consistent, this also makes - // sure we're consitent about returning HTTP 404 on bad requests instead + // sure we're consistent about returning HTTP 404 on bad requests instead // of serving HTTP 200 with a login page, which can mislead some clients. $viewer = PhabricatorUser::getOmnipotentUser(); diff --git a/src/applications/files/management/PhabricatorFilesManagementCatWorkflow.php b/src/applications/files/management/PhabricatorFilesManagementCatWorkflow.php index 70d5eca5a2..5bf40a057d 100644 --- a/src/applications/files/management/PhabricatorFilesManagementCatWorkflow.php +++ b/src/applications/files/management/PhabricatorFilesManagementCatWorkflow.php @@ -24,7 +24,7 @@ protected function didConstruct() { 'help' => pht( 'DANGEROUS. Attempt to salvage file content even if the '. 'integrity check fails. If an adversary has tampered with '. - 'the file, the conent may be unsafe.'), + 'the file, the content may be unsafe.'), ), array( 'name' => 'names', diff --git a/src/applications/fund/editor/FundInitiativeEditEngine.php b/src/applications/fund/editor/FundInitiativeEditEngine.php index 201a814a0c..824f4e009f 100644 --- a/src/applications/fund/editor/FundInitiativeEditEngine.php +++ b/src/applications/fund/editor/FundInitiativeEditEngine.php @@ -50,7 +50,7 @@ protected function getObjectCreateShortText() { } protected function getObjectName() { - return pht('Initivative'); + return pht('Initiative'); } protected function getObjectCreateCancelURI($object) { diff --git a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php index 9c3f88d631..dbf1f18cfa 100644 --- a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php @@ -142,7 +142,7 @@ public function getMethodDescription() { "=============\n". "When you send Harbormaster a message, you must include a `type`, ". "which describes the overall state of the build. For example, use ". - "`pass` to tell Harbomaster that a build completed successfully.". + "`pass` to tell Harbormaster that a build completed successfully.". "\n\n". "Supported message types are:". "\n\n". @@ -155,7 +155,7 @@ public function getMethodDescription() { "message, but you can also send a `work` message to report intermediate ". "results.\n\n". "To provide unit test results, pass a list of results in the `unit` ". - "parameter. Each result shoud be a dictionary with these keys:". + "parameter. Each result should be a dictionary with these keys:". "\n\n". "%s". "\n\n". diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php index cdb2044cc5..b29958882c 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php @@ -38,7 +38,7 @@ protected function getConfiguration() { // T6203/NULLABILITY // This should not be nullable. Current `null` values indicate steps // which predated editable names. These should be backfilled with - // default names, then the code for handling `null` shoudl be removed. + // default names, then the code for handling `null` should be removed. 'name' => 'text255?', 'stepAutoKey' => 'text32?', ), diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php index 05a691e34a..f19cf43d0e 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -236,7 +236,7 @@ public function handleRequest(AphrontRequest $request) { // Use the last content update as the modified date. We don't want to // show that a document like a TOS was "updated" by an incidental change - // to a field like the preamble or privacy settings which does not acutally + // to a field like the preamble or privacy settings which does not actually // affect the content of the agreement. $content_updated = $document_body->getDateCreated(); diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 6a5b3ad50c..66aa154e8f 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -116,7 +116,7 @@ public function renderBurn() { case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE: // NOTE: Merging a task does not generate a "status" transaction. // We pretend it did. Note that this is not always accurate: it is - // possble to merge a task which was previously closed, but this + // possible to merge a task which was previously closed, but this // fake transaction always counts a merge as a closure. $oldv = $default_status; $newv = $duplicate_status; @@ -762,7 +762,7 @@ private function getWindow() { // and equal distance in the past. This is so users can type "6 days" (which // means "6 days from now") and get the behavior of "6 days ago", rather // than no results (because the window epoch is in the future). This might - // be a little confusing because it casues "tomorrow" to mean "yesterday" + // be a little confusing because it causes "tomorrow" to mean "yesterday" // and "2022" (or whatever) to mean "ten years ago", but these inputs are // nonsense anyway. diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 1b780549dc..caf70b8f3c 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -972,7 +972,7 @@ private function applyBoardMove($object, array $move) { $object_phid = $object->getPHID(); - // We're doing layout with the ominpotent viewer to make sure we don't + // We're doing layout with the omnipotent viewer to make sure we don't // remove positions in columns that exist, but which the actual actor // can't see. $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); diff --git a/src/applications/metamta/management/PhabricatorMailManagementUnverifyWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementUnverifyWorkflow.php index 61861d1db0..64f4976ebe 100644 --- a/src/applications/metamta/management/PhabricatorMailManagementUnverifyWorkflow.php +++ b/src/applications/metamta/management/PhabricatorMailManagementUnverifyWorkflow.php @@ -63,7 +63,7 @@ public function execute(PhutilArgumentParser $args) { echo tsprintf( "%s\n", pht( - 'Address "%s" (owned by "%s") is already unveriifed.', + 'Address "%s" (owned by "%s") is already unverified.', $address, $user->getUsername())); continue; diff --git a/src/applications/metamta/receiver/PhabricatorMailReceiver.php b/src/applications/metamta/receiver/PhabricatorMailReceiver.php index 05b44f364a..12483bf5b6 100644 --- a/src/applications/metamta/receiver/PhabricatorMailReceiver.php +++ b/src/applications/metamta/receiver/PhabricatorMailReceiver.php @@ -255,7 +255,7 @@ public static function stripMailboxPrefix($address) { /** - * Reduce an email address to its canonical form. For example, an adddress + * Reduce an email address to its canonical form. For example, an address * like: * * "Abraham Lincoln" < ALincoln@example.com > diff --git a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php index f21f09b44e..d219abfc1e 100644 --- a/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php +++ b/src/applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php @@ -82,7 +82,7 @@ public function testFindLongestPathsPerPackage() { // Now, add a more specific path to Package #1. This tests nested ownership // in packages with weak dominion rules. This time, Package #1 should end - // up back on top, with Package #2 cedeing control to its more specific + // up back on top, with Package #2 ceding control to its more specific // path. $rows[] = array( 'id' => 1, diff --git a/src/applications/passphrase/credentialtype/PassphraseCredentialType.php b/src/applications/passphrase/credentialtype/PassphraseCredentialType.php index 1da93bcd38..60cb9bb5ae 100644 --- a/src/applications/passphrase/credentialtype/PassphraseCredentialType.php +++ b/src/applications/passphrase/credentialtype/PassphraseCredentialType.php @@ -104,7 +104,7 @@ public function getPasswordLabel() { /** - * Return true if the provided credental requires a password to decrypt. + * Return true if the provided credential requires a password to decrypt. * * @param PhutilOpaqueEnvelope Credential secret value. * @return bool True if the credential needs a password. diff --git a/src/applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php b/src/applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php index d573f19f58..fa686aac4c 100644 --- a/src/applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php +++ b/src/applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php @@ -47,7 +47,7 @@ public function decryptSecret( pht( 'Decrypting SSH keys requires the `%s` binary, but it '. 'is not available in %s. Either make it available or strip the '. - 'password fromt his SSH key manually before uploading it.', + 'password from this SSH key manually before uploading it.', 'ssh-keygen', '$PATH')); } diff --git a/src/applications/passphrase/view/PassphraseCredentialControl.php b/src/applications/passphrase/view/PassphraseCredentialControl.php index 9c9706fe3f..8ba1ef9bc8 100644 --- a/src/applications/passphrase/view/PassphraseCredentialControl.php +++ b/src/applications/passphrase/view/PassphraseCredentialControl.php @@ -67,7 +67,7 @@ protected function renderInput() { $user_credential->getName()); } } catch (PhabricatorPolicyException $policy_exception) { - // Pull the credential with the ominipotent viewer so we can look up + // Pull the credential with the omnipotent viewer so we can look up // the ID and provide the monogram. $omnipotent_credential = id(new PassphraseCredentialQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) diff --git a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php index aa5b7f908e..60d4a8168f 100644 --- a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php +++ b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php @@ -10,7 +10,7 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule { // NOTE: The negative lookbehind prevents matches like "mail@lists", while // allowing constructs like "@tomo/@mroch". Since we now allow periods in - // usernames, we can't resonably distinguish that "@company.com" isn't a + // usernames, we can't reasonably distinguish that "@company.com" isn't a // username, so we'll incorrectly pick it up, but there's little to be done // about that. We forbid terminal periods so that we can correctly capture // "@joe" instead of "@joe." in "Hey, @joe.". diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index e756a9a4fd..35ff480833 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -425,7 +425,7 @@ private function rebuildAvailabilityCache(array $rebuild) { } // If the user is set to "Available" for this event, don't consider it - // when computin their away status. + // when computing their away status. if (!$invitee->getDisplayAvailability($event)) { continue; } @@ -492,7 +492,7 @@ private function rebuildAvailabilityCache(array $rebuild) { // valid for that long. // NOTE: This doesn't handle overlapping events with the greatest - // possible care. In theory, if you're attenting multiple events + // possible care. In theory, if you're attending multiple events // simultaneously we should accommodate that. However, it's complex // to compute, rare, and probably not confusing most of the time. diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index fa363c4428..0a4367d367 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -79,7 +79,7 @@ protected function buildCustomSearchFields() { ->setOptions( pht('(Show All)'), pht('Show Only Unapproved Users'), - pht('Hide Unappproved Users')) + pht('Hide Unapproved Users')) ->setDescription( pht( 'Pass true to find only users awaiting administrative approval, '. diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 166feaa237..1745154826 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -407,7 +407,7 @@ public function validateCSRFToken($token) { $token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH); // When the user posts a form, we check that it contains a valid CSRF token. - // Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept + // Tokens cycle each hour (every CSRF_CYCLE_FREQUENCY seconds) and we accept // either the current token, the next token (users can submit a "future" // token if you have two web frontends that have some clock skew) or any of // the last 6 tokens. This means that pages are valid for up to 7 hours. @@ -1180,7 +1180,7 @@ public static function getOmnipotentUser() { /** * Get a scalar string identifying this user. * - * This is similar to using the PHID, but distinguishes between ominpotent + * This is similar to using the PHID, but distinguishes between omnipotent * and public users explicitly. This allows safe construction of cache keys * or cache buckets which do not conflate public and omnipotent users. * @@ -1459,7 +1459,7 @@ public function getFieldSpecificationsForConduit() { id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('roles') ->setType('list') - ->setDescription(pht('List of acccount roles.')), + ->setDescription(pht('List of account roles.')), ); } diff --git a/src/applications/phid/handle/pool/PhabricatorHandleList.php b/src/applications/phid/handle/pool/PhabricatorHandleList.php index d9a279f8ba..a7b404c5a7 100644 --- a/src/applications/phid/handle/pool/PhabricatorHandleList.php +++ b/src/applications/phid/handle/pool/PhabricatorHandleList.php @@ -82,7 +82,7 @@ public function newSublist(array $phids) { if (!isset($this[$phid])) { throw new Exception( pht( - 'Trying to create a new sublist of an existsing handle list, '. + 'Trying to create a new sublist of an existing handle list, '. 'but PHID "%s" does not appear in the parent list.', $phid)); } diff --git a/src/applications/phid/resolver/PhabricatorPHIDResolver.php b/src/applications/phid/resolver/PhabricatorPHIDResolver.php index 9070139a0b..940221f551 100644 --- a/src/applications/phid/resolver/PhabricatorPHIDResolver.php +++ b/src/applications/phid/resolver/PhabricatorPHIDResolver.php @@ -3,7 +3,7 @@ /** * Resolve a list of identifiers into PHIDs. * - * This class simplifies the process of convering a list of mixed token types + * This class simplifies the process of converting a list of mixed token types * (like some PHIDs and some usernames) into a list of just PHIDs. */ abstract class PhabricatorPHIDResolver extends Phobject { diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php index 1e7c348297..5adfc62e19 100644 --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -361,7 +361,7 @@ public function getCreatePaymentMethodErrorMessage($error_code) { case 'error:duplicate_transaction': case 'error:processing_error': default: - // NOTE: These errors currently don't recevive a detailed message. + // NOTE: These errors currently don't receive a detailed message. // NOTE: We can also end up here with "http:nnn" messages. // TODO: At least some of these should have a better message, or be diff --git a/src/applications/phrequent/storage/PhrequentTimeBlock.php b/src/applications/phrequent/storage/PhrequentTimeBlock.php index ee5fd00a44..3800651476 100644 --- a/src/applications/phrequent/storage/PhrequentTimeBlock.php +++ b/src/applications/phrequent/storage/PhrequentTimeBlock.php @@ -124,7 +124,7 @@ public function getObjectTimeRanges() { } } else { // Here, we've prematurely ended a deeper stratum. Merge higher - // stata. This looks like this: + // strata. This looks like this: // // V // V diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 1920b4c718..fcc9fe0474 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -452,7 +452,7 @@ public function validateAncestry( $verb) { $errors = array(); - // NOTE: We use the ominpotent user for these checks because policy + // NOTE: We use the omnipotent user for these checks because policy // doesn't matter; existence does. $other_doc_viewer = PhabricatorUser::getOmnipotentUser(); $ancestral_slugs = PhabricatorSlug::getAncestry($object->getSlug()); diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php index f3e1bd2083..fb03936ec2 100644 --- a/src/applications/policy/filter/PhabricatorPolicyFilter.php +++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php @@ -257,7 +257,7 @@ public function apply(array $objects) { $filtered[$key] = $object; } - // If we survied the primary checks, apply extended checks to objects + // If we survived the primary checks, apply extended checks to objects // with extended policies. $results = array(); $extended = array(); diff --git a/src/applications/project/query/PhabricatorProjectColumnQuery.php b/src/applications/project/query/PhabricatorProjectColumnQuery.php index 41ea770137..13f2f52a43 100644 --- a/src/applications/project/query/PhabricatorProjectColumnQuery.php +++ b/src/applications/project/query/PhabricatorProjectColumnQuery.php @@ -97,7 +97,7 @@ protected function didFilterPage(array $page) { if ($proxy_phid !== null) { $proxy = idx($proxies, $proxy_phid); - // Only attach valid proxies, so we don't end up getting surprsied if + // Only attach valid proxies, so we don't end up getting surprised if // an install somehow gets junk into their database. if (!($proxy instanceof PhabricatorColumnProxyInterface)) { $proxy = null; diff --git a/src/applications/project/remarkup/ProjectRemarkupRule.php b/src/applications/project/remarkup/ProjectRemarkupRule.php index 70d6b8eda3..96092bab86 100644 --- a/src/applications/project/remarkup/ProjectRemarkupRule.php +++ b/src/applications/project/remarkup/ProjectRemarkupRule.php @@ -24,7 +24,7 @@ protected function renderObjectRef( protected function getObjectIDPattern() { // NOTE: The latter half of this rule matches monograms with internal // periods, like `#domain.com`, but does not match monograms with terminal - // periods, because they're probably just puncutation. + // periods, because they're probably just punctuation. // Broadly, this will not match every possible project monogram, and we // accept some false negatives -- like `#dot.` -- in order to avoid a bunch diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index dd67f5bc59..56d371f1d6 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -5,7 +5,7 @@ * * 1: To parse "Releeph: picks RQ" headers in commits created by * arc-releeph so that RQs committed by arc-releeph have real - * PhabricatorRepositoryCommits associated with them (instaed of just the SHA + * PhabricatorRepositoryCommits associated with them (instead of just the SHA * of the commit, as seen by the pusher). * * 2: If requestors want to commit directly to their release branch, they can diff --git a/src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php b/src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php index df5e075ebc..af18bfd3d6 100644 --- a/src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php +++ b/src/applications/releeph/field/selector/ReleephDefaultFieldSelector.php @@ -7,7 +7,7 @@ final class ReleephDefaultFieldSelector extends ReleephFieldSelector { * * TODO: This is a giant hacky mess because I am dumb and moved forward on * Releeph changes with partial information. Recover from this as gracefully - * as possible. This obivously is an abomination. -epriestley + * as possible. This obviously is an abomination. -epriestley */ public static function isFacebook() { return class_exists('ReleephFacebookKarmaFieldSpecification'); diff --git a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php index 33f947e2f8..205b267ed5 100644 --- a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php +++ b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php @@ -113,7 +113,7 @@ private function parseUntil($until_type, $until_name) { private function parseParents($parents, $target_rev) { // The hg '{parents}' token is empty if there is one "natural" parent - // (predecessor local commit ID). Othwerwise, it may have one or two + // (predecessor local commit ID). Otherwise, it may have one or two // parents. The string looks like this: // // 151:1f6c61a60586 154:1d5f799ebe1e diff --git a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php index 27e93535ab..a29d110a67 100644 --- a/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php +++ b/src/applications/repository/data/PhabricatorRepositoryURINormalizer.php @@ -27,10 +27,10 @@ * // URIs are very unlikely to be the same repository. * } * - * Because a repository can be hosted at arbitrarly many arbitrary URIs, there + * Because a repository can be hosted at arbitrarily many arbitrary URIs, there * is no way to completely prevent false negatives by only examining URIs * (that is, repositories with totally different URIs could really be the same). - * However, normalization is relatively agressive and false negatives should + * However, normalization is relatively aggressive and false negatives should * be rare: if normalization says two URIs are different repositories, they * probably are. * diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index d0b84a6b31..8e99aabafa 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -703,7 +703,7 @@ private function insertTask( // new repository for the first time is less important than any other // daemon task. - // If the repostitory has finished importing and we're just catching up + // If the repository has finished importing and we're just catching up // on recent commits, we schedule discovery at COMMIT priority, which is // slightly below the default priority. diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php index 2f09ffd9be..71076314da 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementThawWorkflow.php @@ -90,7 +90,7 @@ public function execute(PhutilArgumentParser $args) { $risk_message); $is_force = $args->getArg('force'); - $prompt = pht('Accept the possibilty of permanent data loss?'); + $prompt = pht('Accept the possibility of permanent data loss?'); if (!$is_force && !phutil_console_confirm($prompt)) { throw new PhutilArgumentUsageException( pht('User aborted the workflow.')); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 09fb239bf0..713ed92270 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1875,7 +1875,7 @@ public function loadUpdateInterval($minimum = 15) { /** - * Retrieve the sevice URI for the device hosting this repository. + * Retrieve the service URI for the device hosting this repository. * * See @{method:newConduitClient} for a general discussion of interacting * with repository services. This method provides lower-level resolution of @@ -2129,7 +2129,7 @@ public function getPassthroughEnvironmentalVariables() { if ($this->isGit()) { // $_ENV does not populate in CLI contexts if "E" is missing from // "variables_order" in PHP config. Currently, we do not require this - // to be configured. Since it may not be, explictitly bring expected Git + // to be configured. Since it may not be, explicitly bring expected Git // environmental variables into scope. This list is not exhaustive, but // only lists variables with a known impact on commit hook behavior. diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php index cbd25b74eb..c8d560705a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -362,7 +362,7 @@ private function getForcedPort() { return PhabricatorEnv::getEnvConfig('diffusion.ssh-port'); } - // If Phabricator is running on a nonstandard port, use that as the defualt + // If Phabricator is running on a nonstandard port, use that as the default // port for URIs with the same protocol. $is_http = ($protocol == self::BUILTIN_PROTOCOL_HTTP); diff --git a/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php b/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php index 51abc70d35..750cc91c47 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php +++ b/src/applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php @@ -66,7 +66,7 @@ public static function getWriteLock($repository_phid) { * may have committed and acknowledged a write on a node that lost the lock * partway through the write and is no longer reachable. * - * In particular, if a node loses its connection to the datbase the global + * In particular, if a node loses its connection to the database the global * lock is released by default. This is a durable lock which stays locked * by default. */ diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php index 0fdd18b20f..60e6c5a5e6 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php @@ -216,7 +216,7 @@ protected function parseCommitChanges( $changes[$path]['changeType'] = DifferentialChangeType::TYPE_COPY_AWAY; } else if ($existing_type == DifferentialChangeType::TYPE_ADD) { - // This change removes a diretory and replaces it with a file. Mark + // This change removes a directory and replaces it with a file. Mark // it as "change" instead of "add". $changes[$path]['changeType'] = DifferentialChangeType::TYPE_CHANGE; } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index f4c460ce34..c0497a0c03 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -359,7 +359,7 @@ protected function buildCustomSearchFields() { * ); * * Any unspecified fields (including custom fields and fields added - * automatically by infrastruture) will be put in the middle. + * automatically by infrastructure) will be put in the middle. * * @return list Default ordering for field keys. */ diff --git a/src/applications/search/management/PhabricatorSearchManagementWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementWorkflow.php index 43dad986ea..b09f4b2105 100644 --- a/src/applications/search/management/PhabricatorSearchManagementWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementWorkflow.php @@ -5,7 +5,7 @@ abstract class PhabricatorSearchManagementWorkflow protected function validateClusterSearchConfig() { // Configuration is normally validated by setup self-checks on the web - // workflow, but users may reasonsably run `bin/search` commands after + // workflow, but users may reasonably run `bin/search` commands after // making manual edits to "local.json". Re-verify configuration here before // continuing. diff --git a/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php index e67d22b555..071979d391 100644 --- a/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php +++ b/src/applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php @@ -80,7 +80,7 @@ protected function newNavigationMenuItems( private function getCatFacts() { return array( pht('Cats purr when they are happy, upset, or asleep.'), - pht('The first cats evolved on the savanah about 8,000 years ago.'), + pht('The first cats evolved on the savannah about 8,000 years ago.'), pht( 'Cats have a tail, two feet, between one and three ears, and two '. 'other feet.'), @@ -111,7 +111,7 @@ private function getCatFacts() { pht( 'Not all cats can retract their claws, but most of them can.'), pht( - 'In the wild, cats and racoons sometimes hunt together in packs.'), + 'In the wild, cats and raccoons sometimes hunt together in packs.'), pht( 'The Spanish word for cat is "cato". The biggest cat is called '. '"el cato".'), diff --git a/src/applications/settings/panel/PhabricatorSettingsPanel.php b/src/applications/settings/panel/PhabricatorSettingsPanel.php index d2008f8857..19ac6fec62 100644 --- a/src/applications/settings/panel/PhabricatorSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSettingsPanel.php @@ -143,7 +143,7 @@ abstract public function getPanelGroupKey(); /** * Return false to prevent this panel from being displayed or used. You can * do, e.g., configuration checks here, to determine if the feature your - * panel controls is unavailble in this install. By default, all panels are + * panel controls is unavailable in this install. By default, all panels are * enabled. * * @return bool True if the panel should be shown. diff --git a/src/applications/settings/query/PhabricatorUserPreferencesQuery.php b/src/applications/settings/query/PhabricatorUserPreferencesQuery.php index de4887cbb8..1a6133724d 100644 --- a/src/applications/settings/query/PhabricatorUserPreferencesQuery.php +++ b/src/applications/settings/query/PhabricatorUserPreferencesQuery.php @@ -49,7 +49,7 @@ public function withBuiltinKeys(array $keys) { * If no settings exist for a user, a new empty settings object with * appropriate defaults is returned. * - * @param bool True to generat synthetic preferences for missing users. + * @param bool True to generate synthetic preferences for missing users. */ public function needSyntheticPreferences($synthetic) { $this->synthetic = $synthetic; diff --git a/src/applications/settings/setting/PhabricatorTimezoneSetting.php b/src/applications/settings/setting/PhabricatorTimezoneSetting.php index b7a6b94e30..887e08129b 100644 --- a/src/applications/settings/setting/PhabricatorTimezoneSetting.php +++ b/src/applications/settings/setting/PhabricatorTimezoneSetting.php @@ -46,7 +46,7 @@ public function assertValidValue($value) { throw new Exception( pht( - 'Timezone "%s" is not a valid timezone identiifer.', + 'Timezone "%s" is not a valid timezone identifier.', $value)); } diff --git a/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php b/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php index e005c120f0..817e92ce39 100644 --- a/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php +++ b/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php @@ -83,7 +83,7 @@ public function handleRequest(AphrontRequest $request) { } else { // TODO: Eventually, get rid of this once everything implements - // PhabriatorApplicationTransactionInterface. + // PhabricatorApplicationTransactionInterface. $editor = id(new PhabricatorSubscriptionsEditor()) ->setActor($viewer) diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index e1d6812778..6c9cb5e201 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -763,7 +763,7 @@ private function newObjectFromPHID($phid, array $capabilities = array()) { * Load an object given a configured query. * * @param PhabricatorPolicyAwareQuery Configured query. - * @param list List of required capabilitiy constants, or omit for + * @param list List of required capability constants, or omit for * defaults. * @return object|null Object, or null if no such object exists. * @task load @@ -1382,7 +1382,7 @@ private function buildEditFormActions($object) { * and that field is visible and editable for the user. * * For example, you can use it to test if a user is able to reassign tasks - * or not, prior to rendering dedicated UI for task reassingment. + * or not, prior to rendering dedicated UI for task reassignment. * * Note that this method does NOT test if the user can actually edit the * current object, just if they have access to the related field. diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 07edf5b0c8..175a5775ee 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -3701,7 +3701,7 @@ private function applyProjectConflictRules(array $phids) { // If a later project in the list is an ancestor of this one, it will // have added itself to the map. If any ancestor of this project points - // at itself in the map, this project should be dicarded in favor of + // at itself in the map, this project should be discarded in favor of // that later ancestor. foreach ($project->getAncestorProjects() as $ancestor) { $ancestor_phid = $ancestor->getPHID(); diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index 6956561490..8b747281b3 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -451,7 +451,7 @@ protected function didEvaluateTokens(array $results) { public static function isFunctionToken($token) { // We're looking for a "(" so that a string like "members(q" is identified // and parsed as a function call. This allows us to start generating - // results immeidately, before the user fully types out "members(quack)". + // results immediately, before the user fully types out "members(quack)". return (strpos($token, '(') !== false); } diff --git a/src/applications/uiexample/examples/PhabricatorStatusUIExample.php b/src/applications/uiexample/examples/PhabricatorStatusUIExample.php index 6583bc5848..76a345b8e3 100644 --- a/src/applications/uiexample/examples/PhabricatorStatusUIExample.php +++ b/src/applications/uiexample/examples/PhabricatorStatusUIExample.php @@ -33,7 +33,7 @@ public function renderExample() { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'dark', pht('Caution')) - ->setTarget(pht('Pomegranite')) + ->setTarget(pht('Pomegranate')) ->setNote(pht('Lots of seeds. Watch out.'))); $view->addItem( diff --git a/src/docs/contributor/cla.diviner b/src/docs/contributor/cla.diviner index f08711483a..54e0142beb 100644 --- a/src/docs/contributor/cla.diviner +++ b/src/docs/contributor/cla.diviner @@ -87,7 +87,7 @@ you to consent. If you are not comfortable with this, do not sign the CLA and do not contribute to Phabricator. **Limitation of Liability**: The second benefit the CLA provides is that it -makes the terms of your contribition explicitly clear upfront, and it puts us +makes the terms of your contribution explicitly clear upfront, and it puts us in a much stronger legal position if a contributor later claims there is ambiguity about ownership of their work. We can point at the document they signed as proof that they consented to our use and understood the terms of @@ -100,7 +100,7 @@ anything we are likely to face, but SCO claimed billions of dollars in damages and the litigation has now been ongoing for more than a decade. We want to avoid situations like this in the future by making the terms of -contibution explicit upfront. +contribution explicit upfront. Generally, we believe the terms of the CLA are fair and reasonable for contributors, and that the primary way contributors benefit from contributing @@ -158,7 +158,7 @@ local law. If you are unsure, you should speak with your employer or a lawyer. If you contribute code you do not own under the individual CLA, you are exposing -yourself to liability. You may also be exposing us to liablity, but we'll have +yourself to liability. You may also be exposing us to liability, but we'll have the CLA on our side to show that we were unwilling pawns in your malicious scheme to defraud your employer. diff --git a/src/docs/contributor/describing_problems.diviner b/src/docs/contributor/describing_problems.diviner index 57bb849b02..d06d7b7d64 100644 --- a/src/docs/contributor/describing_problems.diviner +++ b/src/docs/contributor/describing_problems.diviner @@ -140,7 +140,7 @@ Additional Resources Poor problem descriptions are a common issue in software development and extensively documented elsewhere. Here are some additional resources describing -how to describe problems and ask questions effectivey: +how to describe problems and ask questions effectively: - [[ http://www.catb.org/esr/faqs/smart-questions.html | How To Ask Questions The Smart Way ]], by Eric S. Raymond diff --git a/src/docs/contributor/internationalization.diviner b/src/docs/contributor/internationalization.diviner index e9cdb9dfcd..9c78eb60fb 100644 --- a/src/docs/contributor/internationalization.diviner +++ b/src/docs/contributor/internationalization.diviner @@ -198,7 +198,7 @@ would technically be English and most English readers would understand the meaning, but no native English speaker would speak or write like this. However, some languages have different subject-verb order rules or -colloquisalisms, and a word order which transliterates like this may sound more +colloquialisms, and a word order which transliterates like this may sound more natural to a native speaker. By translating fragments instead of complete sentences, you lock translators into English word order. diff --git a/src/docs/contributor/reproduction_steps.diviner b/src/docs/contributor/reproduction_steps.diviner index d5d58913b7..1050e43c48 100644 --- a/src/docs/contributor/reproduction_steps.diviner +++ b/src/docs/contributor/reproduction_steps.diviner @@ -112,7 +112,7 @@ we'll probably be able to follow them and reproduce the issue ourselves. If you can't follow your steps because they depend on information which is not available on a clean instance (for example, a certain config setting), rewrite -them to include instrutions to create that information (for example, adjusting +them to include instructions to create that information (for example, adjusting the config to the problematic value). If you follow your instructions but the issue does not reproduce, the issue @@ -191,7 +191,7 @@ smaller case which reproduces the problem, which might be something like this: - Files like `A`, which have uppercase letters, do not work. With a simpler reproduction scenario, you can simplify your steps to be more -tailored and mimimal. This will help us pointpoint the issue more quickly and +tailored and minimal. This will help us pinpoint the issue more quickly and be more certain that we've understood and resolved it. It is more important that steps be complete than that they be simple, and it's diff --git a/src/docs/user/cluster/cluster.diviner b/src/docs/user/cluster/cluster.diviner index 30ad53efb8..2362b80485 100644 --- a/src/docs/user/cluster/cluster.diviner +++ b/src/docs/user/cluster/cluster.diviner @@ -225,7 +225,7 @@ Cluster: Notifications Configuring multiple notification hosts is simple and has no pre-requisites. With multiple notification hosts, you can survive the loss of any subset of -hosts as long as at least one host remains alive. Service may be breifly +hosts as long as at least one host remains alive. Service may be briefly disrupted directly after the incident which destroys the other hosts. Notifications are noncritical, so this normally has little practical impact diff --git a/src/docs/user/cluster/cluster_databases.diviner b/src/docs/user/cluster/cluster_databases.diviner index 7255662958..51bf14d925 100644 --- a/src/docs/user/cluster/cluster_databases.diviner +++ b/src/docs/user/cluster/cluster_databases.diviner @@ -31,7 +31,7 @@ although this may change in the future. Phabricator applications //can// be partitioned across multiple database masters. This does not provide redundancy and generally does not increase -resiliance or resistance to data loss, but can help you scale and operate +resilience or resistance to data loss, but can help you scale and operate Phabricator. For details, see @{article:Cluster: Partitioning and Advanced Configuration}. diff --git a/src/docs/user/cluster/cluster_devices.diviner b/src/docs/user/cluster/cluster_devices.diviner index 57729fdb86..c90aa220c4 100644 --- a/src/docs/user/cluster/cluster_devices.diviner +++ b/src/docs/user/cluster/cluster_devices.diviner @@ -212,7 +212,7 @@ $ ./bin/almanac register \ For example, you might run this command on `repo001` when using a shared key: ``` -$ ./bin/almanac register +$ ./bin/almanac register \ --device keywarden.mycompany.net \ --private-key /path/to/private-key \ --identify-as repo001.mycompany.net diff --git a/src/docs/user/cluster/cluster_partitioning.diviner b/src/docs/user/cluster/cluster_partitioning.diviner index 4dbb47f9d4..20ae11d6a6 100644 --- a/src/docs/user/cluster/cluster_partitioning.diviner +++ b/src/docs/user/cluster/cluster_partitioning.diviner @@ -21,7 +21,7 @@ This configuration is complex, and very few installs will benefit from pursuing it. Phabricator will normally run comfortably with a single database master even for large organizations. -Partitioning generally does not do much to increase resiliance or make it +Partitioning generally does not do much to increase resilience or make it easier to recover from disasters, and is primarily a mechanism for scaling and operational convenience. @@ -212,14 +212,14 @@ only one master. `persistent` //(bool)// Enables persistent connections. Defaults to off. -With persitent connections enabled, Phabricator will keep a pool of database +With persistent connections enabled, Phabricator will keep a pool of database connections open between web requests and reuse them when serving subsequent requests. The primary benefit of using persistent connections is that it will greatly reduce pressure on how quickly outbound TCP ports are opened and closed. After a TCP port closes, it normally can't be used again for about 60 seconds, so -rapidly cycling ports can cause resource exuastion. If you're seeing failures +rapidly cycling ports can cause resource exhaustion. If you're seeing failures because requests are unable to bind to an outbound port, enabling this option is likely to fix the issue. This option may also slightly increase performance. diff --git a/src/docs/user/cluster/cluster_repositories.diviner b/src/docs/user/cluster/cluster_repositories.diviner index 21aba731cc..44003679af 100644 --- a/src/docs/user/cluster/cluster_repositories.diviner +++ b/src/docs/user/cluster/cluster_repositories.diviner @@ -440,7 +440,7 @@ device or devices have up-to-date information. When Phabricator can not tell which device in a cluster is a leader, it freezes the cluster because it is possible that some devices have less data and others -have more, and if it choses a leader arbitrarily it may destroy some data +have more, and if it chooses a leader arbitrarily it may destroy some data which you would prefer to retain. To resolve this, you need to tell Phabricator which device has the most diff --git a/src/docs/user/cluster/cluster_search.diviner b/src/docs/user/cluster/cluster_search.diviner index c658f50db4..25c35aa34a 100644 --- a/src/docs/user/cluster/cluster_search.diviner +++ b/src/docs/user/cluster/cluster_search.diviner @@ -100,14 +100,14 @@ A typical `mysql` service configuration looks like this: Service Type: Elasticsearch ====================== -The `elasticsearch` sevice type supports these options: +The `elasticsearch` service type supports these options: | Key | Description | |---|---| | `protocol` | Either `"http"` (default) or `"https"`. | `port` | Elasticsearch TCP port. | `version` | Elasticsearch version, either `2` or `5` (default). -| `path` | Path for the index. Defaults to `/phabriator`. Advanced. +| `path` | Path for the index. Defaults to `/phabricator`. Advanced. A typical `elasticsearch` service configuration looks like this: diff --git a/src/docs/user/configuration/configuration_guide.diviner b/src/docs/user/configuration/configuration_guide.diviner index 98c27b9736..8b221bda41 100644 --- a/src/docs/user/configuration/configuration_guide.diviner +++ b/src/docs/user/configuration/configuration_guide.diviner @@ -116,7 +116,7 @@ Restart nginx after making your edits, then continue to "Setup" below. = Webserver: Configuring lighttpd = -NOTE: Follow these instructions to use lighttpd. To use Apache or niginx, scroll +NOTE: Follow these instructions to use lighttpd. To use Apache or nginx, scroll up to their sections. For lighttpd, add a section like this to your lighttpd.conf: @@ -138,7 +138,7 @@ server.modules list: Finally, you should run the following commands to enable php support: - $ sudo apt-get install php5-cgi # for ubuntu; other distros should be similar + $ sudo apt-get install php5-cgi # for Ubuntu; other distros should be similar $ sudo lighty-enable-mod fastcgi-php Restart lighttpd after making your edits, then continue below. diff --git a/src/docs/user/configuration/configuring_backups.diviner b/src/docs/user/configuration/configuring_backups.diviner index d32eebb0da..9c283c5722 100644 --- a/src/docs/user/configuration/configuring_backups.diviner +++ b/src/docs/user/configuration/configuring_backups.diviner @@ -64,7 +64,7 @@ If you host repositories in Phabricator, you should back them up. You can use 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. +{nav Add Mirror} to have them mirror somewhere automatically. Restore: Hosted Repositories diff --git a/src/docs/user/configuration/configuring_encryption.diviner b/src/docs/user/configuration/configuring_encryption.diviner index dbd0e76314..36315506e6 100644 --- a/src/docs/user/configuration/configuring_encryption.diviner +++ b/src/docs/user/configuration/configuring_encryption.diviner @@ -80,7 +80,7 @@ Format: Raw Data ================ The `raw` storage format is automatically selected for all newly uploaded -file data if no key is makred as the `default` key in the keyring. This is +file data if no key is marked as the `default` key in the keyring. This is the behavior of Phabricator if you haven't configured anything. This format stores raw data without modification. @@ -137,7 +137,7 @@ To change the format of an individual file, run this command: phabricator/ $ ./bin/files encode --as F123 [--key ] ``` -This will change the storage format of the sepcified file. +This will change the storage format of the specified file. Verifying Storage Formats diff --git a/src/docs/user/configuration/managing_garbage.diviner b/src/docs/user/configuration/managing_garbage.diviner index 1896a73904..0b18bd2a0a 100644 --- a/src/docs/user/configuration/managing_garbage.diviner +++ b/src/docs/user/configuration/managing_garbage.diviner @@ -21,7 +21,7 @@ Configuring Retention Policies You can reconfigure the data retention policies for most collectors. -The default retention polcies should be suitable for most installs. However, +The default retention policies should be suitable for most installs. However, you might want to **decrease** retention to reduce the amount of disk space used by some high-volume log that you don't find particularly interesting, or to adhere to an organizational data retention policy. diff --git a/src/docs/user/field/conduit_changes.diviner b/src/docs/user/field/conduit_changes.diviner index a5aa8fef6e..96a890600a 100644 --- a/src/docs/user/field/conduit_changes.diviner +++ b/src/docs/user/field/conduit_changes.diviner @@ -11,7 +11,7 @@ For example, when we write a new application, it usually adds several new API methods and may update older methods. This document discusses API stability and how to minimize disruption when -transitionig between API versions. +transitioning between API versions. Method Statuses diff --git a/src/docs/user/field/darkconsole.diviner b/src/docs/user/field/darkconsole.diviner index a31f0492ae..cbdfb9bda5 100644 --- a/src/docs/user/field/darkconsole.diviner +++ b/src/docs/user/field/darkconsole.diviner @@ -131,7 +131,7 @@ Generally, this column can help pinpoint these kinds of problems: - Unbatched queries which should be batched (see @{article:Performance: N+1 Query Problem}). - Opportunities to improve performance with caching. - - General goofiness in how service calls are woking. + - General goofiness in how service calls are working. If the services tab looks fine, and particularly if a page is slow but the "All Services" cost is small, that may indicate a problem in PHP. The best diff --git a/src/docs/user/field/performance.diviner b/src/docs/user/field/performance.diviner index 09413e47df..c5980acd0e 100644 --- a/src/docs/user/field/performance.diviner +++ b/src/docs/user/field/performance.diviner @@ -34,7 +34,7 @@ Phabricator will need substantial time to process it, it will take a long time to download over the network, and your browser will probably not be able to render it especially quickly. -We may be able to improve perfomance in some cases, but Phabricator is not +We may be able to improve performance in some cases, but Phabricator is not magic and can not wish away real complexity. The best solution to these problems is usually to find another way to solve your problem: for example, maybe the 100MB document can be split into several smaller documents. @@ -51,7 +51,7 @@ The upstream will be less able to help resolve unusual workloads with high inherent complexity, like these: - {icon times, color=red} A 100MB wiki page takes a long time to render. - - {icon times, color=red} A turing-complete simulation of Conway's Game of + - {icon times, color=red} A Turing-complete simulation of Conway's Game of Life implemented in 958,000 Herald rules executes slowly. - {icon times, color=red} Uploading an 8GB file takes several minutes. diff --git a/src/docs/user/field/repository_imports.diviner b/src/docs/user/field/repository_imports.diviner index 495758ea4a..1b1c92bbc0 100644 --- a/src/docs/user/field/repository_imports.diviner +++ b/src/docs/user/field/repository_imports.diviner @@ -127,7 +127,7 @@ may have briefly been down, or some configuration wasn't quite right, or the daemons were killed halfway through the work. These commits will retry eventually and usually succeed, but some of the retry -time limits are very conserative (up to 24 hours) and you might not want to +time limits are very conservative (up to 24 hours) and you might not want to wait that long. In the Daemon console, temporarily failures usually look like tasks in the @@ -137,7 +137,7 @@ waiting to retry these tasks. In the daemon log, these temporary failures might have created log entries, but might also not have. For example, if the failure was rooted in a network issue -that probably will create a log entry, but if the faiulre was rooted in the +that probably will create a log entry, but if the failure was rooted in the daemons being abruptly killed that may not create a log entry. You can follow the instructions from "Handling Permanent Failures" above to @@ -167,7 +167,7 @@ Forced Parsing ============== In rare cases, the actual tasks may be lost from the task queue. Usually, they -have been stolen by gremlins or spritied away by ghosts, or someone may have +have been stolen by gremlins or spirited away by ghosts, or someone may have been too ambitious with running manual SQL commands and deleted a bunch of extra things they shouldn't have. @@ -226,7 +226,7 @@ General Tips ============ Broadly, `bin/repository` contains several useful debugging commands which -let you figure out where failures are occuring. You can add the `--trace` flag +let you figure out where failures are occurring. You can add the `--trace` flag to any command to get more details about what it is doing. For any command, you can use `help` to learn more about what it does and which flag it takes: diff --git a/src/docs/user/field/xhprof.diviner b/src/docs/user/field/xhprof.diviner index 7d032e6865..ad6fe82cb8 100644 --- a/src/docs/user/field/xhprof.diviner +++ b/src/docs/user/field/xhprof.diviner @@ -88,7 +88,7 @@ that you're looking at a problem which is deeper in the stack, and you need to go down further to identify and understand it. Conversely, if the "Wall Time (Exclusive)" column is large, or the children -of a call are all cheap, there's probably something expesive happening in the +of a call are all cheap, there's probably something expensive happening in the call itself. The "Count" column can also sometimes tip you off that something is amiss, if diff --git a/src/docs/user/userguide/almanac.diviner b/src/docs/user/userguide/almanac.diviner index d6f0853430..9250724c03 100644 --- a/src/docs/user/userguide/almanac.diviner +++ b/src/docs/user/userguide/almanac.diviner @@ -122,7 +122,7 @@ Each service has a name, and may have properties and bindings. **Bindings**: Bindings are connections between services and interfaces. They tell callers which devices host a named service. -**Networks**: Networks allow Almanac to distingiush between addresses on +**Networks**: Networks allow Almanac to distinguish between addresses on different networks, like VPNs vs the public internet. If you have hosts in different VPNs or on private networks, you might have diff --git a/src/docs/user/userguide/arcanist.diviner b/src/docs/user/userguide/arcanist.diviner index 324ff10490..f6ccc3550f 100644 --- a/src/docs/user/userguide/arcanist.diviner +++ b/src/docs/user/userguide/arcanist.diviner @@ -3,7 +3,7 @@ Guide to Arcanist, a command-line interface to Phabricator. -Arcanists provides command-line access to many Phabricator tools (like +Arcanist provides command-line access to many Phabricator tools (like Differential, Files, and Paste), integrates with static analysis ("lint") and unit tests, and manages common workflows like getting changes into Differential for review. diff --git a/src/docs/user/userguide/arcanist_lint.diviner b/src/docs/user/userguide/arcanist_lint.diviner index d7fcce14e8..460b8f85b6 100644 --- a/src/docs/user/userguide/arcanist_lint.diviner +++ b/src/docs/user/userguide/arcanist_lint.diviner @@ -169,7 +169,7 @@ Normally, you will be presented with lint messages as you are sending code for review. In that context, the severities behave like this: - `error` When a file contains lint errors, they are always reported. These - are intended to be severe problems, like a syntax error. Unresoved lint + are intended to be severe problems, like a syntax error. Unresolved lint errors require you to confirm that you want to continue. - `warning` When a file contains warnings, they are reported by default only if they appear on lines that you have changed. They are intended to be diff --git a/src/docs/user/userguide/calendar_exports.diviner b/src/docs/user/userguide/calendar_exports.diviner index 340cff863d..487de559a4 100644 --- a/src/docs/user/userguide/calendar_exports.diviner +++ b/src/docs/user/userguide/calendar_exports.diviner @@ -61,7 +61,7 @@ To export a group of events: - Example: All events tagged `#meetup`. - Select the {nav Use Results... > Export Query as .ics} action to turn the query into an export. - - Name the export with a descritive name. + - Name the export with a descriptive name. - Select a policy mode for the export (see below for discussion). - Click {nav Create New Export} to finish the process. diff --git a/src/docs/user/userguide/calendar_imports.diviner b/src/docs/user/userguide/calendar_imports.diviner index a4163e9bcd..a8fbc6ff09 100644 --- a/src/docs/user/userguide/calendar_imports.diviner +++ b/src/docs/user/userguide/calendar_imports.diviner @@ -10,7 +10,7 @@ IMPORTANT: Calendar is a prototype application. See @{article:User Guide: Prototype Applications}. You can import events into Phabricator to other calendar applications or from -`.ics` files. This document will guide you through how to importe event data +`.ics` files. This document will guide you through how to import event data into Phabricator. When you import events from another application, they can not be edited in diff --git a/src/docs/user/userguide/conduit_edit.diviner b/src/docs/user/userguide/conduit_edit.diviner index 01d15873a8..5188300e6d 100644 --- a/src/docs/user/userguide/conduit_edit.diviner +++ b/src/docs/user/userguide/conduit_edit.diviner @@ -37,7 +37,7 @@ Editing Objects To edit objects, pass a list of transactions and use `objectIdentifier` to specify which object to apply them to. You can normally pass an ID or PHID, -and many applicaitons also allow you to pass a monogram (for example, you can +and many applications also allow you to pass a monogram (for example, you can edit a task by passing `T123`). diff --git a/src/docs/user/userguide/diffusion_api.diviner b/src/docs/user/userguide/diffusion_api.diviner index f57d5007aa..c2508dda72 100644 --- a/src/docs/user/userguide/diffusion_api.diviner +++ b/src/docs/user/userguide/diffusion_api.diviner @@ -147,7 +147,7 @@ Activate the Repository ======================= Now that any URIs have been configured, activate the repository with another -call to `diffusion.repository.edit`. This time, modify the existing repostitory +call to `diffusion.repository.edit`. This time, modify the existing repository instead of creating a new one: ``` diff --git a/src/docs/user/userguide/diffusion_hosting.diviner b/src/docs/user/userguide/diffusion_hosting.diviner index 2443b475e8..47197f52fd 100644 --- a/src/docs/user/userguide/diffusion_hosting.diviner +++ b/src/docs/user/userguide/diffusion_hosting.diviner @@ -151,7 +151,7 @@ vcs-user ALL=(daemon-user) SETENV: NOPASSWD: /path/to/x, /path/to/y, ... This is just a template. In the real configuration file, you need to: - - Replace `www-user`, `dameon-user` and `vcs-user` with the correct + - Replace `www-user`, `daemon-user` and `vcs-user` with the correct usernames for your system. - List every binary that these users need access to, as described above. - Make sure each binary path is the full path to the correct binary location diff --git a/src/docs/user/userguide/diffusion_managing.diviner b/src/docs/user/userguide/diffusion_managing.diviner index f6ab0997c4..cc03433532 100644 --- a/src/docs/user/userguide/diffusion_managing.diviner +++ b/src/docs/user/userguide/diffusion_managing.diviner @@ -121,7 +121,7 @@ It is normally a good idea to leave this protection enabled because most scalable workflows rarely rewrite repository history and it's easy to make mistakes which are expensive to correct if this protection is disabled. -If you do occasionally need to rewite published history, you can treat this +If you do occasionally need to rewrite published history, you can treat this option like a safety: disable it, perform required rewrites, then enable it again. @@ -148,7 +148,7 @@ Repositories can be deactivated. Deactivating a repository has these effects: - the repository will be hidden from view in default queries. When repositories are created for the first time, they are deactivated. This -gives you an opportuinty to customize settings, like adjusting policies or +gives you an opportunity to customize settings, like adjusting policies or configuring a URI to observe. You must activate a repository before it will start working normally. @@ -177,7 +177,7 @@ The **Policies** section of the management interface allows you to review and manage repository access policies. You can configure granular access policies for each repository to control who -can view, clone, administate, and push to the repository. +can view, clone, administrate, and push to the repository. Policies: View @@ -302,7 +302,7 @@ Actions The **Actions** panel can configure notifications and publishing behavior. Normally, Phabricator publishes notifications when it discovers new commits. -You can disable publishing for a repository by turning off **Publish/Noitfy**. +You can disable publishing for a repository by turning off **Publish/Notify**. This will disable notifications, feed, and Herald (including audits and build plans) for this repository. @@ -347,7 +347,7 @@ Commit Identifiers ================== Diffusion uses repository identifiers and information about the commit itself -to generate globally unique identifers for each commit, like `rE12345`. +to generate globally unique identifiers for each commit, like `rE12345`. Each commit may have several identifiers: diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index d1f4b541bc..140948f55e 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -20,7 +20,7 @@ is hosted elsewhere (like GitHub or Bitbucket) and track updates to the remote repository. This will create a read-only copy of the repository in Phabricator. **Mirror Repositories**: Phabricator can publish any repository to mirrors, -overwiting them with an exact copy of the repository that stays up to date as +overwriting them with an exact copy of the repository that stays up to date as the source changes. This works with both local repositories that Phabricator is hosting and remote repositories that Phabricator is observing. @@ -293,7 +293,7 @@ For builtin URIs in //Read Only// or //Read/Write// mode, the most human-readable URI defaults to //Always// and the others default to //Never//. **Always**: This URI will be shown to users as a clone/checkout URI. You can -add URIs in this mode to cutomize exactly what users are shown. +add URIs in this mode to customize exactly what users are shown. **Never**: This URI will not be shown to users. You can hide less-preferred URIs to guide users to the URIs they should be using to interact with the diff --git a/src/docs/user/userguide/drydock.diviner b/src/docs/user/userguide/drydock.diviner index 44a645bf8a..0d43f7f3f0 100644 --- a/src/docs/user/userguide/drydock.diviner +++ b/src/docs/user/userguide/drydock.diviner @@ -151,7 +151,7 @@ thousands of hosts in hundreds of pools, and far beyond that with a little work. Drydock is intended to solve resource management problems at very large scales -and minimzes blocking operations, locks, and artificial sequencing. Drydock is +and minimizes blocking operations, locks, and artificial sequencing. Drydock is designed to fully utilize an almost arbitrarily large pool of resources and improve performance roughly linearly with available hardware. diff --git a/src/docs/user/userguide/drydock_blueprints.diviner b/src/docs/user/userguide/drydock_blueprints.diviner index 21c4342fa6..c150c503d9 100644 --- a/src/docs/user/userguide/drydock_blueprints.diviner +++ b/src/docs/user/userguide/drydock_blueprints.diviner @@ -47,7 +47,7 @@ operations on trusted hosts. For additional discussion, see This also broadly prevents Drydock from surprising you by coming up with a valid but unintended solution to an allocation problem which runs some -operation on resources that are techincally suitable but not desirable. For +operation on resources that are technically suitable but not desirable. For example, you may not want your Android builds running on your iPhone build tier, even if there's no technical reason they can't. diff --git a/src/docs/user/userguide/drydock_security.diviner b/src/docs/user/userguide/drydock_security.diviner index f12586ab35..9a212437c7 100644 --- a/src/docs/user/userguide/drydock_security.diviner +++ b/src/docs/user/userguide/drydock_security.diviner @@ -141,7 +141,7 @@ host which is not on a privileged subnet. For example, use a Even if the host is not privileged, many Drydock processes have some level of privilege (enabling them to clone repositories, or report test results back to Phabricator). Be aware that tests can hijack credentials they are run with, -and potentialy hijack credentials given to other processes on the same hosts. +and potentially hijack credentials given to other processes on the same hosts. You should use credentials with a minimum set of privileges and assume all processes on a host have the highest level of access that any process on the host has. diff --git a/src/docs/user/userguide/forms.diviner b/src/docs/user/userguide/forms.diviner index 98233e4931..034293ff29 100644 --- a/src/docs/user/userguide/forms.diviner +++ b/src/docs/user/userguide/forms.diviner @@ -34,7 +34,7 @@ objects, which will also affect their ability to take inline actions in the comment form if you're working in an application which supports comments. This can streamline the edit workflow for less experienced users. -Anyone can use prefiling, but you must have permission to configure an +Anyone can use prefilling, but you must have permission to configure an application in order to modify the application's forms. By default, only administrators can configure applications. diff --git a/src/docs/user/userguide/herald.diviner b/src/docs/user/userguide/herald.diviner index 6c5c6239cb..c5a008474d 100644 --- a/src/docs/user/userguide/herald.diviner +++ b/src/docs/user/userguide/herald.diviner @@ -126,7 +126,7 @@ match the content. You can use these together to express conditions like For example, if you want to match revisions which add or remove calls to a "muffinize" function, //but only in JS files//, you can set the value to `["/\\.js$/", "/muffinize/"]` or similar. This condition is satisfied only -when the filename matches the first expression and the conent matches the +when the filename matches the first expression and the content matches the second expression. **Another Herald rule**: you can create Herald rules which depend on other diff --git a/src/docs/user/userguide/jump.diviner b/src/docs/user/userguide/jump.diviner index 413c4fa47b..0421f194a8 100644 --- a/src/docs/user/userguide/jump.diviner +++ b/src/docs/user/userguide/jump.diviner @@ -1,7 +1,7 @@ @title Search User Guide: Shortcuts @group userguide -Command reference for global search shorcuts. +Command reference for global search shortcuts. Overview ======== diff --git a/src/docs/user/userguide/owners.diviner b/src/docs/user/userguide/owners.diviner index a9a52bfab8..f30fe5023c 100644 --- a/src/docs/user/userguide/owners.diviner +++ b/src/docs/user/userguide/owners.diviner @@ -24,7 +24,7 @@ including these paths: Any files in those directories are considered to be part of the package, and you can now conveniently refer to them (for example, in a Herald rule) by -refering to the package instead of copy/pasting a huge regular expression +referring to the package instead of copy/pasting a huge regular expression into a bunch of places. If new source files are later added, or the scope of the package otherwise diff --git a/src/docs/user/userguide/projects.diviner b/src/docs/user/userguide/projects.diviner index 573c8c4c69..4e3ab3616f 100644 --- a/src/docs/user/userguide/projects.diviner +++ b/src/docs/user/userguide/projects.diviner @@ -14,7 +14,7 @@ a name and an icon, and may optionally have members. For example, you can create projects to provide: - - **Organization**: Create a project to represent a product or initative, + - **Organization**: Create a project to represent a product or initiative, then use it to organize related work. - **Groups**: Create a project to represent a group of people (like a team), then add members of the group as project members. diff --git a/src/docs/user/userguide/remarkup.diviner b/src/docs/user/userguide/remarkup.diviner index 6002867b78..28a1a31fc4 100644 --- a/src/docs/user/userguide/remarkup.diviner +++ b/src/docs/user/userguide/remarkup.diviner @@ -366,7 +366,7 @@ can be found on the date stamp of any transaction/comment): T123#412 # Link to comment id #412 of task T123 -See the Phabricator configuraton setting `remarkup.ignored-object-names` to +See the Phabricator configuration setting `remarkup.ignored-object-names` to modify this behavior. = Embedding Objects diff --git a/src/docs/user/userguide/search.diviner b/src/docs/user/userguide/search.diviner index 875edc6d0f..d1c6182128 100644 --- a/src/docs/user/userguide/search.diviner +++ b/src/docs/user/userguide/search.diviner @@ -87,7 +87,7 @@ view show the results you most often want. You can share queries with other users by sending them the URL. This will run the same query for them with all the parameters you've set (they may see -different results than you do, because they may not have the same permisions). +different results than you do, because they may not have the same permissions). Typeaheads diff --git a/src/docs/user/userguide/spaces.diviner b/src/docs/user/userguide/spaces.diviner index 212def61f3..4a748fb25b 100644 --- a/src/docs/user/userguide/spaces.diviner +++ b/src/docs/user/userguide/spaces.diviner @@ -70,7 +70,7 @@ spaces exist. This simplifies the UI for users with limited access. Space Policies ============== -Briefly, spacess affect policies like this: +Briefly, spaces affect policies like this: - Spaces apply their view policy to all objects inside the space. - Space policies are absolute, and stronger than all other policies. A diff --git a/src/docs/user/userguide/users.diviner b/src/docs/user/userguide/users.diviner index e96bf8f354..d66f8080d3 100644 --- a/src/docs/user/userguide/users.diviner +++ b/src/docs/user/userguide/users.diviner @@ -73,7 +73,7 @@ The **Mailing List** role for an account can not be changed after the account is created. Some options can be configured for mailing lists by browsing to the list user's -profile and clicking {nav Edit Settings}. You can change the addresss for a +profile and clicking {nav Edit Settings}. You can change the address for a list by editing "Email Addresses" here, choose the language and format for email the list receives, and customize which actions the list is notified about. diff --git a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php index cb1e56711d..efe12e68b8 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php @@ -160,7 +160,7 @@ public function appendFieldsToPropertyList( if ($value !== null) { switch ($field->getStyleForPropertyView()) { case 'header': - // We want to hide headers if the fields the're assciated with + // We want to hide headers if the fields they're associated with // don't actually produce any visible properties. For example, in a // list like this: // diff --git a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php index c569f0c695..a8dc5061e7 100644 --- a/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php +++ b/src/infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php @@ -5,7 +5,7 @@ final class PhabricatorWorkerTriggerQuery // NOTE: This is a PolicyAware query so it can work with other infrastructure // like handles; triggers themselves are low-level and do not have - // meaninguful policies. + // meaningful policies. const ORDER_ID = 'id'; const ORDER_EXECUTION = 'execution'; @@ -69,7 +69,7 @@ public function setOrder($order) { protected function nextPage(array $page) { // NOTE: We don't implement paging because we don't currently ever need - // it and paging ORDER_EXCUTION is a hassle. + // it and paging ORDER_EXECUTION is a hassle. throw new PhutilMethodNotImplementedException(); } diff --git a/src/infrastructure/diff/view/PHUIDiffGraphView.php b/src/infrastructure/diff/view/PHUIDiffGraphView.php index 76ae0b2045..85741faab7 100644 --- a/src/infrastructure/diff/view/PHUIDiffGraphView.php +++ b/src/infrastructure/diff/view/PHUIDiffGraphView.php @@ -160,7 +160,7 @@ public function renderRawGraph(array $parents) { $terminated[$ii] = true; - // If this thread is joinining some other node here, we don't want + // If this thread is joining some other node here, we don't want // to terminate it. if (isset($graph[$key + 1])) { $joins = $graph[$key + 1]['join']; diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php index 5da25389a5..94eb91ec58 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php @@ -4,7 +4,7 @@ * Wraps an inline comment row scaffold in a table. * * This scaffold is used to ship inlines over the wire to the client, so they - * arrive in a form that's easy to mainipulate (a valid table node). + * arrive in a form that's easy to manipulate (a valid table node). */ final class PHUIDiffInlineCommentTableScaffold extends AphrontView { diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index 70ddb80630..ba05d0409d 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -670,7 +670,7 @@ public static function isValidLocalURIForLink($uri) { * Detect if a URI identifies some valid linkable remote resource. * * @param string URI to test. - * @return bool True if a URI idenfies a remote resource with an allowed + * @return bool True if a URI identifies a remote resource with an allowed * protocol. * @task uri */ diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 95ba57f0c0..184c2d5151 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -730,7 +730,7 @@ protected function getTranslations() { '%s added %s reverting commit(s) for %s: %s.' => array( array( '%s added a reverting commit for %3$s: %4$s.', - '%s added reverting commitsi for %3$s: %4$s.', + '%s added reverting commits for %3$s: %4$s.', ), ), @@ -1564,7 +1564,7 @@ protected function getTranslations() { '%s uninvited %s attendee(s): %s.' => '%s uninvited: %3$s.', - '%s invited %s attendee(s): %s; uninvinted %s attendee(s): %s.' => + '%s invited %s attendee(s): %s; uninvited %s attendee(s): %s.' => '%s invited: %3$s; uninvited: %5$s.', '%s invited %s attendee(s) to %s: %s.' => @@ -1573,7 +1573,7 @@ protected function getTranslations() { '%s uninvited %s attendee(s) to %s: %s.' => '%s removed invites for %3$s: %4$s.', - '%s updated the invite list for %s, invited %s: %s; uninvinted %s: %s.' => + '%s updated the invite list for %s, invited %s: %s; uninvited %s: %s.' => '%s updated the invite list for %s, invited: %4$s; uninvited: %6$s.', 'Restart %s build(s)?' => array( diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index c2b8ce04e0..9bebfe6957 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -175,7 +175,7 @@ protected function didLoadRawRows(array $rows) { * So, generally, internal paging must bypass policy controls. * * This method returns the appropriate viewer, based on the context in which - * the paging is occuring. + * the paging is occurring. * * @return PhabricatorUser Viewer for executing paging queries. */ @@ -536,7 +536,7 @@ protected function willExecuteCursorQuery( * * @param AphrontDatabaseConnection Connection query will execute on. * @param list Column description dictionaries. - * @param map Additional constuction options. + * @param map Additional construction options. * @return string Query clause. * @task paging */ diff --git a/src/infrastructure/storage/lisk/LiskDAO.php b/src/infrastructure/storage/lisk/LiskDAO.php index 94eef7d163..0c940ef5f0 100644 --- a/src/infrastructure/storage/lisk/LiskDAO.php +++ b/src/infrastructure/storage/lisk/LiskDAO.php @@ -1121,7 +1121,7 @@ public function update() { $this->willSaveObject(); $data = $this->getAllLiskPropertyValues(); - // Remove colums flagged as nonmutable from the update statement. + // Remove columns flagged as nonmutable from the update statement. $no_mutate = $this->getConfigOption(self::CONFIG_NO_MUTATE); if ($no_mutate) { foreach ($no_mutate as $column) { diff --git a/src/infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php b/src/infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php index 065c23e2ff..92c43fcd4b 100644 --- a/src/infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php +++ b/src/infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php @@ -98,7 +98,7 @@ public function testCounters() { $obj = new HarbormasterObject(); $conn_w = $obj->establishConnection('w'); - // Test that the counter bascially behaves as expected. + // Test that the counter basically behaves as expected. $this->assertEqual(1, LiskDAO::loadNextCounterValue($conn_w, 'a')); $this->assertEqual(2, LiskDAO::loadNextCounterValue($conn_w, 'a')); $this->assertEqual(3, LiskDAO::loadNextCounterValue($conn_w, 'a')); diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index a03ef41c4d..f6b120ba83 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -181,8 +181,8 @@ final private function doAdjustSchemata( if (!$this->force && !$api->isCharacterSetAvailable('utf8mb4')) { $message = pht( "You have an old version of MySQL (older than 5.5) which does not ". - "support the utf8mb4 character set. We strongly recomend upgrading to ". - "5.5 or newer.\n\n". + "support the utf8mb4 character set. We strongly recommend upgrading ". + "to 5.5 or newer.\n\n". "If you apply adjustments now and later update MySQL to 5.5 or newer, ". "you'll need to apply adjustments again (and they will take a long ". "time).\n\n". diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php index 19c8414539..5ca4e9b53c 100644 --- a/src/infrastructure/util/PhabricatorHash.php +++ b/src/infrastructure/util/PhabricatorHash.php @@ -11,7 +11,7 @@ final class PhabricatorHash extends Phobject { * weak. Callers should prefer @{method:digestWithNamedKey}. * * @param string Input string. - * @return string 32-byte hexidecimal SHA1+HMAC hash. + * @return string 32-byte hexadecimal SHA1+HMAC hash. */ public static function weakDigest($string, $key = null) { if ($key === null) { diff --git a/src/infrastructure/util/password/PhabricatorPasswordHasher.php b/src/infrastructure/util/password/PhabricatorPasswordHasher.php index 7972acc0a8..f0f30045c0 100644 --- a/src/infrastructure/util/password/PhabricatorPasswordHasher.php +++ b/src/infrastructure/util/password/PhabricatorPasswordHasher.php @@ -70,7 +70,7 @@ abstract public function getInstallInstructions(); /** * Return an indicator of this hasher's strength. When choosing to hash - * new passwords, the strongest available hasher which is usuable for new + * new passwords, the strongest available hasher which is usable for new * passwords will be used, and the presence of a stronger hasher will * prompt users to update their hashes. * @@ -208,7 +208,7 @@ private static function parseHashFromStorage(PhutilOpaqueEnvelope $hash) { * Get all available password hashers. This may include hashers which can not * actually be used (for example, a required extension is missing). * - * @return list Hasher objects. + * @return list Hasher objects. * @task hashing */ public static function getAllHashers() { @@ -243,7 +243,7 @@ public static function getAllHashers() { * Get all usable password hashers. This may include hashers which are * not desirable or advisable. * - * @return list Hasher objects. + * @return list Hasher objects. * @task hashing */ public static function getAllUsableHashers() { @@ -280,7 +280,7 @@ public static function getBestHasher() { /** - * Get the hashser for a given stored hash. + * Get the hasher for a given stored hash. * * @return PhabricatorPasswordHasher Corresponding hasher. * @task hashing diff --git a/src/view/AphrontView.php b/src/view/AphrontView.php index c014d7d50c..669742f29b 100644 --- a/src/view/AphrontView.php +++ b/src/view/AphrontView.php @@ -42,7 +42,7 @@ public function getViewer() { /** - * Test if a viewer has been set on this elmeent. + * Test if a viewer has been set on this element. * * @return bool True if a viewer is available. */ diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index d26cca945f..78ff716a44 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -272,7 +272,7 @@ protected function willRenderPage() { 'doc_href' => $doc_href, 'message' => pht( 'Phabricator thinks you are using %s, but your '. - 'client is conviced that it is using %s. This is a serious '. + 'client is convinced that it is using %s. This is a serious '. 'misconfiguration with subtle, but significant, consequences.', $server_protocol, $client_protocol), )); diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index dc836f3d3a..53ec096265 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -166,7 +166,7 @@ protected function getTagAttributes() { $classes[] = 'phui-header-shell'; if ($this->noBackground) { - $classes[] = 'phui-header-no-backgound'; + $classes[] = 'phui-header-no-background'; } if ($this->bleedHeader) { diff --git a/webroot/rsrc/css/phui/phui-document.css b/webroot/rsrc/css/phui/phui-document.css index e5985f8889..954a220229 100644 --- a/webroot/rsrc/css/phui/phui-document.css +++ b/webroot/rsrc/css/phui/phui-document.css @@ -35,13 +35,13 @@ } .phui-document-content - .phui-header-shell.phui-header-no-backgound { + .phui-header-shell.phui-header-no-background { border-bottom: 1px solid {$thinblueborder}; margin: 0 0 16px 0; } .phui-document-content - .phui-header-shell.phui-header-no-backgound + .phui-header-shell.phui-header-no-background .phui-header-view { padding: 8px 0 4px; } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 18b1464e53..478dde2153 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -34,7 +34,7 @@ vertical-align: middle; } -body .phui-header-shell.phui-header-no-backgound { +body .phui-header-shell.phui-header-no-background { background-color: transparent; border: none; } diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index a5566dc5a4..43488ce4ff 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -94,7 +94,7 @@ JX.behavior('conpherence-menu', function(config) { var _oldDevice = null; /** - * Initializes this behavior based on all the configuraton jonx and the + * Initializes this behavior based on all the configuration jonx and the * result of JX.Device.getDevice(); */ function init() { diff --git a/webroot/rsrc/js/application/conpherence/behavior-participant-pane.js b/webroot/rsrc/js/application/conpherence/behavior-participant-pane.js index 8c9fc056f4..e428cbdbce 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-participant-pane.js +++ b/webroot/rsrc/js/application/conpherence/behavior-participant-pane.js @@ -69,7 +69,7 @@ JX.behavior('conpherence-participant-pane', function() { // While the user is removing themselves, disable the notification // update behavior. If we don't do this, the user can get an error // when they remove themselves about permissions as the notification - // code tries to load what jist happened. + // code tries to load what just happened. var loadedPhid = threadManager.getLoadedThreadPHID(); threadManager.setLoadedThreadPHID(null); diff --git a/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js b/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js index 14894f6cc0..1f94f349d3 100644 --- a/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js +++ b/webroot/rsrc/js/application/diffusion/DiffusionLocateFileSource.js @@ -260,7 +260,7 @@ JX.install('DiffusionLocateFileSource', { /** * Score a matching string by finding the longest prefix of the search - * query it contains continguously. + * query it contains contiguously. */ scoreMatch: function(haystack, haypos, search) { var pos = 0; diff --git a/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js b/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js index 3991f4cc4e..0a02aebce7 100644 --- a/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js +++ b/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js @@ -201,7 +201,7 @@ JX.behavior('pholio-mock-view', function(config, statics) { } function render_image_header(image) { - // Render image dimensions and visible size. If we have this infomation + // Render image dimensions and visible size. If we have this information // from the server we can display some of it immediately; otherwise, we need // to wait for the image to load so we can read dimension information from // it. diff --git a/webroot/rsrc/js/core/Prefab.js b/webroot/rsrc/js/core/Prefab.js index c5a8ef5e9d..979ad3473b 100644 --- a/webroot/rsrc/js/core/Prefab.js +++ b/webroot/rsrc/js/core/Prefab.js @@ -184,7 +184,7 @@ JX.install('Prefab', { var self_hits = {}; // We'll put matches where the user's input is a prefix of the name - // above mathches where that isn't true. + // above matches where that isn't true. var prefix_hits = {}; var tokens = this.tokenize(value); @@ -212,7 +212,7 @@ JX.install('Prefab', { } // If one result is open and one is closed, show the open result - // first. The "!" tricks here are becaused closed values are display + // first. The "!" tricks here are because closed values are display // strings, so the value is either `null` or some truthy string. If // we compare the values directly, we'll apply this rule to two // objects which are both closed but for different reasons, like diff --git a/webroot/rsrc/js/core/behavior-device.js b/webroot/rsrc/js/core/behavior-device.js index d74939a7e0..d68a46ff4e 100644 --- a/webroot/rsrc/js/core/behavior-device.js +++ b/webroot/rsrc/js/core/behavior-device.js @@ -27,7 +27,7 @@ JX.install('Device', { var self = JX.Device; // Even when we emit a '' tag which tells - // devices to fit the conent to the screen width, we'll sometimes measure + // devices to fit the content to the screen width, we'll sometimes measure // a viewport dimension which is larger than the available screen width, // particularly if we check too early. diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index df4a031377..a7f719dab4 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -543,7 +543,7 @@ JX.install('PHUIXAutocomplete', { // If the input is terminated by a space or another word-terminating // punctuation mark, we're going to deactivate if the results can not - // be refined by addding more words. + // be refined by adding more words. // The idea is that if you type "@alan ab", you're allowed to keep // editing "ab" until you type a space, period, or other terminator, diff --git a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js index 3f93c1ee41..d9d86bf595 100644 --- a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js +++ b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js @@ -124,7 +124,7 @@ JX.install('PHUIXDropdownMenu', { } // If this action was built dynamically with PHUIXActionView, don't - // do anything by default. The caller is repsonsible for installing a + // do anything by default. The caller is responsible for installing a // handler if they want to react to clicks. if (e.getNode('phuix-action-view')) { return; From b583093186974272188c9403cfd57a7179a3ecab Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Oct 2017 10:52:27 -0700 Subject: [PATCH 222/865] Fix Celerity map definition after spelling corrections See D18693. --- resources/celerity/map.php | 196 ++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6c7d2db90c..ce069dbd11 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,9 +8,9 @@ return array( 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', - 'conpherence.pkg.js' => 'b5b51108', - 'core.pkg.css' => '87a9a59b', - 'core.pkg.js' => '28552e58', + 'conpherence.pkg.js' => '15191c65', + 'core.pkg.css' => '1a4e0c25', + 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', 'differential.pkg.js' => 'b71b8c5d', @@ -151,13 +151,13 @@ 'rsrc/css/phui/phui-curtain-view.css' => '2bdaf026', 'rsrc/css/phui/phui-document-pro.css' => '8af7ea27', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', - 'rsrc/css/phui/phui-document.css' => 'c32e8dec', + 'rsrc/css/phui/phui-document.css' => '878c2f52', 'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9', 'rsrc/css/phui/phui-fontkit.css' => '1320ed01', 'rsrc/css/phui/phui-form-view.css' => 'ae9f8d16', 'rsrc/css/phui/phui-form.css' => '7aaa04e3', 'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f', - 'rsrc/css/phui/phui-header-view.css' => '67fab16d', + 'rsrc/css/phui/phui-header-view.css' => '31dc6c72', 'rsrc/css/phui/phui-hovercard.css' => 'f0592bcf', 'rsrc/css/phui/phui-icon-set-selector.css' => '87db8fee', 'rsrc/css/phui/phui-icon.css' => '5c4a5de6', @@ -384,8 +384,8 @@ 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '4d863052', 'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '9bbf3762', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '2ae077e1', - 'rsrc/js/application/conpherence/behavior-menu.js' => 'c9b99b77', - 'rsrc/js/application/conpherence/behavior-participant-pane.js' => '8604caa8', + 'rsrc/js/application/conpherence/behavior-menu.js' => '4047cd35', + 'rsrc/js/application/conpherence/behavior-participant-pane.js' => 'd057e45a', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '55616e04', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '3dbf94d5', @@ -403,7 +403,7 @@ 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-populate.js' => '419998ab', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', - 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'c93358e3', + 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '00676f00', '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' => '75b83cbb', @@ -428,7 +428,7 @@ 'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3', 'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc', 'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => 'bee502c8', - 'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => 'fbe497e7', + 'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => 'ec1f3669', 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => 'a6b98425', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c', 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', @@ -468,7 +468,7 @@ 'rsrc/js/core/KeyboardShortcutManager.js' => 'c19dd9b9', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => '008faf9c', - 'rsrc/js/core/Prefab.js' => 'c5af80a2', + 'rsrc/js/core/Prefab.js' => '77b0ae28', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 'rsrc/js/core/Title.js' => '485aaa6c', @@ -480,7 +480,7 @@ 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', - 'rsrc/js/core/behavior-device.js' => 'bb1dd507', + 'rsrc/js/core/behavior-device.js' => 'a3714c76', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '484a6e22', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-fancy-datepicker.js' => 'ecf4e799', @@ -527,9 +527,9 @@ 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => '4b7430ab', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'e0731603', 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', - 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', + 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', @@ -599,8 +599,8 @@ 'javelin-behavior-choose-control' => '327a00d1', 'javelin-behavior-comment-actions' => '9a6dd75c', 'javelin-behavior-config-reorder-fields' => 'b6993408', - 'javelin-behavior-conpherence-menu' => 'c9b99b77', - 'javelin-behavior-conpherence-participant-pane' => '8604caa8', + 'javelin-behavior-conpherence-menu' => '4047cd35', + 'javelin-behavior-conpherence-participant-pane' => 'd057e45a', 'javelin-behavior-conpherence-pontificate' => '55616e04', 'javelin-behavior-conpherence-search' => '9bbf3762', 'javelin-behavior-countdown-timer' => 'e4cc26b3', @@ -612,7 +612,7 @@ 'javelin-behavior-day-view' => '4b3c4443', 'javelin-behavior-desktop-notifications-control' => '27ca6289', 'javelin-behavior-detect-timezone' => '4c193c96', - 'javelin-behavior-device' => 'bb1dd507', + 'javelin-behavior-device' => 'a3714c76', 'javelin-behavior-diff-preview-link' => '051c7832', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-feedback-preview' => '51c5ad07', @@ -669,7 +669,7 @@ 'javelin-behavior-phabricator-transaction-list' => '1f6794f6', 'javelin-behavior-phabricator-watch-anchor' => '9f36c42d', 'javelin-behavior-pholio-mock-edit' => 'bee502c8', - 'javelin-behavior-pholio-mock-view' => 'fbe497e7', + 'javelin-behavior-pholio-mock-view' => 'ec1f3669', 'javelin-behavior-phui-dropdown-menu' => 'b95d6f7d', 'javelin-behavior-phui-file-upload' => 'b003d4fb', 'javelin-behavior-phui-hovercards' => 'bcaccd64', @@ -709,7 +709,7 @@ 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', - 'javelin-diffusion-locate-file-source' => 'c93358e3', + 'javelin-diffusion-locate-file-source' => '00676f00', 'javelin-dom' => '4976858c', 'javelin-dynval' => 'f6555212', 'javelin-event' => '2ee659ce', @@ -794,7 +794,7 @@ 'phabricator-notification-menu-css' => '10685bd4', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => 'c5af80a2', + 'phabricator-prefab' => '77b0ae28', 'phabricator-remarkup-css' => 'cad18339', 'phabricator-search-results-css' => '505dd8cf', 'phabricator-shaped-request' => '7cbe244b', @@ -835,7 +835,7 @@ 'phui-crumbs-view-css' => '6ece3bbb', 'phui-curtain-view-css' => '2bdaf026', 'phui-document-summary-view-css' => '9ca48bdf', - 'phui-document-view-css' => 'c32e8dec', + 'phui-document-view-css' => '878c2f52', 'phui-document-view-pro-css' => '8af7ea27', 'phui-feed-story-css' => '44a9c8e9', 'phui-font-icon-base-css' => '870a7360', @@ -843,7 +843,7 @@ 'phui-form-css' => '7aaa04e3', 'phui-form-view-css' => 'ae9f8d16', 'phui-head-thing-view-css' => 'fd311e5f', - 'phui-header-view-css' => '67fab16d', + 'phui-header-view-css' => '31dc6c72', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'f0592bcf', 'phui-icon-set-selector-css' => '87db8fee', @@ -879,9 +879,9 @@ 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '442efd08', - 'phuix-autocomplete' => '4b7430ab', + 'phuix-autocomplete' => 'e0731603', 'phuix-button-view' => '8a91e1ac', - 'phuix-dropdown-menu' => '8018ee50', + 'phuix-dropdown-menu' => '04b2ae03', 'phuix-form-control-view' => '83e03671', 'phuix-icon-view' => 'bff6884b', 'policy-css' => '957ea14c', @@ -904,6 +904,12 @@ 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( + '00676f00' => array( + 'javelin-install', + 'javelin-dom', + 'javelin-typeahead-preloaded-source', + 'javelin-util', + ), '008faf9c' => array( 'javelin-install', 'javelin-dom', @@ -924,6 +930,13 @@ 'javelin-dom', 'phabricator-keyboard-shortcut', ), + '04b2ae03' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + 'javelin-stratcom', + ), '051c7832' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1142,6 +1155,20 @@ '3ffe32d6' => array( 'javelin-install', ), + '4047cd35' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '408bf173' => array( 'javelin-behavior', 'javelin-dom', @@ -1232,12 +1259,6 @@ 'javelin-util', 'phabricator-shaped-request', ), - '4b7430ab' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), '4c193c96' => array( 'javelin-behavior', 'javelin-uri', @@ -1471,6 +1492,18 @@ 'javelin-reactor', 'javelin-util', ), + '77b0ae28' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-typeahead', + 'javelin-tokenizer', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), '77c1f0b0' => array( 'javelin-behavior', 'javelin-dom', @@ -1513,13 +1546,6 @@ '7f243deb' => array( 'javelin-install', ), - '8018ee50' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-vector', - 'javelin-stratcom', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1536,15 +1562,6 @@ '85ee8ce6' => array( 'aphront-dialog-view-css', ), - '8604caa8' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-util', - 'phabricator-notification', - 'conpherence-thread-manager', - ), '88236f00' => array( 'javelin-behavior', 'phabricator-keyboard-shortcut', @@ -1698,6 +1715,13 @@ 'javelin-util', 'phabricator-keyboard-shortcut', ), + 'a3714c76' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + 'javelin-install', + ), 'a3a63478' => array( 'phui-workcard-view-css', ), @@ -1835,13 +1859,6 @@ 'javelin-uri', 'phabricator-notification', ), - 'bb1dd507' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - 'javelin-install', - ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1901,18 +1918,6 @@ 'c587b80f' => array( 'javelin-install', ), - 'c5af80a2' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-typeahead', - 'javelin-tokenizer', - 'javelin-typeahead-preloaded-source', - 'javelin-typeahead-ondemand-source', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), 'c7ccd872' => array( 'phui-fontkit-css', ), @@ -1924,31 +1929,11 @@ 'javelin-install', 'javelin-util', ), - 'c93358e3' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-typeahead-preloaded-source', - 'javelin-util', - ), 'c989ade3' => array( 'javelin-install', 'javelin-util', 'javelin-stratcom', ), - 'c9b99b77' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), 'caade6f2' => array( 'javelin-behavior', 'javelin-request', @@ -1966,6 +1951,15 @@ 'cd2b9b77' => array( 'phui-oi-list-view-css', ), + 'd057e45a' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-util', + 'phabricator-notification', + 'conpherence-thread-manager', + ), 'd0a99ab4' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -2030,6 +2024,12 @@ 'javelin-typeahead-ondemand-source', 'javelin-dom', ), + 'e0731603' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2096,6 +2096,20 @@ 'javelin-dom', 'phabricator-draggable-list', ), + 'ec1f3669' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + 'javelin-magical-init', + 'javelin-request', + 'javelin-history', + 'javelin-workflow', + 'javelin-mask', + 'javelin-behavior-device', + 'phabricator-keyboard-shortcut', + ), 'ecf4e799' => array( 'javelin-behavior', 'javelin-util', @@ -2144,20 +2158,6 @@ 'javelin-install', 'javelin-dom', ), - 'fbe497e7' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - 'javelin-magical-init', - 'javelin-request', - 'javelin-history', - 'javelin-workflow', - 'javelin-mask', - 'javelin-behavior-device', - 'phabricator-keyboard-shortcut', - ), 'fc91ab6c' => array( 'javelin-behavior', 'javelin-dom', From cf3e198b9f69c10714d1fd58a4b591ac1723b72d Mon Sep 17 00:00:00 2001 From: Dmitri Iouchtchenko Date: Mon, 9 Oct 2017 11:56:05 -0700 Subject: [PATCH 223/865] Change 'tempate' to 'template' Summary: Fixed typo. See D18693. Test Plan: None. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D18694 --- .../fund/application/PhabricatorFundApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/fund/application/PhabricatorFundApplication.php b/src/applications/fund/application/PhabricatorFundApplication.php index 9f29f30986..58ce4f8922 100644 --- a/src/applications/fund/application/PhabricatorFundApplication.php +++ b/src/applications/fund/application/PhabricatorFundApplication.php @@ -56,7 +56,7 @@ protected function getCustomCapabilities() { return array( FundDefaultViewCapability::CAPABILITY => array( 'caption' => pht('Default view policy for newly created initiatives.'), - 'tempate' => FundInitiativePHIDType::TYPECONST, + 'template' => FundInitiativePHIDType::TYPECONST, ), FundCreateInitiativesCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, From 5897294fa97e17002fd8d847abd97a81cc420971 Mon Sep 17 00:00:00 2001 From: Dmitri Iouchtchenko Date: Mon, 9 Oct 2017 11:56:52 -0700 Subject: [PATCH 224/865] Add spelling TODOs Summary: Ref T13005. Added reminders not to copy/paste. Test Plan: None. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley, PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T13005 Differential Revision: https://secure.phabricator.com/D18695 --- src/applications/auth/provider/PhabricatorLDAPAuthProvider.php | 1 + .../capability/HarbormasterBuildPlanDefaultViewCapability.php | 1 + .../legalpad/xaction/LegalpadDocumentPreambleTransaction.php | 1 + 3 files changed, 3 insertions(+) diff --git a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php index 7c2bf38618..44b58b85ff 100644 --- a/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorLDAPAuthProvider.php @@ -192,6 +192,7 @@ public function processLoginRequest( const KEY_VERSION = 'ldap:version'; const KEY_REFERRALS = 'ldap:referrals'; const KEY_START_TLS = 'ldap:start-tls'; + // TODO: This is misspelled! See T13005. const KEY_ANONYMOUS_USERNAME = 'ldap:anoynmous-username'; const KEY_ANONYMOUS_PASSWORD = 'ldap:anonymous-password'; const KEY_ALWAYS_SEARCH = 'ldap:always-search'; diff --git a/src/applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php b/src/applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php index 5d3d46caa0..d9ebbb2f0d 100644 --- a/src/applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php +++ b/src/applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php @@ -3,6 +3,7 @@ final class HarbormasterBuildPlanDefaultViewCapability extends PhabricatorPolicyCapability { + // TODO: This is misspelled! See T13005. const CAPABILITY = 'harbomaster.plan.default.view'; public function getCapabilityName() { diff --git a/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php b/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php index a2b5e1f5cd..7ccb03c95b 100644 --- a/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php +++ b/src/applications/legalpad/xaction/LegalpadDocumentPreambleTransaction.php @@ -3,6 +3,7 @@ final class LegalpadDocumentPreambleTransaction extends LegalpadDocumentTransactionType { + // TODO: This is misspelled! See T13005. const TRANSACTIONTYPE = 'legalpad:premable'; public function generateOldValue($object) { From 821c7ac8334f54bcb3f290ae91f44421ce232716 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 9 Oct 2017 17:01:56 -0700 Subject: [PATCH 225/865] For backup persitsence, mark the "common ngrams" table as a data table, not an index table Summary: Ref T13000. Garbage collecting common ngrams is slow because MySQL isn't all that great at deleting rows quickly. See PHI96, where it looks like it's going to take a week to GC ngrams for a ~million objects at a relatively conservative 0.15 threshold. In the event of a restore, we can reduce the impact by persisting this table so the ngrams just don't get built when the reindex happens. Test Plan: Viewed schema in Config, saw common ngrams tables marked as "Data" instead of "Index". Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18696 --- .../config/schema/PhabricatorConfigSchemaSpec.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index c451658682..8e67391b59 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -81,12 +81,15 @@ protected function buildFerretIndexSchema(PhabricatorFerretEngine $engine) { $engine->getNgramsSchemaKeys(), $index_options); + // NOTE: The common ngrams table is not marked as an index table. It is + // tiny and persisting it across a restore saves us a lot of work garbage + // collecting common ngrams from the index after it gets built. + $this->buildRawSchema( $engine->getApplicationName(), $engine->getCommonNgramsTableName(), $engine->getCommonNgramsSchemaColumns(), - $engine->getCommonNgramsSchemaKeys(), - $index_options); + $engine->getCommonNgramsSchemaKeys()); } protected function buildRawSchema( From 9777c66576ffef33030663c4badc29af74e152d7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 10 Oct 2017 15:13:56 -0700 Subject: [PATCH 226/865] Allow Phabricator to run with "enable_post_data_reading" disabled Summary: Ref T13008. Depends on D18701. The overall goal here is to make turning `enable_post_data_reading` off not break things, so we can run rate limiting checks before we read file uploads. The biggest blocker for this is that turning it off stops `$_FILES` from coming into existence. This //appears// to mostly work. Specifically: - Skip the `max_post_size` check when POST is off, since it's meaningless. - Don't read or scrub $_POST at startup when POST is off. - When we rebuild REQUEST and POST before processing requests, do multipart parsing if we need to and rebuild FILES. - Skip the `is_uploaded_file()` check if we built FILES ourselves. This probably breaks a couple of small things, like maybe `__profile__` and other DarkConsole triggers over POST, and probably some other weird stuff. The parsers may also need more work than they've received so far. I also need to verify that this actually works (i.e., lets us run code without reading the request body) but I'll include that in the change where I update the actual rate limiting. Test Plan: - Disabled `enable_post_data_reading`. - Uploaded a file with a vanilla upload form (project profile image). - Uploaded a file with drag and drop. - Used DarkConsole. - Submitted comments. - Created a task. - Browsed around. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13008 Differential Revision: https://secure.phabricator.com/D18702 --- ...AphrontDefaultApplicationConfiguration.php | 77 ++++++++++++++++--- .../files/storage/PhabricatorFile.php | 14 +++- support/PhabricatorStartup.php | 34 +++++--- 3 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php index be23895bec..fb67919576 100644 --- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php @@ -15,26 +15,79 @@ public function buildRequest() { $parser = new PhutilQueryStringParser(); $data = array(); - // If the request has "multipart/form-data" content, we can't use - // PhutilQueryStringParser to parse it, and the raw data supposedly is not - // available anyway (according to the PHP documentation, "php://input" is - // not available for "multipart/form-data" requests). However, it is - // available at least some of the time (see T3673), so double check that - // we aren't trying to parse data we won't be able to parse correctly by - // examining the Content-Type header. - $content_type = idx($_SERVER, 'CONTENT_TYPE'); - $is_form_data = preg_match('@^multipart/form-data@i', $content_type); - $request_method = idx($_SERVER, 'REQUEST_METHOD'); if ($request_method === 'PUT') { // For PUT requests, do nothing: in particular, do NOT read input. This // allows us to stream input later and process very large PUT requests, // like those coming from Git LFS. } else { + // For POST requests, we're going to read the raw input ourselves here + // if we can. Among other things, this corrects variable names with + // the "." character in them, which PHP normally converts into "_". + + // There are two major considerations here: whether the + // `enable_post_data_reading` option is set, and whether the content + // type is "multipart/form-data" or not. + + // If `enable_post_data_reading` is off, we're free to read the entire + // raw request body and parse it -- and we must, because $_POST and + // $_FILES are not built for us. If `enable_post_data_reading` is on, + // which is the default, we may not be able to read the body (the + // documentation says we can't, but empirically we can at least some + // of the time). + + // If the content type is "multipart/form-data", we need to build both + // $_POST and $_FILES, which is involved. The body itself is also more + // difficult to parse than other requests. $raw_input = PhabricatorStartup::getRawInput(); - if (strlen($raw_input) && !$is_form_data) { - $data += $parser->parseQueryString($raw_input); + if (strlen($raw_input)) { + $content_type = idx($_SERVER, 'CONTENT_TYPE'); + $is_multipart = preg_match('@^multipart/form-data@i', $content_type); + if ($is_multipart && !ini_get('enable_post_data_reading')) { + $multipart_parser = id(new AphrontMultipartParser()) + ->setContentType($content_type); + + $multipart_parser->beginParse(); + $multipart_parser->continueParse($raw_input); + $parts = $multipart_parser->endParse(); + + $query_string = array(); + foreach ($parts as $part) { + if (!$part->isVariable()) { + continue; + } + + $name = $part->getName(); + $value = $part->getVariableValue(); + + $query_string[] = urlencode($name).'='.urlencode($value); + } + $query_string = implode('&', $query_string); + $post = $parser->parseQueryString($query_string); + + $files = array(); + foreach ($parts as $part) { + if ($part->isVariable()) { + continue; + } + + $files[$part->getName()] = $part->getPHPFileDictionary(); + } + $_FILES = $files; + } else { + $post = $parser->parseQueryString($raw_input); + } + + $_POST = $post; + PhabricatorStartup::rebuildRequest(); + + $data += $post; } else if ($_POST) { + $post = filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW); + if (is_array($post)) { + $_POST = $post; + PhabricatorStartup::rebuildRequest(); + } $data += $_POST; } } diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 19f0bed90a..9636b5b017 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -178,9 +178,17 @@ public static function readUploadedFileData($spec) { } $tmp_name = idx($spec, 'tmp_name'); - $is_valid = @is_uploaded_file($tmp_name); - if (!$is_valid) { - throw new Exception(pht('File is not an uploaded file.')); + + // NOTE: If we parsed the request body ourselves, the files we wrote will + // not be registered in the `is_uploaded_file()` list. It's fine to skip + // this check: it just protects against sloppy code from the long ago era + // of "register_globals". + + if (ini_get('enable_post_data_reading')) { + $is_valid = @is_uploaded_file($tmp_name); + if (!$is_valid) { + throw new Exception(pht('File is not an uploaded file.')); + } } $file_data = Filesystem::readFile($tmp_name); diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index fd286b2aa0..3d771a24f7 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -416,9 +416,12 @@ private static function normalizeInput() { // NOTE: We don't filter INPUT_SERVER because we don't want to overwrite // changes made in "preamble.php". + + // NOTE: WE don't filter INPUT_POST because we may be constructing it + // lazily if "enable_post_data_reading" is disabled. + $filter = array( INPUT_GET, - INPUT_POST, INPUT_ENV, INPUT_COOKIE, ); @@ -434,9 +437,6 @@ private static function normalizeInput() { case INPUT_COOKIE: $_COOKIE = array_merge($_COOKIE, $filtered); break; - case INPUT_POST: - $_POST = array_merge($_POST, $filtered); - break; case INPUT_ENV; $env = array_merge($_ENV, $filtered); $_ENV = self::filterEnvSuperglobal($env); @@ -444,18 +444,28 @@ private static function normalizeInput() { } } - // rebuild $_REQUEST, respecting order declared in ini files + self::rebuildRequest(); + } + + /** + * @task validation + */ + public static function rebuildRequest() { + // Rebuild $_REQUEST, respecting order declared in ".ini" files. $order = ini_get('request_order'); + if (!$order) { $order = ini_get('variables_order'); } + if (!$order) { - // $_REQUEST will be empty, leave it alone + // $_REQUEST will be empty, so leave it alone. return; } + $_REQUEST = array(); - for ($i = 0; $i < strlen($order); $i++) { - switch ($order[$i]) { + for ($ii = 0; $ii < strlen($order); $ii++) { + switch ($order[$ii]) { case 'G': $_REQUEST = array_merge($_REQUEST, $_GET); break; @@ -466,7 +476,7 @@ private static function normalizeInput() { $_REQUEST = array_merge($_REQUEST, $_COOKIE); break; default: - // $_ENV and $_SERVER never go into $_REQUEST + // $_ENV and $_SERVER never go into $_REQUEST. break; } } @@ -593,6 +603,12 @@ private static function detectPostMaxSizeTriggered() { return; } + // If "enable_post_data_reading" is off, we won't have $_POST and this + // condition is effectively impossible. + if (!ini_get('enable_post_data_reading')) { + return; + } + // If there's POST data, clearly we're in good shape. if ($_POST) { return; From 3f53718d107f0fc96ca9a189e669c5a7f3d64cc4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 11 Oct 2017 14:23:09 -0700 Subject: [PATCH 227/865] Modularize rate/connection limits in Phabricator Summary: Depends on D18702. Ref T13008. This replaces the old hard-coded single rate limit with multiple flexible limits, and defines two types of limits: - Rate: reject requests if a client has completed too many requests recently. - Connection: reject requests if a client has too many more connections than disconnections recently. The connection limit adds +1 to the score for each connection, then adds -1 for each disconnection. So the overall number is how many open connections they have, at least approximately. Supporting multiple limits will let us do limiting by Hostname and by remote address (e.g., a specific IP can't exceed a low limit, and all requests to a hostname can't exceed a higher limit). Configuring the new limits looks something like this: ``` PhabricatorStartup::addRateLimit(new PhabricatorClientRateLimit()) ->setLimitKey('rate') ->setClientKey($_SERVER['REMOTE_ADDR']) ->setLimit(5); PhabricatorStartup::addRateLimit(new PhabricatorClientConnectionLimit()) ->setLimitKey('conn') ->setClientKey($_SERVER['REMOTE_ADDR']) ->setLimit(2); ``` Test Plan: - Configured limits as above. - Made a lot of requests, got cut off by the rate limit. - Used `curl --limit-rate -F 'data=@the_letter_m.txt' ...` to upload files really slowly. Got cut off by the connection limit. With `enable_post_data_reading` off, this correctly killed the connections //before// the uploads finished. - I'll send this stuff to `secure` before production to give it more of a chance. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13008 Differential Revision: https://secure.phabricator.com/D18703 --- .../AphrontApplicationConfiguration.php | 18 +- .../PhabricatorAccessControlTestCase.php | 2 +- .../PhabricatorClientConnectionLimit.php | 44 +++ support/startup/PhabricatorClientLimit.php | 291 ++++++++++++++++++ .../startup/PhabricatorClientRateLimit.php | 58 ++++ support/{ => startup}/PhabricatorStartup.php | 289 +++-------------- webroot/index.php | 7 +- 7 files changed, 441 insertions(+), 268 deletions(-) create mode 100644 support/startup/PhabricatorClientConnectionLimit.php create mode 100644 support/startup/PhabricatorClientLimit.php create mode 100644 support/startup/PhabricatorClientRateLimit.php rename support/{ => startup}/PhabricatorStartup.php (73%) diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index c0cd259992..60b12557c9 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -204,20 +204,10 @@ public static function runHTTPRequest(AphrontHTTPSink $sink) { DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log); - // Add points to the rate limits for this request. - $rate_token = PhabricatorStartup::getRateLimitToken(); - if ($rate_token !== null) { - // The base score for a request allows users to make 30 requests per - // minute. - $score = (1000 / 30); - - // If the user was logged in, let them make more requests. - if ($request->getUser() && $request->getUser()->getPHID()) { - $score = $score / 5; - } - - PhabricatorStartup::addRateLimitScore($rate_token, $score); - } + PhabricatorStartup::disconnectRateLimits( + array( + 'viewer' => $request->getUser(), + )); if ($processing_exception) { throw $processing_exception; diff --git a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php index 27e716556e..b6c268d88c 100644 --- a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php +++ b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php @@ -10,7 +10,7 @@ protected function getPhabricatorTestCaseConfiguration() { public function testControllerAccessControls() { $root = dirname(phutil_get_library_root('phabricator')); - require_once $root.'/support/PhabricatorStartup.php'; + require_once $root.'/support/startup/PhabricatorStartup.php'; $application_configuration = new AphrontDefaultApplicationConfiguration(); diff --git a/support/startup/PhabricatorClientConnectionLimit.php b/support/startup/PhabricatorClientConnectionLimit.php new file mode 100644 index 0000000000..4a66d05739 --- /dev/null +++ b/support/startup/PhabricatorClientConnectionLimit.php @@ -0,0 +1,44 @@ + $this->getLimit()); + } + + protected function getConnectScore() { + return 1; + } + + protected function getPenaltyScore() { + return 0; + } + + protected function getDisconnectScore(array $request_state) { + return -1; + } + + protected function getRateLimitReason($score) { + $client_key = $this->getClientKey(); + + // NOTE: This happens before we load libraries, so we can not use pht() + // here. + + return + "TOO MANY CONCURRENT CONNECTIONS\n". + "You (\"{$client_key}\") have too many concurrent ". + "connections.\n"; + } + +} diff --git a/support/startup/PhabricatorClientLimit.php b/support/startup/PhabricatorClientLimit.php new file mode 100644 index 0000000000..bab90c6f15 --- /dev/null +++ b/support/startup/PhabricatorClientLimit.php @@ -0,0 +1,291 @@ +limitKey = $limit_key; + return $this; + } + + final public function getLimitKey() { + return $this->limitKey; + } + + final public function setClientKey($client_key) { + $this->clientKey = $client_key; + return $this; + } + + final public function getClientKey() { + return $this->clientKey; + } + + final public function setLimit($limit) { + $this->limit = $limit; + return $this; + } + + final public function getLimit() { + return $this->limit; + } + + final public function didConnect() { + // NOTE: We can not use pht() here because this runs before libraries + // load. + + if (!function_exists('apc_fetch') && !function_exists('apcu_fetch')) { + throw new Exception( + 'You can not configure connection rate limits unless APC/APCu are '. + 'available. Rate limits rely on APC/APCu to track clients and '. + 'connections.'); + } + + if ($this->getClientKey() === null) { + throw new Exception( + 'You must configure a client key when defining a rate limit.'); + } + + if ($this->getLimitKey() === null) { + throw new Exception( + 'You must configure a limit key when defining a rate limit.'); + } + + if ($this->getLimit() === null) { + throw new Exception( + 'You must configure a limit when defining a rate limit.'); + } + + $points = $this->getConnectScore(); + if ($points) { + $this->addScore($points); + } + + $score = $this->getScore(); + if (!$this->shouldRejectConnection($score)) { + // Client has not hit the limit, so continue processing the request. + return null; + } + + $penalty = $this->getPenaltyScore(); + if ($penalty) { + $this->addScore($penalty); + $score += $penalty; + } + + return $this->getRateLimitReason($score); + } + + final public function didDisconnect(array $request_state) { + $score = $this->getDisconnectScore($request_state); + if ($score) { + $this->addScore($score); + } + } + + + /** + * Get the number of seconds for each rate bucket. + * + * For example, a value of 60 will create one-minute buckets. + * + * @return int Number of seconds per bucket. + */ + abstract protected function getBucketDuration(); + + + /** + * Get the total number of rate limit buckets to retain. + * + * @return int Total number of rate limit buckets to retain. + */ + abstract protected function getBucketCount(); + + + /** + * Get the score to add when a client connects. + * + * @return double Connection score. + */ + abstract protected function getConnectScore(); + + + /** + * Get the number of penalty points to add when a client hits a rate limit. + * + * @return double Penalty score. + */ + abstract protected function getPenaltyScore(); + + + /** + * Get the score to add when a client disconnects. + * + * @return double Connection score. + */ + abstract protected function getDisconnectScore(array $request_state); + + + /** + * Get a human-readable explanation of why the client is being rejected. + * + * @return string Brief rejection message. + */ + abstract protected function getRateLimitReason($score); + + + /** + * Determine whether to reject a connection. + * + * @return bool True to reject the connection. + */ + abstract protected function shouldRejectConnection($score); + + + /** + * Get the APC key for the smallest stored bucket. + * + * @return string APC key for the smallest stored bucket. + * @task ratelimit + */ + private function getMinimumBucketCacheKey() { + $limit_key = $this->getLimitKey(); + return "limit:min:{$limit_key}"; + } + + + /** + * Get the current bucket ID for storing rate limit scores. + * + * @return int The current bucket ID. + */ + private function getCurrentBucketID() { + return (int)(time() / $this->getBucketDuration()); + } + + + /** + * Get the APC key for a given bucket. + * + * @param int Bucket to get the key for. + * @return string APC key for the bucket. + */ + private function getBucketCacheKey($bucket_id) { + $limit_key = $this->getLimitKey(); + return "limit:bucket:{$limit_key}:{$bucket_id}"; + } + + + /** + * Add points to the rate limit score for some client. + * + * @param string Some key which identifies the client making the request. + * @param float The cost for this request; more points pushes them toward + * the limit faster. + * @return this + */ + private function addScore($score) { + $is_apcu = (bool)function_exists('apcu_fetch'); + + $current = $this->getCurrentBucketID(); + $bucket_key = $this->getBucketCacheKey($current); + + // There's a bit of a race here, if a second process reads the bucket + // before this one writes it, but it's fine if we occasionally fail to + // record a client's score. If they're making requests fast enough to hit + // rate limiting, we'll get them soon enough. + + if ($is_apcu) { + $bucket = apcu_fetch($bucket_key); + } else { + $bucket = apc_fetch($bucket_key); + } + + if (!is_array($bucket)) { + $bucket = array(); + } + + $client_key = $this->getClientKey(); + if (empty($bucket[$client_key])) { + $bucket[$client_key] = 0; + } + + $bucket[$client_key] += $score; + + if ($is_apcu) { + apcu_store($bucket_key, $bucket); + } else { + apc_store($bucket_key, $bucket); + } + + return $this; + } + + + /** + * Get the current rate limit score for a given client. + * + * @return float The client's current score. + * @task ratelimit + */ + private function getScore() { + $is_apcu = (bool)function_exists('apcu_fetch'); + + // Identify the oldest bucket stored in APC. + $min_key = $this->getMinimumBucketCacheKey(); + if ($is_apcu) { + $min = apcu_fetch($min_key); + } else { + $min = apc_fetch($min_key); + } + + // If we don't have any buckets stored yet, store the current bucket as + // the oldest bucket. + $cur = $this->getCurrentBucketID(); + if (!$min) { + if ($is_apcu) { + apcu_store($min_key, $cur); + } else { + apc_store($min_key, $cur); + } + $min = $cur; + } + + // Destroy any buckets that are older than the minimum bucket we're keeping + // track of. Under load this normally shouldn't do anything, but will clean + // up an old bucket once per minute. + $count = $this->getBucketCount(); + for ($cursor = $min; $cursor < ($cur - $count); $cursor++) { + $bucket_key = $this->getBucketCacheKey($cursor); + if ($is_apcu) { + apcu_delete($bucket_key); + apcu_store($min_key, $cursor + 1); + } else { + apc_delete($bucket_key); + apc_store($min_key, $cursor + 1); + } + } + + $client_key = $this->getClientKey(); + + // Now, sum up the client's scores in all of the active buckets. + $score = 0; + for (; $cursor <= $cur; $cursor++) { + $bucket_key = $this->getBucketCacheKey($cursor); + if ($is_apcu) { + $bucket = apcu_fetch($bucket_key); + } else { + $bucket = apc_fetch($bucket_key); + } + if (isset($bucket[$client_key])) { + $score += $bucket[$client_key]; + } + } + + return $score; + } + +} diff --git a/support/startup/PhabricatorClientRateLimit.php b/support/startup/PhabricatorClientRateLimit.php new file mode 100644 index 0000000000..85a6def878 --- /dev/null +++ b/support/startup/PhabricatorClientRateLimit.php @@ -0,0 +1,58 @@ +getLimit(); + + // Reject connections if the average score across all buckets exceeds the + // limit. + $average_score = $score / $this->getBucketCount(); + + return ($average_score > $limit); + } + + protected function getConnectScore() { + return 0; + } + + protected function getPenaltyScore() { + return 1; + } + + protected function getDisconnectScore(array $request_state) { + $score = 1; + + // If the user was logged in, let them make more requests. + if (isset($request_state['viewer'])) { + $viewer = $request_state['viewer']; + if ($viewer->isLoggedIn()) { + $score = 0.25; + } + } + + return $score; + } + + protected function getRateLimitReason($score) { + $client_key = $this->getClientKey(); + + // NOTE: This happens before we load libraries, so we can not use pht() + // here. + + return + "TOO MANY REQUESTS\n". + "You (\"{$client_key}\") are issuing too many requests ". + "too quickly.\n"; + } + +} diff --git a/support/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php similarity index 73% rename from support/PhabricatorStartup.php rename to support/startup/PhabricatorStartup.php index 3d771a24f7..41164fbd84 100644 --- a/support/PhabricatorStartup.php +++ b/support/startup/PhabricatorStartup.php @@ -46,11 +46,7 @@ final class PhabricatorStartup { private static $oldMemoryLimit; private static $phases; - // TODO: For now, disable rate limiting entirely by default. We need to - // iterate on it a bit for Conduit, some of the specific score levels, and - // to deal with NAT'd offices. - private static $maximumRate = 0; - private static $rateLimitToken; + private static $limits = array(); /* -( Accessing Request Information )-------------------------------------- */ @@ -138,10 +134,7 @@ public static function didStartup($start_time) { // we can switch over to relying on our own exception recovery mechanisms. ini_set('display_errors', 0); - $rate_token = self::getRateLimitToken(); - if ($rate_token !== null) { - self::rateLimitRequest($rate_token); - } + self::connectRateLimits(); self::normalizeInput(); @@ -193,7 +186,7 @@ public static function didShutdown() { } public static function loadCoreLibraries() { - $phabricator_root = dirname(dirname(__FILE__)); + $phabricator_root = dirname(dirname(dirname(__FILE__))); $libraries_root = dirname($phabricator_root); $root = null; @@ -683,269 +676,66 @@ private static function detectPostMaxSizeTriggered() { /** - * Adjust the permissible rate limit score. - * - * By default, the limit is `1000`. You can use this method to set it to - * a larger or smaller value. If you set it to `2000`, users may make twice - * as many requests before rate limiting. - * - * @param int Maximum score before rate limiting. - * @return void - * @task ratelimit - */ - public static function setMaximumRate($rate) { - self::$maximumRate = $rate; - } - - - /** - * Set a token to identify the client for purposes of rate limiting. - * - * By default, the `REMOTE_ADDR` is used. If your install is behind a load - * balancer, you may want to parse `X-Forwarded-For` and use that address - * instead. - * - * @param string Client identity for rate limiting. - */ - public static function setRateLimitToken($token) { - self::$rateLimitToken = $token; - } - - - /** - * Get the current client identity for rate limiting. - */ - public static function getRateLimitToken() { - if (self::$rateLimitToken !== null) { - return self::$rateLimitToken; - } - - if (isset($_SERVER['REMOTE_ADDR'])) { - return $_SERVER['REMOTE_ADDR']; - } - - return null; - } - - - /** - * Check if the user (identified by `$user_identity`) has issued too many - * requests recently. If they have, end the request with a 429 error code. - * - * The key just needs to identify the user. Phabricator uses both user PHIDs - * and user IPs as keys, tracking logged-in and logged-out users separately - * and enforcing different limits. + * Add a new client limits. * - * @param string Some key which identifies the user making the request. - * @return void If the user has exceeded the rate limit, this method - * does not return. - * @task ratelimit + * @param PhabricatorClientLimit New limit. + * @return PhabricatorClientLimit The limit. */ - public static function rateLimitRequest($user_identity) { - if (!self::canRateLimit()) { - return; - } - - $score = self::getRateLimitScore($user_identity); - $limit = self::$maximumRate * self::getRateLimitBucketCount(); - if ($score > $limit) { - // Give the user some bonus points for getting rate limited. This keeps - // bad actors who keep slamming the 429 page locked out completely, - // instead of letting them get a burst of requests through every minute - // after a bucket expires. - $penalty = 50; - - self::addRateLimitScore($user_identity, $penalty); - $score += $penalty; - - self::didRateLimit($user_identity, $score, $limit); - } + public static function addRateLimit(PhabricatorClientLimit $limit) { + self::$limits[] = $limit; + return $limit; } /** - * Add points to the rate limit score for some user. + * Apply configured rate limits. * - * If users have earned more than 1000 points per minute across all the - * buckets they'll be locked out of the application, so awarding 1 point per - * request roughly corresponds to allowing 1000 requests per second, while - * awarding 50 points roughly corresponds to allowing 20 requests per second. + * If any limit is exceeded, this method terminates the request. * - * @param string Some key which identifies the user making the request. - * @param float The cost for this request; more points pushes them toward - * the limit faster. * @return void * @task ratelimit */ - public static function addRateLimitScore($user_identity, $score) { - if (!self::canRateLimit()) { - return; - } - - $is_apcu = (bool)function_exists('apcu_fetch'); - $current = self::getRateLimitBucket(); - - // There's a bit of a race here, if a second process reads the bucket - // before this one writes it, but it's fine if we occasionally fail to - // record a user's score. If they're making requests fast enough to hit - // rate limiting, we'll get them soon enough. + private static function connectRateLimits() { + $limits = self::$limits; - $bucket_key = self::getRateLimitBucketKey($current); - if ($is_apcu) { - $bucket = apcu_fetch($bucket_key); - } else { - $bucket = apc_fetch($bucket_key); - } - - if (!is_array($bucket)) { - $bucket = array(); - } - - if (empty($bucket[$user_identity])) { - $bucket[$user_identity] = 0; + $reason = null; + $connected = array(); + foreach ($limits as $limit) { + $reason = $limit->didConnect(); + $connected[] = $limit; + if ($reason !== null) { + break; + } } - $bucket[$user_identity] += $score; + // If we're killing the request here, disconnect any limits that we + // connected to try to keep the accounting straight. + if ($reason !== null) { + foreach ($connected as $limit) { + $limit->didDisconnect(array()); + } - if ($is_apcu) { - apcu_store($bucket_key, $bucket); - } else { - apc_store($bucket_key, $bucket); + self::didRateLimit($reason); } } /** - * Determine if rate limiting is available. - * - * Rate limiting depends on APC, and isn't available unless the APC user - * cache is available. + * Tear down rate limiting and allow limits to score the request. * - * @return bool True if rate limiting is available. + * @param map Additional, freeform request state. + * @return void * @task ratelimit */ - private static function canRateLimit() { + public static function disconnectRateLimits(array $request_state) { + $limits = self::$limits; - if (!self::$maximumRate) { - return false; + foreach ($limits as $limit) { + $limit->didDisconnect($request_state); } - - if (!function_exists('apc_fetch') && !function_exists('apcu_fetch')) { - return false; - } - - return true; } - /** - * Get the current bucket for storing rate limit scores. - * - * @return int The current bucket. - * @task ratelimit - */ - private static function getRateLimitBucket() { - return (int)(time() / 60); - } - - - /** - * Get the total number of rate limit buckets to retain. - * - * @return int Total number of rate limit buckets to retain. - * @task ratelimit - */ - private static function getRateLimitBucketCount() { - return 5; - } - - - /** - * Get the APC key for a given bucket. - * - * @param int Bucket to get the key for. - * @return string APC key for the bucket. - * @task ratelimit - */ - private static function getRateLimitBucketKey($bucket) { - return 'rate:bucket:'.$bucket; - } - - - /** - * Get the APC key for the smallest stored bucket. - * - * @return string APC key for the smallest stored bucket. - * @task ratelimit - */ - private static function getRateLimitMinKey() { - return 'rate:min'; - } - - - /** - * Get the current rate limit score for a given user. - * - * @param string Unique key identifying the user. - * @return float The user's current score. - * @task ratelimit - */ - private static function getRateLimitScore($user_identity) { - $is_apcu = (bool)function_exists('apcu_fetch'); - - $min_key = self::getRateLimitMinKey(); - - // Identify the oldest bucket stored in APC. - $cur = self::getRateLimitBucket(); - if ($is_apcu) { - $min = apcu_fetch($min_key); - } else { - $min = apc_fetch($min_key); - } - - // If we don't have any buckets stored yet, store the current bucket as - // the oldest bucket. - if (!$min) { - if ($is_apcu) { - apcu_store($min_key, $cur); - } else { - apc_store($min_key, $cur); - } - $min = $cur; - } - - // Destroy any buckets that are older than the minimum bucket we're keeping - // track of. Under load this normally shouldn't do anything, but will clean - // up an old bucket once per minute. - $count = self::getRateLimitBucketCount(); - for ($cursor = $min; $cursor < ($cur - $count); $cursor++) { - $bucket_key = self::getRateLimitBucketKey($cursor); - if ($is_apcu) { - apcu_delete($bucket_key); - apcu_store($min_key, $cursor + 1); - } else { - apc_delete($bucket_key); - apc_store($min_key, $cursor + 1); - } - } - - // Now, sum up the user's scores in all of the active buckets. - $score = 0; - for (; $cursor <= $cur; $cursor++) { - $bucket_key = self::getRateLimitBucketKey($cursor); - if ($is_apcu) { - $bucket = apcu_fetch($bucket_key); - } else { - $bucket = apc_fetch($bucket_key); - } - if (isset($bucket[$user_identity])) { - $score += $bucket[$user_identity]; - } - } - - return $score; - } - /** * Emit an HTTP 429 "Too Many Requests" response (indicating that the user @@ -954,18 +744,13 @@ private static function getRateLimitScore($user_identity) { * @return exit This method **does not return**. * @task ratelimit */ - private static function didRateLimit($user_identity, $score, $limit) { - $message = - "TOO MANY REQUESTS\n". - "You (\"{$user_identity}\") are issuing too many requests ". - "too quickly.\n"; - + private static function didRateLimit($reason) { header( 'Content-Type: text/plain; charset=utf-8', $replace = true, $http_error = 429); - echo $message; + echo $reason; exit(1); } diff --git a/webroot/index.php b/webroot/index.php index 59e5b71075..5c7d79bfa1 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -37,7 +37,12 @@ function phabricator_startup() { // Load the PhabricatorStartup class itself. $t_startup = microtime(true); $root = dirname(dirname(__FILE__)); - require_once $root.'/support/PhabricatorStartup.php'; + require_once $root.'/support/startup/PhabricatorStartup.php'; + + // Load client limit classes so the preamble can configure limits. + require_once $root.'/support/startup/PhabricatorClientLimit.php'; + require_once $root.'/support/startup/PhabricatorClientRateLimit.php'; + require_once $root.'/support/startup/PhabricatorClientConnectionLimit.php'; // If the preamble script exists, load it. $t_preamble = microtime(true); From acb145b3d7adb3d4ce986ccfa9ead4fe90a64641 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 12 Oct 2017 16:26:53 -0700 Subject: [PATCH 228/865] Allow duplicates and merged-in tasks to be queried with `edge.search` Summary: See PHI147. Test Plan: Called the method from the web UI, got sensible results. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18706 --- .../ManiphestTaskHasDuplicateTaskEdgeType.php | 14 ++++++++++++++ .../ManiphestTaskIsDuplicateOfTaskEdgeType.php | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php b/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php index b5f5b44ae3..6d41689ba4 100644 --- a/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php +++ b/src/applications/maniphest/edge/ManiphestTaskHasDuplicateTaskEdgeType.php @@ -13,4 +13,18 @@ public function shouldWriteInverseTransactions() { return true; } + public function getConduitKey() { + return 'task.merged-in'; + } + + public function getConduitName() { + return pht('Merged In'); + } + + public function getConduitDescription() { + return pht( + 'The source task has had the destination task closed as a '. + 'duplicate and merged into it.'); + } + } diff --git a/src/applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php b/src/applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php index 8c0e2a752d..3fafadc0f4 100644 --- a/src/applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php +++ b/src/applications/maniphest/edge/ManiphestTaskIsDuplicateOfTaskEdgeType.php @@ -13,4 +13,19 @@ public function shouldWriteInverseTransactions() { return true; } + public function getConduitKey() { + return 'task.duplicate'; + } + + public function getConduitName() { + return pht('Closed as Duplicate'); + } + + public function getConduitDescription() { + return pht( + 'The source task has been closed as a duplicate of the '. + 'destination task.'); + } + + } From c5e8de945020ea0fe08f6c8f00784b1fc91a3d58 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 13 Oct 2017 13:38:47 -0700 Subject: [PATCH 229/865] Make `bin/storage dump` insert CREATE DATABASE and USE statements Summary: Ref T13000. The new approach for dumping database-by-database means that we don't get CREATE DATABASE or USE statements, which makes importing the dump again inconvenient. Manually stitch these into the dump. Test Plan: - Used `bin/storage dump --namespace ...` to dump a smaller local instance. - Used `bin/storage destroy --namespace ...`, to destroy the namespace, then inported the dump cleanly. - Verified that each CREATE DATABASE statement appears only once. - Verified that `bin/storage renamespace --live` can correctly process this file. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18707 --- ...abricatorStorageManagementDumpWorkflow.php | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index 17491d55e9..88bd80a9a8 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -215,7 +215,10 @@ public function didExecute(PhutilArgumentParser $args) { $target['table']); } - $commands[] = $command; + $commands[] = array( + 'command' => $command, + 'database' => $target['database'], + ); } @@ -244,9 +247,26 @@ public function didExecute(PhutilArgumentParser $args) { $file)); } + $created = array(); + try { - foreach ($commands as $command) { - $future = new ExecFuture('%C', $command); + foreach ($commands as $spec) { + // Because we're dumping database-by-database, we need to generate our + // own CREATE DATABASE and USE statements. + + $database = $spec['database']; + $preamble = array(); + if (!isset($created[$database])) { + $preamble[] = + "CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{$database}` ". + "/*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */;\n"; + $created[$database] = true; + } + $preamble[] = "USE `{$database}`;\n"; + $preamble = implode('', $preamble); + $this->writeData($preamble, $file, $is_compress, $output_file); + + $future = new ExecFuture('%C', $spec['command']); $iterator = id(new FutureIterator(array($future))) ->setUpdateInterval(0.100); @@ -258,23 +278,7 @@ public function didExecute(PhutilArgumentParser $args) { fwrite(STDERR, $stderr); } - if (strlen($stdout)) { - if (!$file) { - $ok = fwrite(STDOUT, $stdout); - } else if ($is_compress) { - $ok = gzwrite($file, $stdout); - } else { - $ok = fwrite($file, $stdout); - } - - if ($ok !== strlen($stdout)) { - throw new Exception( - pht( - 'Failed to write %d byte(s) to file "%s".', - new PhutilNumber(strlen($stdout)), - $output_file)); - } - } + $this->writeData($stdout, $file, $is_compress, $output_file); if ($ready !== null) { $ready->resolvex(); @@ -314,4 +318,27 @@ public function didExecute(PhutilArgumentParser $args) { return 0; } + + private function writeData($data, $file, $is_compress, $output_file) { + if (!strlen($data)) { + return; + } + + if (!$file) { + $ok = fwrite(STDOUT, $data); + } else if ($is_compress) { + $ok = gzwrite($file, $data); + } else { + $ok = fwrite($file, $data); + } + + if ($ok !== strlen($data)) { + throw new Exception( + pht( + 'Failed to write %d byte(s) to file "%s".', + new PhutilNumber(strlen($data)), + $output_file)); + } + } + } From 0e645b8f11d2ff3430c3f04cf01be4c30e803f01 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 14 Oct 2017 07:14:31 -0700 Subject: [PATCH 230/865] Disconnect rate limits in the PhabricatorStartup shutdown handler This makes counts more accurate, particularly for connection limits. --- support/startup/PhabricatorStartup.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/support/startup/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php index 41164fbd84..1911a46b8a 100644 --- a/support/startup/PhabricatorStartup.php +++ b/support/startup/PhabricatorStartup.php @@ -150,6 +150,11 @@ public static function didStartup($start_time) { * @task hook */ public static function didShutdown() { + // Disconnect any active rate limits before we shut down. If we don't do + // this, requests which exit early will lock a slot in any active + // connection limits, and won't count for rate limits. + self::disconnectRateLimits(array()); + $event = error_get_last(); if (!$event) { @@ -730,6 +735,10 @@ private static function connectRateLimits() { public static function disconnectRateLimits(array $request_state) { $limits = self::$limits; + // Remove all limits before disconnecting them so this works properly if + // it runs twice. (We run this automatically as a shutdown handler.) + self::$limits = array(); + foreach ($limits as $limit) { $limit->didDisconnect($request_state); } From e21d603356fc4242143a045e436d42689343e7fa Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 14 Oct 2017 07:14:31 -0700 Subject: [PATCH 231/865] (stable) Disconnect rate limits in the PhabricatorStartup shutdown handler This makes counts more accurate, particularly for connection limits. --- support/startup/PhabricatorStartup.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/support/startup/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php index 41164fbd84..1911a46b8a 100644 --- a/support/startup/PhabricatorStartup.php +++ b/support/startup/PhabricatorStartup.php @@ -150,6 +150,11 @@ public static function didStartup($start_time) { * @task hook */ public static function didShutdown() { + // Disconnect any active rate limits before we shut down. If we don't do + // this, requests which exit early will lock a slot in any active + // connection limits, and won't count for rate limits. + self::disconnectRateLimits(array()); + $event = error_get_last(); if (!$event) { @@ -730,6 +735,10 @@ private static function connectRateLimits() { public static function disconnectRateLimits(array $request_state) { $limits = self::$limits; + // Remove all limits before disconnecting them so this works properly if + // it runs twice. (We run this automatically as a shutdown handler.) + self::$limits = array(); + foreach ($limits as $limit) { $limit->didDisconnect($request_state); } From 819b833607a6f9f05c60390a99cc1c206fde93e2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 14 Oct 2017 07:58:23 -0700 Subject: [PATCH 232/865] Tweak rate limiting point counts for omnipotent users Summary: Ref T13008. We haven't hit any issues with this, but I can imagine we might in the future. When one host makes an intracluster request to another host, the `$viewer` ends up as the omnipotent viewer. This viewer isn't logged in, so they'll currently accumulate rate limit points at a high rate. Instead, don't give them any points. These requests are always legitimate, and if they originated from a user request, that request should be the one getting rate limited. Test Plan: Browsed around. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13008 Differential Revision: https://secure.phabricator.com/D18708 --- support/startup/PhabricatorClientRateLimit.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/support/startup/PhabricatorClientRateLimit.php b/support/startup/PhabricatorClientRateLimit.php index 85a6def878..89a273e3bf 100644 --- a/support/startup/PhabricatorClientRateLimit.php +++ b/support/startup/PhabricatorClientRateLimit.php @@ -35,7 +35,15 @@ protected function getDisconnectScore(array $request_state) { // If the user was logged in, let them make more requests. if (isset($request_state['viewer'])) { $viewer = $request_state['viewer']; - if ($viewer->isLoggedIn()) { + if ($viewer->isOmnipotent()) { + // If the viewer was omnipotent, this was an intracluster request or + // some other kind of special request, so don't give it any points + // toward rate limiting. + $score = 0; + } else if ($viewer->isLoggedIn()) { + // If the viewer was logged in, give them fewer points than if they + // were logged out, since this traffic is much more likely to be + // legitimate. $score = 0.25; } } From 63d1230ade0f610729edc1eee99bd515213c1f31 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 17 Oct 2017 13:58:27 -0700 Subject: [PATCH 233/865] Parameterize the common ngrams threshold Summary: Ref T13000. Since other changes have generally made the ngrams table manageable, I'm not planning to enable common ngrams by default at this time. Instead, make the threshold configurable with "--threshold" so we can guide installs through tuning this if they want (e.g. PHI110), and tune hosted instances. (This might eventually become automatic, but just smoothing this bit off for now feels reasonable to me.) Test Plan: Ran with `--reset`, and with various invalid and valid `--threshold` arguments. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13000 Differential Revision: https://secure.phabricator.com/D18710 --- ...bricatorSearchManagementNgramsWorkflow.php | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php index 4b983fe0f9..ef0d73fa3d 100644 --- a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php @@ -16,19 +16,49 @@ protected function didConstruct() { 'name' => 'reset', 'help' => pht('Reset all common ngram records.'), ), + array( + 'name' => 'threshold', + 'param' => 'threshold', + 'help' => pht( + 'Prune ngrams present in more than this fraction of '. + 'documents.'), + ), )); } public function execute(PhutilArgumentParser $args) { + $min_documents = 4096; + $is_reset = $args->getArg('reset'); + $threshold = $args->getArg('threshold'); + + if ($is_reset && $threshold !== null) { + throw new PhutilArgumentUsageException( + pht('Specify either --reset or --threshold, not both.')); + } + + if (!$is_reset && $threshold === null) { + throw new PhutilArgumentUsageException( + pht('Specify either --reset or --threshold.')); + } + + if (!$is_reset) { + if (!is_numeric($threshold)) { + throw new PhutilArgumentUsageException( + pht('Specify a numeric threshold between 0 and 1.')); + } + + $threshold = (double)$threshold; + if ($threshold <= 0 || $threshold >= 1) { + throw new PhutilArgumentUsageException( + pht('Threshold must be greater than 0.0 and less than 1.0.')); + } + } $all_objects = id(new PhutilClassMapQuery()) ->setAncestorClass('PhabricatorFerretInterface') ->execute(); - $min_documents = 4096; - $threshold = 0.15; - foreach ($all_objects as $object) { $engine = $object->newFerretEngine(); $conn = $object->establishConnection('w'); From d36f98a15a6aab7cc403db0311f020174f9aa307 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 17 Oct 2017 14:29:05 -0700 Subject: [PATCH 234/865] Clarify acceptable values for `--threshold` in `search ngrams` Summary: See D18710. Test Plan: o_O Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18712 --- .../management/PhabricatorSearchManagementNgramsWorkflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php index ef0d73fa3d..1a1124b4d8 100644 --- a/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php +++ b/src/applications/search/management/PhabricatorSearchManagementNgramsWorkflow.php @@ -21,7 +21,7 @@ protected function didConstruct() { 'param' => 'threshold', 'help' => pht( 'Prune ngrams present in more than this fraction of '. - 'documents.'), + 'documents. Provide a value between 0.0 and 1.0.'), ), )); } From bfabe49c5ae88b62556238443e536c986a78d1f6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 19 Oct 2017 13:35:02 -0700 Subject: [PATCH 235/865] Start revisions in "Draft" if prototypes are enabled Summary: Ref T2543. This is a less ambitious version of the rule in D18628, which I backed off from, since I think this probably still has a fair number of loose ends to tie up. Test Plan: Created a revision locally. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18713 --- .../differential/storage/DifferentialRevision.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index e29db36b14..d14f69f563 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -67,13 +67,19 @@ public static function initializeNewRevision(PhabricatorUser $actor) { $view_policy = $app->getPolicy( DifferentialDefaultViewCapability::CAPABILITY); + if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + $initial_state = DifferentialRevisionStatus::DRAFT; + } else { + $initial_state = DifferentialRevisionStatus::NEEDS_REVIEW; + } + return id(new DifferentialRevision()) ->setViewPolicy($view_policy) ->setAuthorPHID($actor->getPHID()) ->attachRepository(null) ->attachActiveDiff(null) ->attachReviewers(array()) - ->setModernRevisionStatus(DifferentialRevisionStatus::NEEDS_REVIEW); + ->setModernRevisionStatus($initial_state); } protected function getConfiguration() { From 1755ec242989fabf1c27ab71f5364ce9be68a74a Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 19 Oct 2017 14:09:25 -0700 Subject: [PATCH 236/865] Show more detailed hints about draft revisions in the UI Summary: Ref T2543. When revisions are in the draft state, tell the user what we're waiting for or why they aren't moving forward. Test Plan: {F5228840} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18714 --- src/__phutil_library_map__.php | 2 + .../customfield/DifferentialDraftField.php | 90 +++++++++++++++++++ .../editor/DifferentialTransactionEditor.php | 48 +--------- .../storage/DifferentialRevision.php | 52 +++++++++++ 4 files changed, 145 insertions(+), 47 deletions(-) create mode 100644 src/applications/differential/customfield/DifferentialDraftField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a650e33fcf..93efcc65e1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -444,6 +444,7 @@ 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', + 'DifferentialDraftField' => 'applications/differential/customfield/DifferentialDraftField.php', 'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php', 'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php', 'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php', @@ -5451,6 +5452,7 @@ 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', + 'DifferentialDraftField' => 'DifferentialCoreCustomField', 'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DifferentialFieldParseException' => 'Exception', 'DifferentialFieldValidationException' => 'Exception', diff --git a/src/applications/differential/customfield/DifferentialDraftField.php b/src/applications/differential/customfield/DifferentialDraftField.php new file mode 100644 index 0000000000..e37d622a33 --- /dev/null +++ b/src/applications/differential/customfield/DifferentialDraftField.php @@ -0,0 +1,90 @@ +getViewer(); + $revision = $this->getObject(); + + if (!$revision->isDraft()) { + return array(); + } + + $warnings = array(); + + $blocking_map = array( + HarbormasterBuildStatus::STATUS_FAILED, + HarbormasterBuildStatus::STATUS_ABORTED, + HarbormasterBuildStatus::STATUS_ERROR, + HarbormasterBuildStatus::STATUS_PAUSED, + HarbormasterBuildStatus::STATUS_DEADLOCKED, + ); + $blocking_map = array_fuse($blocking_map); + + $builds = $revision->loadActiveBuilds($viewer); + + $waiting = array(); + $blocking = array(); + foreach ($builds as $build) { + if (isset($blocking_map[$build->getBuildStatus()])) { + $blocking[] = $build; + } else { + $waiting[] = $build; + } + } + + $blocking_list = $viewer->renderHandleList(mpull($blocking, 'getPHID')) + ->setAsInline(true); + $waiting_list = $viewer->renderHandleList(mpull($waiting, 'getPHID')) + ->setAsInline(true); + + if ($blocking) { + $warnings[] = pht( + 'This draft revision will not be submitted for review because %s '. + 'build(s) failed: %s.', + phutil_count($blocking), + $blocking_list); + $warnings[] = pht( + 'Fix build failures and update the revision.'); + } else if ($waiting) { + $warnings[] = pht( + 'This draft revision will be sent for review once %s '. + 'build(s) pass: %s.', + phutil_count($waiting), + $waiting_list); + } else { + $warnings[] = pht( + 'This is a draft revision that has not yet been submitted for '. + 'review.'); + } + + return $warnings; + } + +} diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 253d49c1ec..7c9bb01b07 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1570,58 +1570,12 @@ protected function didApplyTransactions($object, array $xactions) { private function hasActiveBuilds($object) { $viewer = $this->requireActor(); - $diff = $object->getActiveDiff(); - $buildables = id(new HarbormasterBuildableQuery()) - ->setViewer($viewer) - ->withContainerPHIDs(array($object->getPHID())) - ->withBuildablePHIDs(array($diff->getPHID())) - ->withManualBuildables(false) - ->execute(); - if (!$buildables) { - return false; - } - - $builds = id(new HarbormasterBuildQuery()) - ->setViewer($viewer) - ->withBuildablePHIDs(mpull($buildables, 'getPHID')) - ->withBuildStatuses( - array( - HarbormasterBuildStatus::STATUS_INACTIVE, - HarbormasterBuildStatus::STATUS_PENDING, - HarbormasterBuildStatus::STATUS_BUILDING, - HarbormasterBuildStatus::STATUS_FAILED, - HarbormasterBuildStatus::STATUS_ABORTED, - HarbormasterBuildStatus::STATUS_ERROR, - HarbormasterBuildStatus::STATUS_PAUSED, - HarbormasterBuildStatus::STATUS_DEADLOCKED, - )) - ->needBuildTargets(true) - ->execute(); + $builds = $object->loadActiveBuilds($viewer); if (!$builds) { return false; } - $active = array(); - foreach ($builds as $key => $build) { - foreach ($build->getBuildTargets() as $target) { - if ($target->isAutotarget()) { - // Ignore autotargets when looking for active of failed builds. If - // local tests fail and you continue anyway, you don't need to - // double-confirm them. - continue; - } - - // This build has at least one real target that's doing something. - $active[$key] = $build; - break; - } - } - - if (!$active) { - return false; - } - return true; } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index d14f69f563..76c7c4da95 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -708,6 +708,58 @@ public function shouldBroadcast() { return false; } + public function loadActiveBuilds(PhabricatorUser $viewer) { + $diff = $this->getActiveDiff(); + + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withContainerPHIDs(array($this->getPHID())) + ->withBuildablePHIDs(array($diff->getPHID())) + ->withManualBuildables(false) + ->execute(); + if (!$buildables) { + return array(); + } + + $builds = id(new HarbormasterBuildQuery()) + ->setViewer($viewer) + ->withBuildablePHIDs(mpull($buildables, 'getPHID')) + ->withBuildStatuses( + array( + HarbormasterBuildStatus::STATUS_INACTIVE, + HarbormasterBuildStatus::STATUS_PENDING, + HarbormasterBuildStatus::STATUS_BUILDING, + HarbormasterBuildStatus::STATUS_FAILED, + HarbormasterBuildStatus::STATUS_ABORTED, + HarbormasterBuildStatus::STATUS_ERROR, + HarbormasterBuildStatus::STATUS_PAUSED, + HarbormasterBuildStatus::STATUS_DEADLOCKED, + )) + ->needBuildTargets(true) + ->execute(); + if (!$builds) { + return array(); + } + + $active = array(); + foreach ($builds as $key => $build) { + foreach ($build->getBuildTargets() as $target) { + if ($target->isAutotarget()) { + // Ignore autotargets when looking for active of failed builds. If + // local tests fail and you continue anyway, you don't need to + // double-confirm them. + continue; + } + + // This build has at least one real target that's doing something. + $active[$key] = $build; + break; + } + } + + return $active; + } + /* -( HarbormasterBuildableInterface )------------------------------------- */ From 01b1854fb45c3fda896362bc565ba09300e59ccd Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 07:40:11 -0700 Subject: [PATCH 237/865] Fix an issue with attempting to index comments on packages Summary: See rPcd14194a329788d5fff6365bcade278fd18f3612 for a similar change. Implement `getApplicationTransactionCommentObject()` to return `null` explicitly. Test Plan: Ran `bin/search index --type ownerspackage`, got indexing after change. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18715 --- .../owners/storage/PhabricatorOwnersPackageTransaction.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index 66e15b634a..1dfc944f63 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -15,4 +15,8 @@ public function getBaseTransactionClass() { return 'PhabricatorOwnersPackageTransactionType'; } + public function getApplicationTransactionCommentObject() { + return null; + } + } From cbcab60fbb95e8ba6e00860e71b7e703e71f3d71 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 10:47:24 -0700 Subject: [PATCH 238/865] Remove obsolete instructions from information on prototype applications Summary: Most of this document is no longer relevant, since we're happy to work on prototypes if you're paying us and no longer have any meaningful free support. Test Plan: Read document. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18719 --- src/docs/user/userguide/prototypes.diviner | 33 ---------------------- 1 file changed, 33 deletions(-) diff --git a/src/docs/user/userguide/prototypes.diviner b/src/docs/user/userguide/prototypes.diviner index 7352502f99..c84bad1178 100644 --- a/src/docs/user/userguide/prototypes.diviner +++ b/src/docs/user/userguide/prototypes.diviner @@ -9,8 +9,6 @@ Overview Phabricator includes //prototype applications//, which are applications in an early stage of development. -IMPORTANT: The upstream does not offer support for these applications. - When we begin working on a new application, we usually implement it as a prototype first. This allows us to get a better sense of how the application might work and integrate with other applications, and what technical and product @@ -32,34 +30,3 @@ on hold indefinitely if we're less excited about it after we begin building it. If you're interested in previewing upcoming applications, you can use the `phabricator.show-prototypes` configuration setting to enable prototypes. - -Feedback on Prototypes -====================== - -We're usually interested in this sort of feedback on prototypes: - - - {icon check, color=green} **Use Cases**: If we're building something that - you think you'd use, we'd love to hear about your use cases for it. This can - help us figure out what features to add and how users may think about, use, - and integrate the application. - - {icon check, color=green} **General Interest**: Is an application something - you're looking forward to? Knowing which applications users are interested - in can help us set priorities. - -We're usually **not** interested in this sort of feedback on prototypes: - - - {icon times, color=red} **Support Requests**: We do not support these - applications. Use them at your own risk, or wait for them to leave the - prototype phase. - - {icon times, color=red} **Bug Reports**: We know these applications don't - work well yet, and usually know about most of the open bugs. Even if we - don't, whatever isn't working yet may change completely before the - application leaves the prototype phase. - - {icon times, color=red} **Contributions / Pull Requests**: These - applications are usually in too early a state to accept contributions. Let - us know about your use case, but wait for release to send code. - -Overall, using prototypes makes it easier for us to explore and develop -application ideas, and to share a preview of what's coming in the future with -users, but prototypes are not yet full applications and we do not provide -support until applications leave the prototype phase. From 65f13b156f5ecdbf689cce16f2bc7b9680818789 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 10:15:07 -0700 Subject: [PATCH 239/865] Improve "refengine" performance for testing large numbers of Mercurial branches Summary: See PHI158. In the RefEngine, we test if any old branch positions have been removed from the repository. This is uncommon (but not impossible) in Mercurial, and corresponds to users deleting branches in Git. Currently, we end up running `hg log` for each position, in parallel. Because of Python's large startup overhead, this can be resource intensive for repositories with a large number of branches. We have to do this in the general case because the caller may be asking us to resolve `tip`, `newfeature`, `tip~3`, `9`, etc. However, in the specific case where the refs are 40-digit hashes, we can bulk resolve them if they exist, like this: ``` hg log ... --rev (abcd or def0 or ab12 or ...) ``` In the general case, we could probably do less of this than we currently do (instead of testing all old heads, we could prune the list by removing commits which we know are still pointed to by current heads) but that's a slightly more involved change and the effect here is already dramatic. Test Plan: Verified that CPU usage drops from ~110s -> ~0.9s: Before: ``` epriestley@orbital ~/dev/phabricator $ time ./bin/repository refs nss Updating refs in "nss"... Done. real 0m14.676s user 1m24.714s sys 0m21.645s ``` After: ``` epriestley@orbital ~/dev/phabricator $ time ./bin/repository refs nss Updating refs in "nss"... Done. real 0m0.861s user 0m0.882s sys 0m0.213s ``` - Manually resolved `blue`, `tip`, `9`, etc., got expected results. - Tried to resolve invalid hashes, got expected result (no resolution). Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18717 --- .../DiffusionLowLevelResolveRefsQuery.php | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php index b649ca65a8..f9e9f74774 100644 --- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php +++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php @@ -256,6 +256,66 @@ private function resolveMercurialRefs() { return $results; } + // If some of the refs look like hashes, try to bulk resolve them. This + // workflow happens via RefEngine and bulk resolution is dramatically + // faster than individual resolution. See PHI158. + + $hashlike = array(); + foreach ($unresolved as $key => $ref) { + if (preg_match('/^[a-f0-9]{40}\z/', $ref)) { + $hashlike[$key] = $ref; + } + } + + if (count($hashlike) > 1) { + $hashlike_map = array(); + + $hashlike_groups = array_chunk($hashlike, 64, true); + foreach ($hashlike_groups as $hashlike_group) { + $hashlike_arg = array(); + foreach ($hashlike_group as $hashlike_ref) { + $hashlike_arg[] = hgsprintf('%s', $hashlike_ref); + } + $hashlike_arg = '('.implode(' or ', $hashlike_arg).')'; + + list($err, $refs) = $repository->execLocalCommand( + 'log --template=%s --rev %s', + '{node}\n', + $hashlike_arg); + if ($err) { + // NOTE: If any ref fails to resolve, Mercurial will exit with an + // error. We just give up on the whole group and resolve it + // individually below. In theory, we could split it into subgroups + // but the pathway where this bulk resolution matters rarely tries + // to resolve missing refs (see PHI158). + continue; + } + + $refs = phutil_split_lines($refs, false); + + foreach ($refs as $ref) { + $hashlike_map[$ref] = true; + } + } + + foreach ($unresolved as $key => $ref) { + if (!isset($hashlike_map[$ref])) { + continue; + } + + $results[$ref][] = array( + 'type' => 'commit', + 'identifier' => $ref, + ); + + unset($unresolved[$key]); + } + } + + if (!$unresolved) { + return $results; + } + // If we still have unresolved refs (which might be things like "tip"), // try to resolve them individually. From 672247eff30b65210e2f0f00b009f8c6c3f4b64f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 10:43:33 -0700 Subject: [PATCH 240/865] Add aural "+" and "-" hints to unified diffs for users who use screenreaders Summary: See PHI160 for discussion. Test Plan: With `?__aural__=1`, saw aural hints: {F5229986} Without, saw normal visual diff. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18718 --- .../DifferentialChangesetOneUpRenderer.php | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index 7a694cfd98..586680d1b8 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -41,6 +41,20 @@ protected function renderPrimitives(array $primitives, $rows) { $column_width = 4; + $aural_minus = javelin_tag( + 'span', + array( + 'aural' => true, + ), + '- '); + + $aural_plus = javelin_tag( + 'span', + array( + 'aural' => true, + ), + '+ '); + $out = array(); foreach ($primitives as $k => $p) { $type = $p['type']; @@ -55,8 +69,10 @@ protected function renderPrimitives(array $primitives, $rows) { if ($is_old) { if ($p['htype']) { $class = 'left old'; + $aural = $aural_minus; } else { $class = 'left'; + $aural = null; } if ($type == 'old-file') { @@ -79,14 +95,20 @@ protected function renderPrimitives(array $primitives, $rows) { ), $line); + $render = $p['render']; + if ($aural !== null) { + $render = array($aural, $render); + } + $cells[] = phutil_tag('th', array('class' => $class)); $cells[] = $no_copy; - $cells[] = phutil_tag('td', array('class' => $class), $p['render']); + $cells[] = phutil_tag('td', array('class' => $class), $render); $cells[] = $no_coverage; } else { if ($p['htype']) { $class = 'right new'; $cells[] = phutil_tag('th', array('class' => $class)); + $aural = $aural_plus; } else { $class = 'right'; if ($left_prefix) { @@ -98,6 +120,7 @@ protected function renderPrimitives(array $primitives, $rows) { $oline = $p['oline']; $cells[] = phutil_tag('th', array('id' => $left_id), $oline); + $aural = null; } if ($type == 'new-file') { @@ -120,8 +143,13 @@ protected function renderPrimitives(array $primitives, $rows) { ), $line); + $render = $p['render']; + if ($aural !== null) { + $render = array($aural, $render); + } + $cells[] = $no_copy; - $cells[] = phutil_tag('td', array('class' => $class), $p['render']); + $cells[] = phutil_tag('td', array('class' => $class), $render); $cells[] = $no_coverage; } From 63e6b2553e0ecdb0d10cde7c1845529a6d53e836 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 11:20:02 -0700 Subject: [PATCH 241/865] Simply how Differential drafts ignore Harbormaster autobuilds Summary: Ref T2543. When a revision is created, we check if any builds are waiting/failed, and submit it for review immediately if we aren't waiting for anything. In doing this, we ignore builds with only autotargets, since these are client-side and failures from local `arc lint` / `arc unit` should not count (the user has already chosen to ignore/skip them). The way we do this has some issues: - Herald may have started builds, but they may still be PENDING and not have any targets yet. In this case, we'll see "no non-autotargets" and ignore the build, which is wrong. - We have to load targets but don't really care about them, which is more work than we really need to do. - And it's kind of complex, too. Instead, just let `BuildQuery` filter out "autobuilds" (builds generated from autoplans) with a JOIN. Test Plan: Ran `arc diff` with builds configured, got a clean "Draft" state instead of an incorrect promotion directly to "Needs Review". Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18721 --- .../storage/DifferentialRevision.php | 25 +-------- .../query/HarbormasterBuildQuery.php | 55 +++++++++++++++++-- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 76c7c4da95..da7e18ce05 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -721,9 +721,10 @@ public function loadActiveBuilds(PhabricatorUser $viewer) { return array(); } - $builds = id(new HarbormasterBuildQuery()) + return id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withBuildablePHIDs(mpull($buildables, 'getPHID')) + ->withAutobuilds(false) ->withBuildStatuses( array( HarbormasterBuildStatus::STATUS_INACTIVE, @@ -735,29 +736,7 @@ public function loadActiveBuilds(PhabricatorUser $viewer) { HarbormasterBuildStatus::STATUS_PAUSED, HarbormasterBuildStatus::STATUS_DEADLOCKED, )) - ->needBuildTargets(true) ->execute(); - if (!$builds) { - return array(); - } - - $active = array(); - foreach ($builds as $key => $build) { - foreach ($build->getBuildTargets() as $target) { - if ($target->isAutotarget()) { - // Ignore autotargets when looking for active of failed builds. If - // local tests fail and you continue anyway, you don't need to - // double-confirm them. - continue; - } - - // This build has at least one real target that's doing something. - $active[$key] = $build; - break; - } - } - - return $active; } diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php index cf6db6115b..770ca4413b 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php @@ -10,6 +10,7 @@ final class HarbormasterBuildQuery private $buildPlanPHIDs; private $initiatorPHIDs; private $needBuildTargets; + private $autobuilds; public function withIDs(array $ids) { $this->ids = $ids; @@ -41,6 +42,11 @@ public function withInitiatorPHIDs(array $initiator_phids) { return $this; } + public function withAutobuilds($with_autobuilds) { + $this->autobuilds = $with_autobuilds; + return $this; + } + public function needBuildTargets($need_targets) { $this->needBuildTargets = $need_targets; return $this; @@ -141,50 +147,87 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'b.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid in (%Ls)', + 'b.phid in (%Ls)', $this->phids); } if ($this->buildStatuses !== null) { $where[] = qsprintf( $conn, - 'buildStatus in (%Ls)', + 'b.buildStatus in (%Ls)', $this->buildStatuses); } if ($this->buildablePHIDs !== null) { $where[] = qsprintf( $conn, - 'buildablePHID IN (%Ls)', + 'b.buildablePHID IN (%Ls)', $this->buildablePHIDs); } if ($this->buildPlanPHIDs !== null) { $where[] = qsprintf( $conn, - 'buildPlanPHID IN (%Ls)', + 'b.buildPlanPHID IN (%Ls)', $this->buildPlanPHIDs); } if ($this->initiatorPHIDs !== null) { $where[] = qsprintf( $conn, - 'initiatorPHID IN (%Ls)', + 'b.initiatorPHID IN (%Ls)', $this->initiatorPHIDs); } + if ($this->autobuilds !== null) { + if ($this->autobuilds) { + $where[] = qsprintf( + $conn, + 'p.planAutoKey IS NOT NULL'); + } else { + $where[] = qsprintf( + $conn, + 'p.planAutoKey IS NULL'); + } + } + return $where; } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->shouldJoinPlanTable()) { + $joins[] = qsprintf( + $conn, + 'JOIN %T p ON b.buildPlanPHID = p.phid', + id(new HarbormasterBuildPlan())->getTableName()); + } + + return $joins; + } + + private function shouldJoinPlanTable() { + if ($this->autobuilds !== null) { + return true; + } + + return false; + } + public function getQueryApplicationClass() { return 'PhabricatorHarbormasterApplication'; } + protected function getPrimaryTableAlias() { + return 'b'; + } + } From 157f47cd149c1a811c12850bbf76cc05dd805ad9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 20 Oct 2017 15:37:19 -0700 Subject: [PATCH 242/865] Rewrite CommitQuery to use UNION for performance Summary: Ref T12680. See PHI167. See that task for discussion. Rewrite `DiffusionCommitQuery` to work more like `DifferentialRevisionQuery`, and use a UNION to find "all revisions you need to audit OR respond to". I tried to get this working a little more cleanly than RevisionQuery does, and can probably simplify that now. Test Plan: Poked at the UI locally without hitting any apparent issues, but my local data is pretty garbage at this point. I'll take a look at how the query plans work on `secure`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12680 Differential Revision: https://secure.phabricator.com/D18722 --- .../diffusion/query/DiffusionCommitQuery.php | 83 +++++++++++++------ ...PhabricatorCursorPagedPolicyAwareQuery.php | 35 ++++++-- 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index da8937910e..8996bab628 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -177,7 +177,63 @@ public function newResultObject() { } protected function loadPage() { - return $this->loadStandardPage($this->newResultObject()); + $table = $this->newResultObject(); + $conn = $table->establishConnection('r'); + + $subqueries = array(); + if ($this->responsiblePHIDs) { + $base_authors = $this->authorPHIDs; + $base_auditors = $this->auditorPHIDs; + + $responsible_phids = $this->responsiblePHIDs; + if ($base_authors) { + $all_authors = array_merge($base_authors, $responsible_phids); + } else { + $all_authors = $responsible_phids; + } + + if ($base_auditors) { + $all_auditors = array_merge($base_auditors, $responsible_phids); + } else { + $all_auditors = $responsible_phids; + } + + $this->authorPHIDs = $all_authors; + $this->auditorPHIDs = $base_auditors; + $subqueries[] = $this->buildStandardPageQuery( + $conn, + $table->getTableName()); + + $this->authorPHIDs = $base_authors; + $this->auditorPHIDs = $all_auditors; + $subqueries[] = $this->buildStandardPageQuery( + $conn, + $table->getTableName()); + } else { + $subqueries[] = $this->buildStandardPageQuery( + $conn, + $table->getTableName()); + } + + if (count($subqueries) > 1) { + foreach ($subqueries as $key => $subquery) { + $subqueries[$key] = '('.$subquery.')'; + } + + $query = qsprintf( + $conn, + '%Q %Q %Q', + implode(' UNION DISTINCT ', $subqueries), + $this->buildOrderClause($conn, true), + $this->buildLimitClause($conn)); + } else { + $query = head($subqueries); + } + + $rows = queryfx_all($conn, '%Q', $query); + $rows = $this->didLoadRawRows($rows); + + return $table->loadAllFromArray($rows); } protected function willFilterPage(array $commits) { @@ -487,18 +543,10 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $this->auditorPHIDs); } - if ($this->responsiblePHIDs !== null) { - $where[] = qsprintf( - $conn, - '(audit.auditorPHID IN (%Ls) OR commit.authorPHID IN (%Ls))', - $this->responsiblePHIDs, - $this->responsiblePHIDs); - } - if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'commit.auditStatus IN (%Ls)', + 'commit.auditStatus IN (%Ld)', $this->statuses); } @@ -541,10 +589,6 @@ private function shouldJoinAuditor() { return ($this->auditIDs || $this->auditorPHIDs); } - private function shouldJoinAudit() { - return (bool)$this->responsiblePHIDs; - } - private function shouldJoinOwners() { return (bool)$this->packagePHIDs; } @@ -560,13 +604,6 @@ protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $audit_request->getTableName()); } - if ($this->shouldJoinAudit()) { - $join[] = qsprintf( - $conn, - 'LEFT JOIN %T audit ON commit.phid = audit.commitPHID', - $audit_request->getTableName()); - } - if ($this->shouldJoinOwners()) { $join[] = qsprintf( $conn, @@ -584,10 +621,6 @@ protected function shouldGroupQueryResultRows() { return true; } - if ($this->shouldJoinAudit()) { - return true; - } - if ($this->shouldJoinOwners()) { return true; } diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 9bebfe6957..19d6ba07f4 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -111,7 +111,19 @@ protected function loadStandardPageRowsWithConnection( AphrontDatabaseConnection $conn, $table_name) { - $rows = queryfx_all( + $query = $this->buildStandardPageQuery($conn, $table_name); + + $rows = queryfx_all($conn, '%Q', $query); + $rows = $this->didLoadRawRows($rows); + + return $rows; + } + + protected function buildStandardPageQuery( + AphrontDatabaseConnection $conn, + $table_name) { + + return qsprintf( $conn, '%Q FROM %T %Q %Q %Q %Q %Q %Q %Q', $this->buildSelectClause($conn), @@ -123,10 +135,6 @@ protected function loadStandardPageRowsWithConnection( $this->buildHavingClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); - - $rows = $this->didLoadRawRows($rows); - - return $rows; } protected function didLoadRawRows(array $rows) { @@ -1032,7 +1040,10 @@ public function getOrderableColumns() { /** * @task order */ - final protected function buildOrderClause(AphrontDatabaseConnection $conn) { + final protected function buildOrderClause( + AphrontDatabaseConnection $conn, + $for_union = false) { + $orderable = $this->getOrderableColumns(); $vector = $this->getOrderVector(); @@ -1045,7 +1056,7 @@ final protected function buildOrderClause(AphrontDatabaseConnection $conn) { $parts[] = $part; } - return $this->formatOrderClause($conn, $parts); + return $this->formatOrderClause($conn, $parts, $for_union); } @@ -1054,7 +1065,8 @@ final protected function buildOrderClause(AphrontDatabaseConnection $conn) { */ protected function formatOrderClause( AphrontDatabaseConnection $conn, - array $parts) { + array $parts, + $for_union = false) { $is_query_reversed = false; if ($this->getBeforeID()) { @@ -1075,6 +1087,13 @@ protected function formatOrderClause( } $table = idx($part, 'table'); + + // When we're building an ORDER BY clause for a sequence of UNION + // statements, we can't refer to tables from the subqueries. + if ($for_union) { + $table = null; + } + $column = $part['column']; if ($table !== null) { From e3a48dde1d5b95df4eb0ea1763a6baa7576e418f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 Oct 2017 10:03:56 -0700 Subject: [PATCH 243/865] Correct a method signature in DifferentialDraftField Summary: Ref T12190. See . (I have a followup to fix the root issue.) Test Plan: Loaded Differential with an eye on the error log in PHP7, no longer saw warnings. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12190 Differential Revision: https://secure.phabricator.com/D18723 --- .../differential/customfield/DifferentialDraftField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/differential/customfield/DifferentialDraftField.php b/src/applications/differential/customfield/DifferentialDraftField.php index e37d622a33..e7ed2bedb2 100644 --- a/src/applications/differential/customfield/DifferentialDraftField.php +++ b/src/applications/differential/customfield/DifferentialDraftField.php @@ -24,7 +24,7 @@ public function shouldAppearInPropertyView() { return true; } - public function renderPropertyViewValue() { + public function renderPropertyViewValue(array $handles) { return null; } From 5975b9d28e41546a741293ed2ae1377f973bf612 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 Oct 2017 10:03:56 -0700 Subject: [PATCH 244/865] (stable) Correct a method signature in DifferentialDraftField Summary: Ref T12190. See . (I have a followup to fix the root issue.) Test Plan: Loaded Differential with an eye on the error log in PHP7, no longer saw warnings. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12190 Differential Revision: https://secure.phabricator.com/D18723 --- .../differential/customfield/DifferentialDraftField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/differential/customfield/DifferentialDraftField.php b/src/applications/differential/customfield/DifferentialDraftField.php index e37d622a33..e7ed2bedb2 100644 --- a/src/applications/differential/customfield/DifferentialDraftField.php +++ b/src/applications/differential/customfield/DifferentialDraftField.php @@ -24,7 +24,7 @@ public function shouldAppearInPropertyView() { return true; } - public function renderPropertyViewValue() { + public function renderPropertyViewValue(array $handles) { return null; } From 683df399e7f4753dcea7ce03961e36956d6e790d Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 Oct 2017 14:59:32 -0700 Subject: [PATCH 245/865] Simplify UNION/ORDER query construction in DifferentialRevisionQuery Summary: Ref T12680. Use the slightly sleeker construction from D18722 in Differential. Test Plan: Viewed revision list, reordered by date modified. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12680 Differential Revision: https://secure.phabricator.com/D18727 --- .../query/DifferentialRevisionQuery.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 8fd91cbd7e..727a4ca184 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -38,8 +38,6 @@ final class DifferentialRevisionQuery private $needDrafts; private $needFlags; - private $buildingGlobalOrder; - /* -( Query Configuration )------------------------------------------------ */ @@ -484,12 +482,11 @@ private function loadData() { } if (count($selects) > 1) { - $this->buildingGlobalOrder = true; $query = qsprintf( $conn_r, '%Q %Q %Q', implode(' UNION DISTINCT ', $selects), - $this->buildOrderClause($conn_r), + $this->buildOrderClause($conn_r, true), $this->buildLimitClause($conn_r)); } else { $query = head($selects); @@ -513,7 +510,6 @@ private function buildSelectStatement(AphrontDatabaseConnection $conn_r) { $group_by = $this->buildGroupClause($conn_r); $having = $this->buildHavingClause($conn_r); - $this->buildingGlobalOrder = false; $order_by = $this->buildOrderClause($conn_r); $limit = $this->buildLimitClause($conn_r); @@ -758,17 +754,9 @@ protected function getDefaultOrderVector() { } public function getOrderableColumns() { - $primary = ($this->buildingGlobalOrder ? null : 'r'); - return array( - 'id' => array( - 'table' => $primary, - 'column' => 'id', - 'type' => 'int', - 'unique' => true, - ), 'updated' => array( - 'table' => $primary, + 'table' => $this->getPrimaryTableAlias(), 'column' => 'dateModified', 'type' => 'int', ), From 1d213dc1fa4b45280196457c61a1c36c396a602c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 23 Oct 2017 15:04:06 -0700 Subject: [PATCH 246/865] Clean up virtual "_ft_rank" column for query construction of Ferret objects Summary: Ref T12974. Ferret object queries SELECT a virtual "_ft_rank" column for relevance ordering. Currently, they always SELECT this column. That's fine and doesn't hurt anything, but makes developing and debugging things kind of a pain since every query has this `, blah blah _ft_rank` junk. Instead, construct this column only if we're actually going to use it. Mostly, this cleans up DarkConsole / query logs a bit. Test Plan: Viewed normal query results on various pages, viewed global search results, ordered Maniphest tasks by normal stuff and by "Relevance". Viewed DarkConsole, saw no more "_ft_rank" junk on normal pages. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18728 --- .../policy/PhabricatorCursorPagedPolicyAwareQuery.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php index 19d6ba07f4..63daa6df79 100644 --- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php @@ -1565,6 +1565,13 @@ protected function buildFerretSelectClause(AphrontDatabaseConnection $conn) { return $select; } + $vector = $this->getOrderVector(); + if (!$vector->containsKey('rank')) { + // We only need to SELECT the virtual "_ft_rank" column if we're + // actually sorting the results by rank. + return $select; + } + if (!$this->ferretEngine) { $select[] = '0 _ft_rank'; return $select; From 28a24c333fdd4a8def13e624552eeaa3e37f7e08 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Oct 2017 05:57:15 -0700 Subject: [PATCH 247/865] Fix a couple of other missing getApplicationTransactionCommentObject() implementations Summary: See PHI165. See D18715. These objects (projects, blogs) also need implementations now. (I thought about making this method `abstract` or doing try/catch to maybe make this more robust, but I think this should be the end of it, and those changes have mild complexity/compatibility/risk issues.) Test Plan: Changed `bin/search index` to index only one document of each type, ran `bin/search index --all --force`, saw no more comment-related errors. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18729 --- src/applications/phame/storage/PhameBlogTransaction.php | 4 ++++ .../project/storage/PhabricatorProjectTransaction.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/applications/phame/storage/PhameBlogTransaction.php b/src/applications/phame/storage/PhameBlogTransaction.php index c605510d7d..d3d6a79d0a 100644 --- a/src/applications/phame/storage/PhameBlogTransaction.php +++ b/src/applications/phame/storage/PhameBlogTransaction.php @@ -15,6 +15,10 @@ public function getApplicationTransactionType() { return PhabricatorPhameBlogPHIDType::TYPECONST; } + public function getApplicationTransactionCommentObject() { + return null; + } + public function getBaseTransactionClass() { return 'PhameBlogTransactionType'; } diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index 158c2480c0..a8b2bb0d4a 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -19,6 +19,10 @@ public function getApplicationTransactionType() { return PhabricatorProjectProjectPHIDType::TYPECONST; } + public function getApplicationTransactionCommentObject() { + return null; + } + public function getBaseTransactionClass() { return 'PhabricatorProjectTransactionType'; } From beaf0ad9a6360154d5c3f79e2e7eb62b553dcf5b Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Oct 2017 09:10:48 -0700 Subject: [PATCH 248/865] Attribute revision promotion from "Draft" to "Needs Review" to the author Summary: Ref T2543. When Harbormaster finishes builds and promotes a draft revision to review, we currently publish "Harbormaster requested review of...". Instead, attribute this action to the author, since that's more natural and more useful. Test Plan: Promoted a diff locally, saw it attributed to me rather than Harbormaster. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18730 --- .../differential/editor/DifferentialTransactionEditor.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 7c9bb01b07..5e013eff43 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1537,7 +1537,13 @@ protected function didApplyTransactions($object, array $xactions) { if ($object->isDraft() && $auto_undraft) { $active_builds = $this->hasActiveBuilds($object); if (!$active_builds) { + // When Harbormaster moves a revision out of the draft state, we + // attribute the action to the revision author since this is more + // natural and more useful. + $author_phid = $object->getAuthorPHID(); + $xaction = $object->getApplicationTransactionTemplate() + ->setAuthorPHID($author_phid) ->setTransactionType( DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE) ->setOldValue(false) From f1204c8c45683d86afa5624a6ef7f88f88f8a6c7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:38:12 -0700 Subject: [PATCH 249/865] Convert Ponder Questions to Ferret engine Summary: See PHI177. Ref T12974. PonderQuestion was overlooked during the Ferret engine conversions. Test Plan: Ran migrations, searched for questions, got results: {F5241185} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12974 Differential Revision: https://secure.phabricator.com/D18736 --- .../20171026.ferret.01.ponder.doc.sql | 9 +++++++++ .../20171026.ferret.02.ponder.field.sql | 8 ++++++++ .../20171026.ferret.03.ponder.ngrams.sql | 5 +++++ .../20171026.ferret.04.ponder.cngrams.sql | 7 +++++++ .../20171026.ferret.05.ponder.index.php | 11 +++++++++++ src/__phutil_library_map__.php | 3 +++ .../search/PonderQuestionFerretEngine.php | 18 ++++++++++++++++++ .../ponder/storage/PonderQuestion.php | 12 +++++++++++- 8 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql create mode 100644 resources/sql/autopatches/20171026.ferret.02.ponder.field.sql create mode 100644 resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql create mode 100644 resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql create mode 100644 resources/sql/autopatches/20171026.ferret.05.ponder.index.php create mode 100644 src/applications/ponder/search/PonderQuestionFerretEngine.php diff --git a/resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql b/resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql new file mode 100644 index 0000000000..38c86a4134 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.01.ponder.doc.sql @@ -0,0 +1,9 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_fdocument ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + isClosed BOOL NOT NULL, + authorPHID VARBINARY(64), + ownerPHID VARBINARY(64), + epochCreated INT UNSIGNED NOT NULL, + epochModified INT UNSIGNED NOT NULL +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.02.ponder.field.sql b/resources/sql/autopatches/20171026.ferret.02.ponder.field.sql new file mode 100644 index 0000000000..871f0d8f5b --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.02.ponder.field.sql @@ -0,0 +1,8 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_ffield ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT}, + rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}, + normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql b/resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql new file mode 100644 index 0000000000..3d2a3024b8 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.03.ponder.ngrams.sql @@ -0,0 +1,5 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_fngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + documentID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql b/resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql new file mode 100644 index 0000000000..49b66e0d39 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.04.ponder.cngrams.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_ponder.ponder_question_fngrams_common ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + needsCollection BOOL NOT NULL, + UNIQUE KEY `key_ngram` (ngram), + KEY `key_collect` (needsCollection) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20171026.ferret.05.ponder.index.php b/resources/sql/autopatches/20171026.ferret.05.ponder.index.php new file mode 100644 index 0000000000..20489846d2 --- /dev/null +++ b/resources/sql/autopatches/20171026.ferret.05.ponder.index.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 93efcc65e1..91d5a3cc7b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4774,6 +4774,7 @@ 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', 'PonderQuestionEditEngine' => 'applications/ponder/editor/PonderQuestionEditEngine.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', + 'PonderQuestionFerretEngine' => 'applications/ponder/search/PonderQuestionFerretEngine.php', 'PonderQuestionFulltextEngine' => 'applications/ponder/search/PonderQuestionFulltextEngine.php', 'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php', 'PonderQuestionListController' => 'applications/ponder/controller/PonderQuestionListController.php', @@ -10539,6 +10540,7 @@ 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', 'PhabricatorFulltextInterface', + 'PhabricatorFerretInterface', ), 'PonderQuestionAnswerTransaction' => 'PonderQuestionTransactionType', 'PonderQuestionAnswerWikiTransaction' => 'PonderQuestionTransactionType', @@ -10548,6 +10550,7 @@ 'PonderQuestionEditController' => 'PonderController', 'PonderQuestionEditEngine' => 'PhabricatorEditEngine', 'PonderQuestionEditor' => 'PonderEditor', + 'PonderQuestionFerretEngine' => 'PhabricatorFerretEngine', 'PonderQuestionFulltextEngine' => 'PhabricatorFulltextEngine', 'PonderQuestionHistoryController' => 'PonderController', 'PonderQuestionListController' => 'PonderController', diff --git a/src/applications/ponder/search/PonderQuestionFerretEngine.php b/src/applications/ponder/search/PonderQuestionFerretEngine.php new file mode 100644 index 0000000000..72345f60c0 --- /dev/null +++ b/src/applications/ponder/search/PonderQuestionFerretEngine.php @@ -0,0 +1,18 @@ + Date: Thu, 26 Oct 2017 13:00:09 -0700 Subject: [PATCH 250/865] Add a missing `artifactIndex` key to HarbormasterBuildArtifact Summary: See PHI176. We issue a query with only `artifactIndex` from `BuildTarget`, but don't have an applicable key. Test Plan: This isn't on the normal Harbormaster execution path so I'm not 100% sure I have a local repro, but will confirm with customer. Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18732 --- .../harbormaster/storage/build/HarbormasterBuildArtifact.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index 6825bf6646..461ef4b06f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -43,6 +43,9 @@ protected function getConfiguration() { 'key_target' => array( 'columns' => array('buildTargetPHID', 'artifactType'), ), + 'key_index' => array( + 'columns' => array('artifactIndex'), + ), ), ) + parent::getConfiguration(); } From fbfed82efddee9732909036f358bc48c272136e4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:05:31 -0700 Subject: [PATCH 251/865] Add a missing DaemonLogEvent key for garbage collection Summary: See PHI176. This is issued periodically by the garbage collector. Normally this table is relatively small-ish so this missing key isn't hugely noticeable. Test Plan: Ran `./bin/garbage collect --collector daemon.processes --trace` to get the query the GC runs. Ran ##DELETE FROM `daemon_logevent` WHERE epoch < 1508443504 LIMIT 100## before and after the key, saw a much better query plan afterward: Before: ``` mysql> explain DELETE FROM `daemon_logevent` WHERE epoch < 1508443504 LIMIT 100; +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | daemon_logevent | ALL | NULL | NULL | NULL | NULL | 19325 | Using where | +----+-------------+-----------------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.00 sec) ``` After: ``` mysql> explain DELETE FROM `daemon_logevent` WHERE epoch < 1508443504 LIMIT 100; +----+-------------+-----------------+-------+---------------+-----------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------------+-------+---------------+-----------+---------+-------+------+-------------+ | 1 | SIMPLE | daemon_logevent | range | key_epoch | key_epoch | 4 | const | 1 | Using where | +----+-------------+-----------------+-------+---------------+-----------+---------+-------+------+-------------+ 1 row in set (0.00 sec) ``` Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18733 --- src/applications/daemon/storage/PhabricatorDaemonLogEvent.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php b/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php index afdbd1f621..c5d2cbd1d4 100644 --- a/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php +++ b/src/applications/daemon/storage/PhabricatorDaemonLogEvent.php @@ -18,6 +18,9 @@ protected function getConfiguration() { 'logID' => array( 'columns' => array('logID', 'epoch'), ), + 'key_epoch' => array( + 'columns' => array('epoch'), + ), ), ) + parent::getConfiguration(); } From b04d9fdc0ba875d1fd0ff61152a712e163135e18 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:11:51 -0700 Subject: [PATCH 252/865] Add a missing key to PhabricatorFile for destroying Files Summary: See PHI176. Depends on D18733. We issue a query when deleting files that currently doesn't hit any keys. Test Plan: Ran `./bin/remove destroy --force --trace F56376` to get the query. Ran ##SELECT * FROM `file` WHERE storageEngine = 'blob' AND storageHandle = '23366' LIMIT 1## before and after the change. Before: ``` mysql> explain SELECT * FROM `file` WHERE storageEngine = 'blob' AND storageHandle = '23366' LIMIT 1; +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | file | ALL | NULL | NULL | NULL | NULL | 33866 | Using where | +----+-------------+-------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.01 sec) ``` After: ``` mysql> explain SELECT * FROM `file` WHERE storageEngine = 'blob' AND storageHandle = '23366' LIMIT 1; +----+-------------+-------+------+---------------+------------+---------+-------------+------+------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------------+---------+-------------+------+------------------------------------+ | 1 | SIMPLE | file | ref | key_engine | key_engine | 388 | const,const | 190 | Using index condition; Using where | +----+-------------+-------+------+---------------+------------+---------+-------------+------+------------------------------------+ 1 row in set (0.00 sec) ``` Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18734 --- src/applications/files/storage/PhabricatorFile.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 9636b5b017..c87623c68c 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -134,6 +134,9 @@ protected function getConfiguration() { 'columns' => array('builtinKey'), 'unique' => true, ), + 'key_engine' => array( + 'columns' => array('storageEngine', 'storageHandle(64)'), + ), ), ) + parent::getConfiguration(); } From d1b4c9f50b99b08fd77fbf6554834b61839813eb Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 26 Oct 2017 13:17:33 -0700 Subject: [PATCH 253/865] Add a missing key on DrydockLease Summary: Depends on D18734. See PHI176. We run this query on the main Drydock lease web UI, among other places. There is currently no `status` key which can satisfy it. Test Plan: Viewed Drydock lease page to get the query. Ran ##explain SELECT * FROM `drydock_lease` WHERE (status IN ('pending', 'acquired', 'active')) ORDER BY `id` DESC LIMIT 101;## before and after the change. I don't have a ton of leases locally so the un-key'd EXPLAIN isn't //that// bad, but still shows that we're getting a better key. Before: ``` mysql> explain SELECT * FROM `drydock_lease` WHERE (status IN ('pending', 'acquired', 'active')) ORDER BY `id` DESC LIMIT 101; +----+-------------+---------------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | drydock_lease | index | NULL | PRIMARY | 4 | NULL | 101 | Using where | +----+-------------+---------------+-------+---------------+---------+---------+------+------+-------------+ 1 row in set (0.00 sec) ``` After: ``` mysql> explain SELECT * FROM `drydock_lease` WHERE (status IN ('pending', 'acquired', 'active')) ORDER BY `id` DESC LIMIT 101; +----+-------------+---------------+-------+---------------+------------+---------+------+------+---------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------------+-------+---------------+------------+---------+------+------+---------------------------------------+ | 1 | SIMPLE | drydock_lease | range | key_status | key_status | 130 | NULL | 5 | Using index condition; Using filesort | +----+-------------+---------------+-------+---------------+------------+---------+------+------+---------------------------------------+ 1 row in set (0.00 sec) ``` Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18735 --- src/applications/drydock/storage/DrydockLease.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index 0dd4e36b48..e60529afe4 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -83,6 +83,9 @@ protected function getConfiguration() { 'key_resource' => array( 'columns' => array('resourcePHID', 'status'), ), + 'key_status' => array( + 'columns' => array('status'), + ), ), ) + parent::getConfiguration(); } From f7f3dd5b20848c221e16bc34b3ce61ddbdbdde94 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 24 Oct 2017 14:04:06 -0700 Subject: [PATCH 254/865] Don't run Herald build and mail rules when they don't make sense Summary: Ref T2543. Fixes T10109. Currently, Herald only runs in Differential when a change updates the diff. This is partly for historical reasons, and partly because we don't want to restart builds every time someone makes a comment. However, this behavior is inconsistent with other applications (which always trigger on any change), and occasionally confusing to users (in T10109, for example) or otherwise undesirable. A similar issue is that T2543 has introduced a "Draft" state, where revisions don't send normal mail until builds finish. This interacts poorly with "Send me an email" rules (which shouldn't do anything here) and particularly with "Send me an email + only run these actions the first time the rule matches", since that might have an effect like "do nothing when the revision is created, then never anything again since you already did nothing once". To navigate both of these issues, let objects tell Herald that certain actions (like mail or builds) are currently forbidden. If a rule uses a field or action which is currently forbidden, the whole rule automatically fails before it executes, but doesn't count toward "only the first time" as far as Herald's tracking of rule execution is concerned. Then, forbid mail for draft revisions, and forbid builds for revisions which didn't just get updated. Forbidding mail fixes the issues with "Send me an email" that were created by the introduction of the draft state. Finally, make Herald run on every revision update, not just substantive updates to the diff. This resolves T10109. Test Plan: Created revisions via the draft -> submit workflow, saw different transcripts. Here's a mail action being forbidden for a draft: {F5237324} Here's a build action being forbidden for a "mundane" update: {F5237326} Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T10109, T2543 Differential Revision: https://secure.phabricator.com/D18731 --- src/__phutil_library_map__.php | 10 + .../editor/DifferentialTransactionEditor.php | 48 +++-- .../herald/DifferentialHeraldStateReasons.php | 22 ++ .../HarbormasterRunBuildPlansHeraldAction.php | 6 + .../herald/action/HeraldAction.php | 12 ++ .../herald/adapter/HeraldAdapter.php | 35 +++ .../controller/HeraldTranscriptController.php | 20 +- .../herald/engine/HeraldEngine.php | 204 ++++++++++++++---- src/applications/herald/field/HeraldField.php | 4 + .../herald/state/HeraldBuildableState.php | 7 + .../herald/state/HeraldMailableState.php | 7 + src/applications/herald/state/HeraldState.php | 3 + .../herald/state/HeraldStateReasons.php | 26 +++ .../transcript/HeraldConditionTranscript.php | 7 + .../transcript/HeraldRuleTranscript.php | 6 + .../PhabricatorMetaMTAEmailHeraldAction.php | 6 + 16 files changed, 359 insertions(+), 64 deletions(-) create mode 100644 src/applications/differential/herald/DifferentialHeraldStateReasons.php create mode 100644 src/applications/herald/state/HeraldBuildableState.php create mode 100644 src/applications/herald/state/HeraldMailableState.php create mode 100644 src/applications/herald/state/HeraldState.php create mode 100644 src/applications/herald/state/HeraldStateReasons.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 91d5a3cc7b..2040568496 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -458,6 +458,7 @@ 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 'DifferentialGitSVNIDCommitMessageField' => 'applications/differential/field/DifferentialGitSVNIDCommitMessageField.php', 'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php', + 'DifferentialHeraldStateReasons' => 'applications/differential/herald/DifferentialHeraldStateReasons.php', 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 'DifferentialHovercardEngineExtension' => 'applications/differential/engineextension/DifferentialHovercardEngineExtension.php', @@ -1328,6 +1329,7 @@ 'HeraldApplicationActionGroup' => 'applications/herald/action/HeraldApplicationActionGroup.php', 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php', 'HeraldBasicFieldGroup' => 'applications/herald/field/HeraldBasicFieldGroup.php', + 'HeraldBuildableState' => 'applications/herald/state/HeraldBuildableState.php', 'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php', 'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', @@ -1351,6 +1353,7 @@ 'HeraldGroup' => 'applications/herald/group/HeraldGroup.php', 'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php', 'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php', + 'HeraldMailableState' => 'applications/herald/state/HeraldMailableState.php', 'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php', 'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php', 'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php', @@ -1388,6 +1391,8 @@ 'HeraldSchemaSpec' => 'applications/herald/storage/HeraldSchemaSpec.php', 'HeraldSelectFieldValue' => 'applications/herald/value/HeraldSelectFieldValue.php', 'HeraldSpaceField' => 'applications/spaces/herald/HeraldSpaceField.php', + 'HeraldState' => 'applications/herald/state/HeraldState.php', + 'HeraldStateReasons' => 'applications/herald/state/HeraldStateReasons.php', 'HeraldSubscribersField' => 'applications/subscriptions/herald/HeraldSubscribersField.php', 'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php', 'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php', @@ -5467,6 +5472,7 @@ 'DifferentialGetWorkingCopy' => 'Phobject', 'DifferentialGitSVNIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialHarbormasterField' => 'DifferentialCustomField', + 'DifferentialHeraldStateReasons' => 'HeraldStateReasons', 'DifferentialHiddenComment' => 'DifferentialDAO', 'DifferentialHostField' => 'DifferentialCustomField', 'DifferentialHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', @@ -6458,6 +6464,7 @@ 'HeraldApplicationActionGroup' => 'HeraldActionGroup', 'HeraldApplyTranscript' => 'Phobject', 'HeraldBasicFieldGroup' => 'HeraldFieldGroup', + 'HeraldBuildableState' => 'HeraldState', 'HeraldCommitAdapter' => array( 'HeraldAdapter', 'HarbormasterBuildableAdapterInterface', @@ -6487,6 +6494,7 @@ 'HeraldGroup' => 'Phobject', 'HeraldInvalidActionException' => 'Exception', 'HeraldInvalidConditionException' => 'Exception', + 'HeraldMailableState' => 'HeraldState', 'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability', 'HeraldManiphestTaskAdapter' => 'HeraldAdapter', 'HeraldNewController' => 'HeraldController', @@ -6531,6 +6539,8 @@ 'HeraldSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HeraldSelectFieldValue' => 'HeraldFieldValue', 'HeraldSpaceField' => 'HeraldField', + 'HeraldState' => 'Phobject', + 'HeraldStateReasons' => 'Phobject', 'HeraldSubscribersField' => 'HeraldField', 'HeraldSupportActionGroup' => 'HeraldActionGroup', 'HeraldSupportFieldGroup' => 'HeraldFieldGroup', diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 5e013eff43..9ab30c3f06 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1003,26 +1003,7 @@ private function requireDiff($phid, $need_changesets = false) { protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { - - if ($this->getIsNewObject()) { - return true; - } - - foreach ($xactions as $xaction) { - switch ($xaction->getTransactionType()) { - case DifferentialTransaction::TYPE_UPDATE: - if (!$this->getIsCloseByCommit()) { - return true; - } - break; - case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE: - // When users commandeer revisions, we may need to trigger - // signatures or author-based rules. - return true; - } - } - - return parent::shouldApplyHeraldRules($object, $xactions); + return true; } protected function didApplyHeraldRules( @@ -1211,6 +1192,33 @@ protected function buildHeraldAdapter( $revision, $revision->getActiveDiff()); + // If the object is still a draft, prevent "Send me an email" and other + // similar rules from acting yet. + if (!$object->shouldBroadcast()) { + $adapter->setForbiddenAction( + HeraldMailableState::STATECONST, + DifferentialHeraldStateReasons::REASON_DRAFT); + } + + // If this edit didn't actually change the diff (for example, a user + // edited the title or changed subscribers), prevent "Run build plan" + // and other similar rules from acting yet, since the build results will + // not (or, at least, should not) change unless the actual source changes. + $has_update = false; + $type_update = DifferentialTransaction::TYPE_UPDATE; + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() == $type_update) { + $has_update = true; + break; + } + } + + if (!$has_update) { + $adapter->setForbiddenAction( + HeraldBuildableState::STATECONST, + DifferentialHeraldStateReasons::REASON_UNCHANGED); + } + return $adapter; } diff --git a/src/applications/differential/herald/DifferentialHeraldStateReasons.php b/src/applications/differential/herald/DifferentialHeraldStateReasons.php new file mode 100644 index 0000000000..d3259560d5 --- /dev/null +++ b/src/applications/differential/herald/DifferentialHeraldStateReasons.php @@ -0,0 +1,22 @@ + pht( + 'This revision is still an unsubmitted draft, so mail will not '. + 'be sent yet.'), + self::REASON_UNCHANGED => pht( + 'The update which triggered Herald did not update the diff for '. + 'this revision, so builds will not run.'), + ); + + return idx($reasons, $reason); + } + +} diff --git a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php index 76bdf9ccc3..8c718e5f5d 100644 --- a/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php +++ b/src/applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php @@ -7,6 +7,12 @@ final class HarbormasterRunBuildPlansHeraldAction const ACTIONCONST = 'harbormaster.build'; + public function getRequiredAdapterStates() { + return array( + HeraldBuildableState::STATECONST, + ); + } + public function getActionGroupKey() { return HeraldSupportActionGroup::ACTIONGROUPKEY; } diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index 091f3e9bc2..a2d589f9f2 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -17,6 +17,7 @@ abstract class HeraldAction extends Phobject { const DO_STANDARD_PERMISSION = 'do.standard.permission'; const DO_STANDARD_INVALID_ACTION = 'do.standard.invalid-action'; const DO_STANDARD_WRONG_RULE_TYPE = 'do.standard.wrong-rule-type'; + const DO_STANDARD_FORBIDDEN = 'do.standard.forbidden'; abstract public function getHeraldActionName(); abstract public function supportsObject($object); @@ -25,6 +26,10 @@ abstract public function applyEffect($object, HeraldEffect $effect); abstract public function renderActionDescription($value); + public function getRequiredAdapterStates() { + return array(); + } + protected function renderActionEffectDescription($type, $data) { return null; } @@ -336,6 +341,11 @@ protected function getStandardEffectMap() { 'color' => 'red', 'name' => pht('Wrong Rule Type'), ), + self::DO_STANDARD_FORBIDDEN => array( + 'icon' => 'fa-ban', + 'color' => 'violet', + 'name' => pht('Forbidden'), + ), ); } @@ -381,6 +391,8 @@ final public function renderEffectDescription($type, $data) { return pht( 'This action does not support rules of type "%s".', $data); + case self::DO_STANDARD_FORBIDDEN: + return HeraldStateReasons::getExplanation($data); } return null; diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 78ce862945..1c50c0b5ee 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -37,6 +37,7 @@ abstract class HeraldAdapter extends Phobject { private $fieldMap; private $actionMap; private $edgeCache = array(); + private $forbiddenActions = array(); public function getEmailPHIDs() { return array_values($this->emailPHIDs); @@ -1116,4 +1117,38 @@ public function loadEdgePHIDs($type) { return $this->edgeCache[$type]; } + +/* -( Forbidden Actions )-------------------------------------------------- */ + + + final public function getForbiddenActions() { + return array_keys($this->forbiddenActions); + } + + final public function setForbiddenAction($action, $reason) { + $this->forbiddenActions[$action] = $reason; + return $this; + } + + final public function getRequiredFieldStates($field_key) { + return $this->requireFieldImplementation($field_key) + ->getRequiredAdapterStates(); + } + + final public function getRequiredActionStates($action_key) { + return $this->requireActionImplementation($action_key) + ->getRequiredAdapterStates(); + } + + final public function getForbiddenReason($action) { + if (!isset($this->forbiddenActions[$action])) { + throw new Exception( + pht( + 'Action "%s" is not forbidden!', + $action)); + } + + return $this->forbiddenActions[$action]; + } + } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index a9509beb33..dc7ca676e3 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -273,7 +273,11 @@ private function buildActionTranscriptPanel(HeraldTranscript $xscript) { ->setTarget(phutil_tag('strong', array(), pht('Conditions')))); foreach ($cond_xscripts as $cond_xscript) { - if ($cond_xscript->getResult()) { + if ($cond_xscript->isForbidden()) { + $icon = 'fa-ban'; + $color = 'indigo'; + $result = pht('Forbidden'); + } else if ($cond_xscript->getResult()) { $icon = 'fa-check'; $color = 'green'; $result = pht('Passed'); @@ -284,12 +288,17 @@ private function buildActionTranscriptPanel(HeraldTranscript $xscript) { } if ($cond_xscript->getNote()) { + $note_text = $cond_xscript->getNote(); + if ($cond_xscript->isForbidden()) { + $note_text = HeraldStateReasons::getExplanation($note_text); + } + $note = phutil_tag( 'div', array( 'class' => 'herald-condition-note', ), - $cond_xscript->getNote()); + $note_text); } else { $note = null; } @@ -310,7 +319,12 @@ private function buildActionTranscriptPanel(HeraldTranscript $xscript) { $cond_list->addItem($cond_item); } - if ($rule_xscript->getResult()) { + if ($rule_xscript->isForbidden()) { + $last_icon = 'fa-ban'; + $last_color = 'indigo'; + $last_result = pht('Forbidden'); + $last_note = pht('Object state prevented rule evaluation.'); + } else if ($rule_xscript->getResult()) { $last_icon = 'fa-check-circle'; $last_color = 'green'; $last_result = pht('Passed'); diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index df81117b80..b8e1db5626 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -12,6 +12,9 @@ final class HeraldEngine extends Phobject { protected $object; private $dryRun; + private $forbiddenFields = array(); + private $forbiddenActions = array(); + public function setDryRun($dry_run) { $this->dryRun = $dry_run; return $this; @@ -76,39 +79,42 @@ public function applyRules(array $rules, HeraldAdapter $object) { // This is not a dry run, and this rule is only supposed to be // applied a single time, and it's already been applied... // That means automatic failure. - $xscript = id(new HeraldRuleTranscript()) - ->setRuleID($rule->getID()) + $this->newRuleTranscript($rule) ->setResult(false) - ->setRuleName($rule->getName()) - ->setRuleOwner($rule->getAuthorPHID()) ->setReason( pht( 'This rule is only supposed to be repeated a single time, '. 'and it has already been applied.')); - $this->transcript->addRuleTranscript($xscript); + $rule_matches = false; } else { - $rule_matches = $this->doesRuleMatch($rule, $object); + if ($this->isForbidden($rule, $object)) { + $this->newRuleTranscript($rule) + ->setResult(HeraldRuleTranscript::RESULT_FORBIDDEN) + ->setReason( + pht( + 'Object state is not compatible with rule.')); + + $rule_matches = false; + } else { + $rule_matches = $this->doesRuleMatch($rule, $object); + } } } catch (HeraldRecursiveConditionsException $ex) { $names = array(); - foreach ($this->stack as $rule_id => $ignored) { - $names[] = '"'.$rules[$rule_id]->getName().'"'; + foreach ($this->stack as $rule_phid => $ignored) { + $names[] = '"'.$rules[$rule_phid]->getName().'"'; } $names = implode(', ', $names); - foreach ($this->stack as $rule_id => $ignored) { - $xscript = new HeraldRuleTranscript(); - $xscript->setRuleID($rule_id); - $xscript->setResult(false); - $xscript->setReason( - pht( - "Rules %s are recursively dependent upon one another! ". - "Don't do this! You have formed an unresolvable cycle in the ". - "dependency graph!", - $names)); - $xscript->setRuleName($rules[$rule_id]->getName()); - $xscript->setRuleOwner($rules[$rule_id]->getAuthorPHID()); - $this->transcript->addRuleTranscript($xscript); + foreach ($this->stack as $rule_phid => $ignored) { + $this->newRuleTranscript($rules[$rule_phid]) + ->setResult(false) + ->setReason( + pht( + "Rules %s are recursively dependent upon one another! ". + "Don't do this! You have formed an unresolvable cycle in the ". + "dependency graph!", + $names)); } $rule_matches = false; } @@ -309,14 +315,9 @@ public function doesRuleMatch( } } - $rule_transcript = new HeraldRuleTranscript(); - $rule_transcript->setRuleID($rule->getID()); - $rule_transcript->setResult($result); - $rule_transcript->setReason($reason); - $rule_transcript->setRuleName($rule->getName()); - $rule_transcript->setRuleOwner($rule->getAuthorPHID()); - - $this->transcript->addRuleTranscript($rule_transcript); + $this->newRuleTranscript($rule) + ->setResult($result) + ->setReason($reason); return $result; } @@ -327,16 +328,7 @@ protected function doesConditionMatch( HeraldAdapter $object) { $object_value = $this->getConditionObjectValue($condition, $object); - $test_value = $condition->getValue(); - - $cond = $condition->getFieldCondition(); - - $transcript = new HeraldConditionTranscript(); - $transcript->setRuleID($rule->getID()); - $transcript->setConditionID($condition->getID()); - $transcript->setFieldName($condition->getFieldName()); - $transcript->setCondition($cond); - $transcript->setTestValue($test_value); + $transcript = $this->newConditionTranscript($rule, $condition); try { $result = $object->doesConditionMatch( @@ -351,8 +343,6 @@ protected function doesConditionMatch( $transcript->setResult($result); - $this->transcript->addConditionTranscript($transcript); - return $result; } @@ -446,4 +436,136 @@ private function canRuleApplyToObject( return false; } + private function newRuleTranscript(HeraldRule $rule) { + $xscript = id(new HeraldRuleTranscript()) + ->setRuleID($rule->getID()) + ->setRuleName($rule->getName()) + ->setRuleOwner($rule->getAuthorPHID()); + + $this->transcript->addRuleTranscript($xscript); + + return $xscript; + } + + private function newConditionTranscript( + HeraldRule $rule, + HeraldCondition $condition) { + + $xscript = id(new HeraldConditionTranscript()) + ->setRuleID($rule->getID()) + ->setConditionID($condition->getID()) + ->setFieldName($condition->getFieldName()) + ->setCondition($condition->getFieldCondition()) + ->setTestValue($condition->getValue()); + + $this->transcript->addConditionTranscript($xscript); + + return $xscript; + } + + private function newApplyTranscript( + HeraldAdapter $adapter, + HeraldRule $rule, + HeraldActionRecord $action) { + + $effect = id(new HeraldEffect()) + ->setObjectPHID($adapter->getPHID()) + ->setAction($action->getAction()) + ->setTarget($action->getTarget()) + ->setRule($rule); + + $xscript = new HeraldApplyTranscript($effect, false); + + $this->transcript->addApplyTranscript($xscript); + + return $xscript; + } + + private function isForbidden( + HeraldRule $rule, + HeraldAdapter $adapter) { + + $forbidden = $adapter->getForbiddenActions(); + if (!$forbidden) { + return false; + } + + $forbidden = array_fuse($forbidden); + + $is_forbidden = false; + + foreach ($rule->getConditions() as $condition) { + $field_key = $condition->getFieldName(); + + if (!isset($this->forbiddenFields[$field_key])) { + $reason = null; + + try { + $states = $adapter->getRequiredFieldStates($field_key); + } catch (Exception $ex) { + $states = array(); + } + + foreach ($states as $state) { + if (!isset($forbidden[$state])) { + continue; + } + $reason = $adapter->getForbiddenReason($state); + break; + } + + $this->forbiddenFields[$field_key] = $reason; + } + + $forbidden_reason = $this->forbiddenFields[$field_key]; + if ($forbidden_reason !== null) { + $this->newConditionTranscript($rule, $condition) + ->setResult(HeraldConditionTranscript::RESULT_FORBIDDEN) + ->setNote($forbidden_reason); + + $is_forbidden = true; + } + } + + foreach ($rule->getActions() as $action_record) { + $action_key = $action_record->getAction(); + + if (!isset($this->forbiddenActions[$action_key])) { + $reason = null; + + try { + $states = $adapter->getRequiredActionStates($action_key); + } catch (Exception $ex) { + $states = array(); + } + + foreach ($states as $state) { + if (!isset($forbidden[$state])) { + continue; + } + $reason = $adapter->getForbiddenReason($state); + break; + } + + $this->forbiddenActions[$action_key] = $reason; + } + + $forbidden_reason = $this->forbiddenActions[$action_key]; + if ($forbidden_reason !== null) { + $this->newApplyTranscript($adapter, $rule, $action_record) + ->setAppliedReason( + array( + array( + 'type' => HeraldAction::DO_STANDARD_FORBIDDEN, + 'data' => $forbidden_reason, + ), + )); + + $is_forbidden = true; + } + } + + return $is_forbidden; + } + } diff --git a/src/applications/herald/field/HeraldField.php b/src/applications/herald/field/HeraldField.php index 2abed0ff1d..408a76f7fb 100644 --- a/src/applications/herald/field/HeraldField.php +++ b/src/applications/herald/field/HeraldField.php @@ -20,6 +20,10 @@ public function getFieldGroupKey() { return null; } + public function getRequiredAdapterStates() { + return array(); + } + protected function getHeraldFieldStandardType() { throw new PhutilMethodNotImplementedException(); } diff --git a/src/applications/herald/state/HeraldBuildableState.php b/src/applications/herald/state/HeraldBuildableState.php new file mode 100644 index 0000000000..d19ae87c36 --- /dev/null +++ b/src/applications/herald/state/HeraldBuildableState.php @@ -0,0 +1,7 @@ +setAncestorClass(__CLASS__) + ->execute(); + } + + final public static function getExplanation($reason) { + $reasons = self::getAllReasons(); + + foreach ($reasons as $reason_implementation) { + $explanation = $reason_implementation->explainReason($reason); + if ($explanation !== null) { + return $explanation; + } + } + + return pht('Unknown reason ("%s").', $reason); + } + +} diff --git a/src/applications/herald/storage/transcript/HeraldConditionTranscript.php b/src/applications/herald/storage/transcript/HeraldConditionTranscript.php index 36a534e6b0..a7096b6880 100644 --- a/src/applications/herald/storage/transcript/HeraldConditionTranscript.php +++ b/src/applications/herald/storage/transcript/HeraldConditionTranscript.php @@ -10,6 +10,8 @@ final class HeraldConditionTranscript extends Phobject { protected $note; protected $result; + const RESULT_FORBIDDEN = 'forbidden'; + public function setRuleID($rule_id) { $this->ruleID = $rule_id; return $this; @@ -72,4 +74,9 @@ public function setResult($result) { public function getResult() { return $this->result; } + + public function isForbidden() { + return ($this->getResult() === self::RESULT_FORBIDDEN); + } + } diff --git a/src/applications/herald/storage/transcript/HeraldRuleTranscript.php b/src/applications/herald/storage/transcript/HeraldRuleTranscript.php index d12f1f9dd9..8928a74cf6 100644 --- a/src/applications/herald/storage/transcript/HeraldRuleTranscript.php +++ b/src/applications/herald/storage/transcript/HeraldRuleTranscript.php @@ -9,6 +9,12 @@ final class HeraldRuleTranscript extends Phobject { protected $ruleName; protected $ruleOwner; + const RESULT_FORBIDDEN = 'forbidden'; + + public function isForbidden() { + return ($this->getResult() === self::RESULT_FORBIDDEN); + } + public function setResult($result) { $this->result = $result; return $this; diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php index 2b29fe2443..74fb879fe7 100644 --- a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php @@ -6,6 +6,12 @@ abstract class PhabricatorMetaMTAEmailHeraldAction const DO_SEND = 'do.send'; const DO_FORCE = 'do.force'; + public function getRequiredAdapterStates() { + return array( + HeraldMailableState::STATECONST, + ); + } + public function supportsObject($object) { // NOTE: This implementation lacks generality, but there's no great way to // figure out if something generates email right now. From 0da3f34728e313b0a2074984911cf320ef82c106 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 27 Oct 2017 11:27:53 -0700 Subject: [PATCH 255/865] Provide "differential.diff.search" Summary: See PHI90. For now, this only provides a limited amount of information, but should satisfy the use case in PHI90 and build toward a more complete version in the future. Test Plan: Used new Conduit method to retrieve information about diffs. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18744 --- src/__phutil_library_map__.php | 5 ++ ...DifferentialDiffSearchConduitAPIMethod.php | 18 +++++ ...DifferentialQueryDiffsConduitAPIMethod.php | 10 +++ .../query/DifferentialDiffQuery.php | 25 ++++++ .../query/DifferentialDiffSearchEngine.php | 79 ++++++++++++++++++ .../differential/storage/DifferentialDiff.php | 81 ++++++++++++++++++- 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php create mode 100644 src/applications/differential/query/DifferentialDiffSearchEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2040568496..ff1f00c821 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -439,6 +439,8 @@ 'DifferentialDiffQuery' => 'applications/differential/query/DifferentialDiffQuery.php', 'DifferentialDiffRepositoryHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryHeraldField.php', 'DifferentialDiffRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryProjectsHeraldField.php', + 'DifferentialDiffSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php', + 'DifferentialDiffSearchEngine' => 'applications/differential/query/DifferentialDiffSearchEngine.php', 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php', 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', @@ -5435,6 +5437,7 @@ 'HarbormasterBuildkiteBuildableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'DifferentialDiffAffectedFilesHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffAuthorHeraldField' => 'DifferentialDiffHeraldField', @@ -5453,6 +5456,8 @@ 'DifferentialDiffQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialDiffRepositoryHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField', + 'DifferentialDiffSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'DifferentialDiffSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DifferentialDiffTestCase' => 'PhutilTestCase', 'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction', 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php new file mode 100644 index 0000000000..d4222510d7 --- /dev/null +++ b/src/applications/differential/conduit/DifferentialDiffSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ + 'optional list', diff --git a/src/applications/differential/query/DifferentialDiffQuery.php b/src/applications/differential/query/DifferentialDiffQuery.php index f779e40c26..3b1dd3ae62 100644 --- a/src/applications/differential/query/DifferentialDiffQuery.php +++ b/src/applications/differential/query/DifferentialDiffQuery.php @@ -6,6 +6,7 @@ final class DifferentialDiffQuery private $ids; private $phids; private $revisionIDs; + private $revisionPHIDs; private $commitPHIDs; private $hasRevision; @@ -27,6 +28,11 @@ public function withRevisionIDs(array $revision_ids) { return $this; } + public function withRevisionPHIDs(array $revision_phids) { + $this->revisionPHIDs = $revision_phids; + return $this; + } + public function withCommitPHIDs(array $phids) { $this->commitPHIDs = $phids; return $this; @@ -160,6 +166,25 @@ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { } } + if ($this->revisionPHIDs !== null) { + $viewer = $this->getViewer(); + + $revisions = id(new DifferentialRevisionQuery()) + ->setViewer($viewer) + ->setParentQuery($this) + ->withPHIDs($this->revisionPHIDs) + ->execute(); + $revision_ids = mpull($revisions, 'getID'); + if (!$revision_ids) { + throw new PhabricatorEmptyQueryException(); + } + + $where[] = qsprintf( + $conn, + 'revisionID IN (%Ls)', + $revision_ids); + } + return $where; } diff --git a/src/applications/differential/query/DifferentialDiffSearchEngine.php b/src/applications/differential/query/DifferentialDiffSearchEngine.php new file mode 100644 index 0000000000..89feeb5c3e --- /dev/null +++ b/src/applications/differential/query/DifferentialDiffSearchEngine.php @@ -0,0 +1,79 @@ +newQuery(); + + if ($map['revisionPHIDs']) { + $query->withRevisionPHIDs($map['revisionPHIDs']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Revisions')) + ->setKey('revisionPHIDs') + ->setAliases(array('revision', 'revisions', 'revisionPHID')) + ->setDescription( + pht('Find diffs attached to a particular revision.')), + ); + } + + protected function getURI($path) { + return '/differential/diff/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array(); + + $names['all'] = pht('All Diffs'); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + $viewer = $this->requireViewer(); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $revisions, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($revisions, 'DifferentialDiff'); + + $viewer = $this->requireViewer(); + + // NOTE: This is only exposed to Conduit, so we don't currently render + // results. + + return id(new PhabricatorApplicationSearchResultView()); + } + +} diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index f7e85cd835..d70cdbbdf5 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -9,7 +9,8 @@ final class DifferentialDiff HarbormasterCircleCIBuildableInterface, HarbormasterBuildkiteBuildableInterface, PhabricatorApplicationTransactionInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $revisionID; protected $authorPHID; @@ -740,4 +741,82 @@ public function destroyObjectPermanently( $this->saveTransaction(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('revisionPHID') + ->setType('phid') + ->setDescription(pht('Associated revision PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('authorPHID') + ->setType('phid') + ->setDescription(pht('Revision author PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('repositoryPHID') + ->setType('phid') + ->setDescription(pht('Associated repository PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('refs') + ->setType('map') + ->setDescription(pht('List of related VCS references.')), + ); + } + + public function getFieldValuesForConduit() { + $refs = array(); + + $branch = $this->getBranch(); + if (strlen($branch)) { + $refs[] = array( + 'type' => 'branch', + 'name' => $branch, + ); + } + + $onto = $this->loadTargetBranch(); + if (strlen($onto)) { + $refs[] = array( + 'type' => 'onto', + 'name' => $onto, + ); + } + + $base = $this->getSourceControlBaseRevision(); + if (strlen($base)) { + $refs[] = array( + 'type' => 'base', + 'identifier' => $base, + ); + } + + $bookmark = $this->getBookmark(); + if (strlen($bookmark)) { + $refs[] = array( + 'type' => 'bookmark', + 'name' => $bookmark, + ); + } + + $revision_phid = null; + if ($this->getRevisionID()) { + $revision_phid = $this->getRevision()->getPHID(); + } + + return array( + 'revisionPHID' => $revision_phid, + 'authorPHID' => $this->getAuthorPHID(), + 'repositoryPHID' => $this->getRepositoryPHID(), + 'refs' => $refs, + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + + } From f5336cd6e760e6089f009130bb25f2a457c50c4e Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 27 Oct 2017 10:00:34 -0700 Subject: [PATCH 256/865] Return transactions from "differential.parsecommitmessage" Summary: Depends on D18740. Prepares `arc` to receive a `--draft` flag by letting us switch to "differential.revision.edit" instead of "differential.createrevision". To "differential.revision.edit", we need a transaction list, but we can't automatically construct this list from a field map. Return the transaction list alongside the field map. The next change uses this list (if available) to switch us to the modern API method. Test Plan: Ran `arc diff` on the experiemntal branch with followup changes, got a new revision. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18741 --- ...tialParseCommitMessageConduitAPIMethod.php | 2 ++ .../DifferentialCommitMessageParser.php | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php index 59401862f0..d1ad6b67e7 100644 --- a/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php +++ b/src/applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php @@ -36,6 +36,7 @@ protected function execute(ConduitAPIRequest $request) { $field_map = $parser->parseFields($corpus); $errors = $parser->getErrors(); + $xactions = $parser->getTransactions(); $revision_id_value = idx( $field_map, @@ -49,6 +50,7 @@ protected function execute(ConduitAPIRequest $request) { 'value' => $revision_id_value, 'validDomain' => $revision_id_valid_domain, ), + 'transactions' => $xactions, ); } diff --git a/src/applications/differential/parser/DifferentialCommitMessageParser.php b/src/applications/differential/parser/DifferentialCommitMessageParser.php index 7e5a50e61f..3a73428e4b 100644 --- a/src/applications/differential/parser/DifferentialCommitMessageParser.php +++ b/src/applications/differential/parser/DifferentialCommitMessageParser.php @@ -28,6 +28,7 @@ final class DifferentialCommitMessageParser extends Phobject { private $errors; private $commitMessageFields; private $raiseMissingFieldErrors = true; + private $xactions; public static function newStandardParser(PhabricatorUser $viewer) { $key_title = DifferentialTitleCommitMessageField::FIELDKEY; @@ -134,6 +135,7 @@ public function setSummaryKey($summary_key) { */ public function parseCorpus($corpus) { $this->errors = array(); + $this->xactions = array(); $label_map = $this->getLabelMap(); $key_title = $this->titleKey; @@ -284,12 +286,25 @@ public function parseFields($corpus) { try { $result = $field->parseFieldValue($text_value); $result_map[$field_key] = $result; + + try { + $xactions = $field->getFieldTransactions($result); + foreach ($xactions as $xaction) { + $this->xactions[] = $xaction; + } + } catch (Exception $ex) { + $this->errors[] = pht( + 'Error extracting field transactions from "%s": %s', + $field->getFieldName(), + $ex->getMessage()); + } } catch (DifferentialFieldParseException $ex) { $this->errors[] = pht( 'Error parsing field "%s": %s', $field->getFieldName(), $ex->getMessage()); } + } if ($this->getRaiseMissingFieldErrors()) { @@ -317,6 +332,14 @@ public function getErrors() { } + /** + * @task parse + */ + public function getTransactions() { + return $this->xactions; + } + + /* -( Support Methods )---------------------------------------------------- */ From 28cec2f8a2bf96df572c04360c412c8436356421 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 27 Oct 2017 08:55:56 -0700 Subject: [PATCH 257/865] Allow revisions to be held as drafts, even after builds finish Summary: Ref T2543. Instead of autosubmitting revisions to "Needs Review" when builds finish, allow them to be held in "Draft" indefinitely. There's currently no UI for this. I plan to just expose it as `arc diff --draft` for now, in a followup change. Test Plan: - Created a revision (via Conduit) with "hold as draft", saw it hold as draft after builds finished. - Created a revision (normally), saw it autosubmit after builds finished. - Requested review of a "hold as draft" revision to kick it out of draft state. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18737 --- src/__phutil_library_map__.php | 2 + .../customfield/DifferentialDraftField.php | 6 +++ .../editor/DifferentialRevisionEditEngine.php | 16 +++++++ .../editor/DifferentialTransactionEditor.php | 2 +- .../storage/DifferentialRevision.php | 9 ++++ ...fferentialRevisionHoldDraftTransaction.php | 47 +++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ff1f00c821..7180a7ae04 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -550,6 +550,7 @@ 'DifferentialRevisionHasTaskRelationship' => 'applications/differential/relationships/DifferentialRevisionHasTaskRelationship.php', 'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php', 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', + 'DifferentialRevisionHoldDraftTransaction' => 'applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php', 'DifferentialRevisionIDCommitMessageField' => 'applications/differential/field/DifferentialRevisionIDCommitMessageField.php', 'DifferentialRevisionInlineTransaction' => 'applications/differential/xaction/DifferentialRevisionInlineTransaction.php', 'DifferentialRevisionInlinesController' => 'applications/differential/controller/DifferentialRevisionInlinesController.php', @@ -5592,6 +5593,7 @@ 'DifferentialRevisionHasTaskRelationship' => 'DifferentialRevisionRelationship', 'DifferentialRevisionHeraldField' => 'HeraldField', 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', + 'DifferentialRevisionHoldDraftTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionIDCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialRevisionInlineTransaction' => 'PhabricatorModularTransactionType', 'DifferentialRevisionInlinesController' => 'DifferentialController', diff --git a/src/applications/differential/customfield/DifferentialDraftField.php b/src/applications/differential/customfield/DifferentialDraftField.php index e7ed2bedb2..5d625e3ce9 100644 --- a/src/applications/differential/customfield/DifferentialDraftField.php +++ b/src/applications/differential/customfield/DifferentialDraftField.php @@ -36,6 +36,12 @@ public function getWarningsForRevisionHeader(array $handles) { return array(); } + // If the author has held this revision as a draft explicitly, don't + // show any misleading messages about it autosubmitting later. + if ($revision->getHoldAsDraft()) { + return array(); + } + $warnings = array(); $blocking_map = array( diff --git a/src/applications/differential/editor/DifferentialRevisionEditEngine.php b/src/applications/differential/editor/DifferentialRevisionEditEngine.php index 9aad031a7c..2bd3a5cdc3 100644 --- a/src/applications/differential/editor/DifferentialRevisionEditEngine.php +++ b/src/applications/differential/editor/DifferentialRevisionEditEngine.php @@ -235,6 +235,22 @@ protected function buildCustomEditFields($object) { $fields[] = $action->newEditField($object, $viewer); } + $fields[] = id(new PhabricatorBoolEditField()) + ->setKey('draft') + ->setLabel(pht('Hold as Draft')) + ->setIsConduitOnly(true) + ->setOptions( + pht('Autosubmit Once Builds Finish'), + pht('Hold as Draft')) + ->setTransactionType( + DifferentialRevisionHoldDraftTransaction::TRANSACTIONTYPE) + ->setDescription(pht('Hold revision as as draft.')) + ->setConduitDescription( + pht( + 'Change autosubmission from draft state after builds finish.')) + ->setConduitTypeDescription(pht('New "Hold as Draft" setting.')) + ->setValue($object->getHoldAsDraft()); + return $fields; } diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 9ab30c3f06..edc8fd0ad3 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1540,7 +1540,7 @@ private function loadUnbroadcastTransactions($object) { protected function didApplyTransactions($object, array $xactions) { // If a draft revision has no outstanding builds and we're automatically // making drafts public after builds finish, make the revision public. - $auto_undraft = true; + $auto_undraft = !$object->getHoldAsDraft(); if ($object->isDraft() && $auto_undraft) { $active_builds = $this->hasActiveBuilds($object); diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index da7e18ce05..467e503f6f 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -57,6 +57,7 @@ final class DifferentialRevision extends DifferentialDAO const RELATION_SUBSCRIBED = 'subd'; const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; + const PROPERTY_DRAFT_HOLD = 'draft.hold'; public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -708,6 +709,14 @@ public function shouldBroadcast() { return false; } + public function getHoldAsDraft() { + return $this->getProperty(self::PROPERTY_DRAFT_HOLD, false); + } + + public function setHoldAsDraft($hold) { + return $this->setProperty(self::PROPERTY_DRAFT_HOLD, $hold); + } + public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); diff --git a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php new file mode 100644 index 0000000000..5bc257ab62 --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php @@ -0,0 +1,47 @@ +getHoldAsDraft(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setHoldAsDraft($value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s held this revision as a draft.', + $this->renderAuthor()); + } else { + return pht( + '%s set this revision to automatically submit once builds complete.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + if ($this->getNewValue()) { + return pht( + '%s held %s as a draft.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s set %s to automatically submit once builds complete.', + $this->renderAuthor(), + $this->renderObject()); + } + } + +} From 7fa0d066bc459e47e7381476c6fd708355cb9104 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 10:29:33 -0700 Subject: [PATCH 258/865] Don't run Herald build rules when Differential revisions are updated automatically Summary: Ref T2543. After D18731, Herald build rules run more often, but now incorrectly try to run builds when Diffusion closes a revision because a commit landed. Test Plan: Made some mundane updates locally; this is tricky to test comprehensively locally so I'm mostly planning to just push it to `secure`. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18745 --- .../editor/DifferentialTransactionEditor.php | 22 ++++++++++++++++--- .../herald/DifferentialHeraldStateReasons.php | 4 ++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index edc8fd0ad3..70c3272a83 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1204,16 +1204,32 @@ protected function buildHeraldAdapter( // edited the title or changed subscribers), prevent "Run build plan" // and other similar rules from acting yet, since the build results will // not (or, at least, should not) change unless the actual source changes. + // We also don't run Differential builds if the update was caused by + // discovering a commit, as the expectation is that Diffusion builds take + // over once things land. $has_update = false; + $has_commit = false; + $type_update = DifferentialTransaction::TYPE_UPDATE; foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == $type_update) { + if ($xaction->getTransactionType() != $type_update) { + continue; + } + + if ($xaction->getMetadataValue('isCommitUpdate')) { + $has_commit = true; + } else { $has_update = true; - break; } + + break; } - if (!$has_update) { + if ($has_commit) { + $adapter->setForbiddenAction( + HeraldBuildableState::STATECONST, + DifferentialHeraldStateReasons::REASON_LANDED); + } else if (!$has_update) { $adapter->setForbiddenAction( HeraldBuildableState::STATECONST, DifferentialHeraldStateReasons::REASON_UNCHANGED); diff --git a/src/applications/differential/herald/DifferentialHeraldStateReasons.php b/src/applications/differential/herald/DifferentialHeraldStateReasons.php index d3259560d5..92d2ca3067 100644 --- a/src/applications/differential/herald/DifferentialHeraldStateReasons.php +++ b/src/applications/differential/herald/DifferentialHeraldStateReasons.php @@ -5,6 +5,7 @@ final class DifferentialHeraldStateReasons const REASON_DRAFT = 'differential.draft'; const REASON_UNCHANGED = 'differential.unchanged'; + const REASON_LANDED = 'differential.landed'; public function explainReason($reason) { $reasons = array( @@ -14,6 +15,9 @@ public function explainReason($reason) { self::REASON_UNCHANGED => pht( 'The update which triggered Herald did not update the diff for '. 'this revision, so builds will not run.'), + self::REASON_LANDED => pht( + 'The update which triggered Herald was an automatic update in '. + 'response to discovering a commit, so builds will not run.'), ); return idx($reasons, $reason); From 90d0f8ac6cf5eb12406f330a624bbf87c156f517 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 10:49:19 -0700 Subject: [PATCH 259/865] Revert changes to Diffusion blame view Summary: Ref PHI174. This reverts most of these changes: - 37843127e94a878a7f5bf2c65c8e7004bc65c68a / D18481 - 94cad30ac3f052a711ececf7e370bf5c0071827f / D18474 - 12ae08b6b1a1b7c330593e76c32817f7cdbc87dd / D18473 - 0a013341721f8b1fc249047fe6db26062138b562 / D18462 - ac91ab1ef9196eee0deabfd70157ccc0d53d666e / D18452 These changes made the Diffusion blame view very similar to GitHub's blame view. See D18452 for a before/after of the bulk of these changes; the other revisions are bugfixes. I think this was generally a step backward, and not motivated by solving a specific problem. I've found the new UI less usable than the old one, and at least one install (see PHI174) also has. In particular, the revision/commit titles are very bulky and not terribly useful; the date column also isn't terribly useful; the "age" color actually IS pretty useful and was heavily de-emphasized. I've kept one bugfix here (missing `'a'` tag type) and kept the upgraded icon for "Skip Past This Commit". I'm going to follow this up with some additional changes: - Show a small author profile icon, similar to GitHub, to address PHI174 more directly. - Try a zebra-stripe on blocks of rows to make it more clear where changes affected by a particular commit begin and end. - Try a hue shift, not just a brightness/saturation shift, to make the "age" color more distinct. - Try computing colors as even steps, not based purely on age. Currently, if a file has one long-distant commit and several recent commits, all the recent ones show up as very bright green. I think this would probably be more useful if they were distributed more evenly across the available color bands. Test Plan: Viewed blame views in Diffusion, saw a more compact UI similar to the old UI. {F5251019} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18746 --- resources/celerity/map.php | 4 +- .../controller/DiffusionBrowseController.php | 109 +++++++++++------- .../diffusion/diffusion-source.css | 59 +++------- 3 files changed, 88 insertions(+), 84 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ce069dbd11..517827a900 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', - 'rsrc/css/application/diffusion/diffusion-source.css' => '69ac9399', + 'rsrc/css/application/diffusion/diffusion-source.css' => '3a1056d8', 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -572,7 +572,7 @@ 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => '69ac9399', + 'diffusion-source-css' => '3a1056d8', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index b4c0352d53..be9d4fbb9b 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1127,6 +1127,10 @@ private function buildDisplayRows( )); } + $skip_text = pht('Skip Past This Commit'); + $skip_icon = id(new PHUIIconView()) + ->setIcon('fa-caret-square-o-left'); + foreach ($display as $line_index => $line) { $row = array(); @@ -1142,14 +1146,12 @@ private function buildDisplayRows( $revision_link = null; $commit_link = null; $before_link = null; - $commit_date = null; - $style = 'border-right: 3px solid '.$line['color'].';'; + $style = 'background: '.$line['color'].';'; if ($identifier && !$line['duplicate']) { if (isset($commit_links[$identifier])) { - $commit_link = $commit_links[$identifier]['link']; - $commit_date = $commit_links[$identifier]['date']; + $commit_link = $commit_links[$identifier]; } if (isset($revision_map[$identifier])) { @@ -1160,10 +1162,6 @@ private function buildDisplayRows( } $skip_href = $line_href.'?before='.$identifier.'&view=blame'; - $skip_text = pht('Skip Past This Commit'); - $icon = id(new PHUIIconView()) - ->setIcon('fa-caret-square-o-left'); - $before_link = javelin_tag( 'a', array( @@ -1175,7 +1173,7 @@ private function buildDisplayRows( 'size' => 300, ), ), - $icon); + $skip_icon); } if ($show_blame) { @@ -1186,41 +1184,33 @@ private function buildDisplayRows( ), $before_link); - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-rev-link', - ), - $commit_link); - - if ($revision_map) { - $row[] = phutil_tag( - 'th', - array( - 'class' => 'diffusion-blame-revision', - ), - $revision_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-blame-date', + 'class' => 'diffusion-rev-link', ), - $commit_date); + $object_links); } $line_link = phutil_tag( 'a', array( 'href' => $line_href, + 'style' => $style, ), $line_number); $row[] = javelin_tag( 'th', array( - 'class' => 'diffusion-line-link ', + 'class' => 'diffusion-line-link', 'sigil' => 'phabricator-source-line', 'style' => $style, ), @@ -1536,6 +1526,33 @@ private function loadParentCommitOf($commit) { return head($parents); } + private function renderRevisionTooltip( + DifferentialRevision $revision, + $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, + $author) { + + $viewer = $this->getRequest()->getUser(); + + $date = phabricator_date($commit->getEpoch(), $viewer); + $summary = trim($commit->getSummary()); + + return "{$summary}\n{$date} \xC2\xB7 {$author}"; + } + protected function markupText($text) { $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); $engine->setConfig('viewer', $this->getRequest()->getUser()); @@ -1743,6 +1760,9 @@ private function loadBlame($path, $commit, $timeout) { ->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 { @@ -1754,27 +1774,25 @@ private function loadBlame($path, $commit, $timeout) { private function renderCommitLinks(array $commits, $handles) { $links = array(); - $viewer = $this->getViewer(); foreach ($commits as $identifier => $commit) { - $date = phabricator_date($commit->getEpoch(), $viewer); - $summary = trim($commit->getSummary()); - - $commit_link = phutil_tag( - 'a', - array( - 'href' => $commit->getURI(), - ), - $summary); + $tooltip = $this->renderCommitTooltip( + $commit, + $commit->renderAuthorShortName($handles)); - $commit_date = phutil_tag( + $commit_link = javelin_tag( 'a', array( 'href' => $commit->getURI(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), ), - $date); + $commit->getLocalName()); - $links[$identifier]['link'] = $commit_link; - $links[$identifier]['date'] = $commit_date; + $links[$identifier] = $commit_link; } return $links; @@ -1785,10 +1803,19 @@ private function renderRevisionLinks(array $revisions, $handles) { foreach ($revisions as $revision) { $revision_id = $revision->getID(); - $revision_link = phutil_tag( + + $tooltip = $this->renderRevisionTooltip($revision, $handles); + + $revision_link = javelin_tag( 'a', array( 'href' => '/'.$revision->getMonogram(), + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $tooltip, + 'align' => 'E', + 'size' => 600, + ), ), $revision->getMonogram()); diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 3b8b9a8a16..9ade2d3022 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -13,22 +13,24 @@ -webkit-overflow-scrolling: touch; } -.diffusion-source tr.phabricator-source-highlight th, -.diffusion-source tr.phabricator-source-highlight td { - background: {$gentle.highlight}; +.diffusion-source tr.phabricator-source-highlight { + background: {$sh-yellowbackground}; } .diffusion-source th { text-align: right; vertical-align: top; - color: {$darkbluetext}; + background: {$lightgreybackground}; + color: {$bluetext}; border-right: 1px solid {$thinblueborder}; } .diffusion-source td { vertical-align: top; white-space: pre-wrap; - padding: 3px 12px; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 8px; width: 100%; word-break: break-all; } @@ -43,18 +45,12 @@ } .diffusion-blame-link, -.diffusion-rev-link, -.diffusion-blame-date { +.diffusion-rev-link { white-space: nowrap; } -.diffusion-blame-date, -.diffusion-blame-link, -.diffusion-blame-revision, -.diffusion-rev-link { - background: {$lightgreybackground}; - font: {$basefont}; - font-size: {$smallerfontsize}; +.diffusion-blame-link { + min-width: 28px; } .diffusion-source th.diffusion-rev-link { @@ -62,23 +58,16 @@ min-width: 130px; } -.diffusion-source a { +.diffusion-blame-link a, +.diffusion-rev-link a, +.diffusion-line-link a { color: {$darkbluetext}; } -.diffusion-rev-link a { - max-width: 300px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin: 3px 8px; - display: block; -} - -.diffusion-blame-date a, -.diffusion-blame-revision a { - float: right; - margin: 3px 8px; +.diffusion-rev-link a, +.diffusion-rev-link span { + margin: 2px 8px 0; + display: inline-block; } .diffusion-rev-link span { @@ -91,19 +80,7 @@ .diffusion-line-link a { /* Give the user a larger click target. */ display: block; - padding: 4px 8px 3px; -} - -.diffusion-line-link a { - color: {$lightgreytext}; -} - -.diffusion-blame-link a .phui-icon-view { - color: {$bluetext}; -} - -.diffusion-blame-link a:hover .phui-icon-view { - color: {$sky}; + padding: 2px 8px; } .diffusion-line-link { From bde71324f82ba0eaeabeeff8eb6db84505b1614f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 11:28:31 -0700 Subject: [PATCH 260/865] Show small author portraits in Diffusion blame view Summary: Depends on D18746. See PHI174. Adds small author portraits next to each blame line (this is similar to GitHub). Test Plan: My local test data isn't that great since I don't have commits from a lot of accounts, but looks functional: {F5251056} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18747 --- resources/celerity/map.php | 4 +- .../controller/DiffusionBrowseController.php | 49 +++++++++++++++++++ .../diffusion/diffusion-source.css | 12 ++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 517827a900..b7cb976a16 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', - 'rsrc/css/application/diffusion/diffusion-source.css' => '3a1056d8', + 'rsrc/css/application/diffusion/diffusion-source.css' => 'd96b3f9f', 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -572,7 +572,7 @@ 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => '3a1056d8', + 'diffusion-source-css' => 'd96b3f9f', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index be9d4fbb9b..f4bd2bdebf 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -969,6 +969,24 @@ private function buildDisplayRows( $handles = $viewer->loadHandles($phids); + $author_phids = array(); + $author_map = array(); + foreach ($blame_commits as $commit) { + $commit_identifier = $commit->getCommitIdentifier(); + + $author_phid = ''; + if (isset($revision_map[$commit_identifier])) { + $revision_id = $revision_map[$commit_identifier]; + $revision = $revisions[$revision_id]; + $author_phid = $revision->getAuthorPHID(); + } else { + $author_phid = $commit->getAuthorPHID(); + } + + $author_map[$commit_identifier] = $author_phid; + $author_phids[$author_phid] = $author_phid; + } + $colors = array(); if ($blame_commits) { $epochs = array(); @@ -1113,6 +1131,7 @@ private function buildDisplayRows( // blame outputs. $commit_links = $this->renderCommitLinks($blame_commits, $handles); $revision_links = $this->renderRevisionLinks($revisions, $handles); + $author_links = $this->renderAuthorLinks($author_map, $handles); if ($this->coverage) { require_celerity_resource('differential-changeset-view-css'); @@ -1145,6 +1164,7 @@ private function buildDisplayRows( $revision_link = null; $commit_link = null; + $author_link = null; $before_link = null; $style = 'background: '.$line['color'].';'; @@ -1152,6 +1172,7 @@ private function buildDisplayRows( if ($identifier && !$line['duplicate']) { if (isset($commit_links[$identifier])) { $commit_link = $commit_links[$identifier]; + $author_link = $author_links[$author_map[$identifier]]; } if (isset($revision_map[$identifier])) { @@ -1185,6 +1206,7 @@ private function buildDisplayRows( $before_link); $object_links = array(); + $object_links[] = $author_link; $object_links[] = $commit_link; if ($revision_link) { $object_links[] = phutil_tag('span', array(), '/'); @@ -1772,6 +1794,33 @@ private function loadBlame($path, $commit, $timeout) { return array($identifiers, $commits); } + private function renderAuthorLinks(array $authors, $handles) { + $links = array(); + + foreach ($authors as $phid) { + if (!strlen($phid)) { + // This means we couldn't identify an author for the commit or the + // revision. We just render a blank for alignment. + $style = null; + $href = null; + } else { + $src = $handles[$phid]->getImageURI(); + $style = 'background-image: url('http://github.com/.$src.');'; + $href = $handles[$phid]->getURI(); + } + + $links[$phid] = javelin_tag( + $href ? 'a' : 'span', + array( + 'class' => 'diffusion-author-link', + 'style' => $style, + 'href' => $href, + )); + } + + return $links; + } + private function renderCommitLinks(array $commits, $handles) { $links = array(); foreach ($commits as $identifier => $commit) { diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 9ade2d3022..06e7d74477 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -66,7 +66,7 @@ .diffusion-rev-link a, .diffusion-rev-link span { - margin: 2px 8px 0; + margin: 0 8px 0 0; display: inline-block; } @@ -90,3 +90,13 @@ -ms-user-select: none; user-select: none; } + +.diffusion-rev-link .diffusion-author-link { + display: inline-block; + padding: 0; + margin: 2px 4px -4px 4px; + width: 16px; + height: 16px; + background-size: 100% 100%; + background-repeat: no-repeat; +} From a4b934cad2b520fb2de57a5f08eeffa6aff28ffe Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 11:45:38 -0700 Subject: [PATCH 261/865] Clean up Differential draft mail behaviors Summary: Ref T2543. Fixes two relatively minor things: - When builds finish in Harbormaster, send mail "From" the author. - Set the `firstBroadcast` flag so that initial mail picks up earlier history (notably, the "reviewers" line). For now, I'm not setting `firstBroadcast` on explicit "Request Review" (but maybe we should), and not trying to deal with weird cases where you leave a bunch of comments on a draft. Those might be fine as-is or may get tweaked later. Test Plan: Created a revision with Harbormaster builds, ran builds, saw initial email come "From" the right user with more metadata. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18748 --- .../editor/DifferentialTransactionEditor.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 70c3272a83..79a582a43f 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -9,6 +9,7 @@ final class DifferentialTransactionEditor private $didExpandInlineState = false; private $hasReviewTransaction = false; private $affectedPaths; + private $firstBroadcast = false; public function getEditorApplicationClass() { return 'PhabricatorDifferentialApplication'; @@ -27,7 +28,7 @@ public function getCreateObjectTitleForFeed($author, $object) { } public function isFirstBroadcast() { - return $this->getIsNewObject(); + return $this->firstBroadcast; } public function getDiffUpdateTransaction(array $xactions) { @@ -1449,11 +1450,13 @@ protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { protected function getCustomWorkerState() { return array( 'changedPriorToCommitURI' => $this->changedPriorToCommitURI, + 'firstBroadcast' => $this->firstBroadcast, ); } protected function loadCustomWorkerState(array $state) { $this->changedPriorToCommitURI = idx($state, 'changedPriorToCommitURI'); + $this->firstBroadcast = idx($state, 'firstBroadcast'); return $this; } @@ -1566,6 +1569,19 @@ protected function didApplyTransactions($object, array $xactions) { // natural and more useful. $author_phid = $object->getAuthorPHID(); + // Additionally, we change the acting PHID for the transaction set + // to the author if it isn't already a user so that mail comes from + // the natural author. + $acting_phid = $this->getActingAsPHID(); + $user_type = PhabricatorPeopleUserPHIDType::TYPECONST; + if (phid_get_type($acting_phid) != $user_type) { + $this->setActingAsPHID($author_phid); + } + + // Mark this as the first broadcast we're sending about the revision + // so mail can generate specially. + $this->firstBroadcast = true; + $xaction = $object->getApplicationTransactionTemplate() ->setAuthorPHID($author_phid) ->setTransactionType( From 80ebe401e5849799cbc5568f25cca927b7b1b621 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 12:23:16 -0700 Subject: [PATCH 262/865] Tweak padding/spacing on Diffusion blame view for profile pictures Summary: Give profile images a little more space, fix "/" spacing, add a tooltip. Test Plan: {F5251205} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18749 --- resources/celerity/map.php | 4 ++-- .../diffusion/controller/DiffusionBrowseController.php | 5 +++++ .../rsrc/css/application/diffusion/diffusion-source.css | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b7cb976a16..c9e1a5b351 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -73,7 +73,7 @@ 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', - 'rsrc/css/application/diffusion/diffusion-source.css' => 'd96b3f9f', + 'rsrc/css/application/diffusion/diffusion-source.css' => '5f35a3bd', 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', @@ -572,7 +572,7 @@ 'diffusion-icons-css' => '0c15255e', 'diffusion-readme-css' => '419dd5b6', 'diffusion-repository-css' => 'ee6f20ec', - 'diffusion-source-css' => 'd96b3f9f', + 'diffusion-source-css' => '5f35a3bd', 'diviner-shared-css' => '896f1d43', 'font-fontawesome' => 'e838e088', 'font-lato' => 'c7ccd872', diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index f4bd2bdebf..4985ac2bb7 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1815,6 +1815,11 @@ private function renderAuthorLinks(array $authors, $handles) { 'class' => 'diffusion-author-link', 'style' => $style, 'href' => $href, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $handles[$phid]->getName(), + 'align' => 'E', + ), )); } diff --git a/webroot/rsrc/css/application/diffusion/diffusion-source.css b/webroot/rsrc/css/application/diffusion/diffusion-source.css index 06e7d74477..a2c67cf16d 100644 --- a/webroot/rsrc/css/application/diffusion/diffusion-source.css +++ b/webroot/rsrc/css/application/diffusion/diffusion-source.css @@ -64,14 +64,14 @@ color: {$darkbluetext}; } -.diffusion-rev-link a, -.diffusion-rev-link span { +.diffusion-rev-link a { margin: 0 8px 0 0; display: inline-block; } .diffusion-rev-link span { - margin-right: -4px; + display: inline-block; + margin-right: 4px; margin-left: -4px; color: {$lightgreytext}; } @@ -94,7 +94,7 @@ .diffusion-rev-link .diffusion-author-link { display: inline-block; padding: 0; - margin: 2px 4px -4px 4px; + margin: 2px 6px -4px 8px; width: 16px; height: 16px; background-size: 100% 100%; From 2a7cdcf740ac4948b25a88fda3db9fb9d3f7337e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 31 Oct 2017 13:52:23 -0700 Subject: [PATCH 263/865] Fix an issue where the repository symbol index would incorrectly activate inside inline comments Summary: See PHI185. When looking at a revision, you can Command-Click (Mac) symbols to jump to their definitions (provided the symbol index has been built). Currently, the code works on any node inside the changeset list, so it activates when clicking links inside inline comments and opening them in a new window. To avoid this, don't activate if we're inside an inline comment. This technically prevents you from doing a symbol lookup on a symbol inside a codeblock inside an inline, but that seems fine/reasonable. Test Plan: Wrote `Dxxx` in an inline, command-clicked it. Before: got a symbol lookup. After: just a new tab with the revision. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18753 --- resources/celerity/map.php | 18 +++++++++--------- .../repository/repository-crossreference.js | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c9e1a5b351..30c04ffc92 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', - 'differential.pkg.js' => 'b71b8c5d', + 'differential.pkg.js' => 'ae6460e0', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -444,7 +444,7 @@ 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f', - 'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43', + 'rsrc/js/application/repository/repository-crossreference.js' => '7fe9bc12', 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', @@ -690,7 +690,7 @@ 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-reorder-profile-menu-items' => 'e2e0a072', - 'javelin-behavior-repository-crossreference' => 'e5339c43', + 'javelin-behavior-repository-crossreference' => '7fe9bc12', 'javelin-behavior-scrollbar' => '834a1173', 'javelin-behavior-search-reorder-queries' => 'e9581f08', 'javelin-behavior-select-content' => 'bf5374ef', @@ -1546,6 +1546,12 @@ '7f243deb' => array( 'javelin-install', ), + '7fe9bc12' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-uri', + ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -2073,12 +2079,6 @@ 'javelin-behavior', 'javelin-dom', ), - 'e5339c43' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-uri', - ), 'e5822781' => array( 'javelin-behavior', 'javelin-dom', diff --git a/webroot/rsrc/js/application/repository/repository-crossreference.js b/webroot/rsrc/js/application/repository/repository-crossreference.js index bfb849c759..138ce4bf3e 100644 --- a/webroot/rsrc/js/application/repository/repository-crossreference.js +++ b/webroot/rsrc/js/application/repository/repository-crossreference.js @@ -46,8 +46,19 @@ JX.behavior('repository-crossreference', function(config, statics) { if (!isSignalkey(e)) { return; } + + var target = e.getTarget(); + + try { + // If we're in an inline comment, don't link symbols. + if (JX.DOM.findAbove(target, 'div', 'differential-inline-comment')) { + return; + } + } catch (ex) { + // Continue if we're not inside an inline comment. + } + if (e.getType() === 'mouseover') { - var target = e.getTarget(); while (target !== document.body) { if (JX.DOM.isNode(target, 'span') && (target.className in class_map)) { @@ -58,7 +69,7 @@ JX.behavior('repository-crossreference', function(config, statics) { target = target.parentNode; } } else if (e.getType() === 'click') { - openSearch(e.getTarget(), lang); + openSearch(target, lang); } }); } From 6d36eb911309d4741ab26c7f7212b642826752b1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 1 Nov 2017 10:42:27 -0700 Subject: [PATCH 264/865] Denormalize Diff PHIDs onto Revisions Summary: Ref T12539. See PHI190. Currently, each Diff has a `revisionID`, but Revisions do not point at the current active diff. To find the active diff for a given revision, we need to issue a separate query. Furthermore, this query is inefficient for bulk loads: if we have a lot of revisions, we end up querying for all diff IDs for all those revisions first, then selecting the largest ones and querying again to get the actual diff objects. This strategy could likely be optimized but the query is a mess in any case. In several cases, it's useful to have the active diff PHID without needing to do a second query -- sometimes for convenience, and sometimes for performance. T12539 is an example of such a case: it would be nice to refine the bucketing logic (which only depends on active diff PHIDs), but it feels bad to make the page heavier to do it. For now, this is unused. I'll start using it to fix the bucketing issue, and then we can expand it gradually to address other performance/convenience issues. Test Plan: - Ran migrations, inspected database, saw sensible values. - Created a new revision, saw a sensible database value. - Updated an existing revision, saw database update properly. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12539 Differential Revision: https://secure.phabricator.com/D18756 --- .../autopatches/20171101.diff.01.active.sql | 2 ++ .../autopatches/20171101.diff.02.populate.php | 24 +++++++++++++++++++ .../editor/DifferentialTransactionEditor.php | 3 +-- .../storage/DifferentialRevision.php | 2 ++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 resources/sql/autopatches/20171101.diff.01.active.sql create mode 100644 resources/sql/autopatches/20171101.diff.02.populate.php diff --git a/resources/sql/autopatches/20171101.diff.01.active.sql b/resources/sql/autopatches/20171101.diff.01.active.sql new file mode 100644 index 0000000000..aee8c5aa13 --- /dev/null +++ b/resources/sql/autopatches/20171101.diff.01.active.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_differential.differential_revision + ADD activeDiffPHID VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20171101.diff.02.populate.php b/resources/sql/autopatches/20171101.diff.02.populate.php new file mode 100644 index 0000000000..b41b0aa51d --- /dev/null +++ b/resources/sql/autopatches/20171101.diff.02.populate.php @@ -0,0 +1,24 @@ +establishConnection('w'); +$diff_table = new DifferentialDiff(); + +foreach (new LiskMigrationIterator($table) as $revision) { + $revision_id = $revision->getID(); + + $diff_row = queryfx_one( + $conn, + 'SELECT phid FROM %T WHERE revisionID = %d ORDER BY id DESC LIMIT 1', + $diff_table->getTableName(), + $revision_id); + + if ($diff_row) { + queryfx( + $conn, + 'UPDATE %T SET activeDiffPHID = %s WHERE id = %d', + $table->getTableName(), + $diff_row['phid'], + $revision_id); + } +} diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 79a582a43f..3e6cca1edb 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -138,8 +138,7 @@ protected function applyCustomInternalTransaction( $object->setRepositoryPHID($diff->getRepositoryPHID()); } $object->attachActiveDiff($diff); - - // TODO: Update the `diffPHID` once we add that. + $object->setActiveDiffPHID($diff->getPHID()); return; } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 467e503f6f..61f934f13b 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -35,6 +35,8 @@ final class DifferentialRevision extends DifferentialDAO protected $mailKey; protected $branchName; protected $repositoryPHID; + protected $activeDiffPHID; + protected $viewPolicy = PhabricatorPolicies::POLICY_USER; protected $editPolicy = PhabricatorPolicies::POLICY_USER; protected $properties = array(); From 6ecdadb76a955854875fa25515fba266bb34cfa9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 1 Nov 2017 10:54:23 -0700 Subject: [PATCH 265/865] After "Request Review", move revisions with voided "Accepts" into "Ready to Review", not "Waiting on Other Reviewers" Summary: Depends on D18756. Fixes T12539. See PHI190. Currently, when this occurs: - Alice accepts. - Bailey requests review. - Alice views her dashboard. ...the revision appears in "Waiting on Other Reviewers" (regardless of whether other reviewers actually exist or not). Instead, ignore these voided/non-current accepts and let the revisions appear in "Ready to Review", which is more natural. Test Plan: Went through the steps above. On `master`, saw revision in "Waiting on Other Reviewers". After patch, saw it in "Ready to Review". Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12539 Differential Revision: https://secure.phabricator.com/D18757 --- ...fferentialRevisionRequiredActionResultBucket.php | 10 +++++++++- .../query/DifferentialRevisionResultBucket.php | 13 ++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index f3971f8571..5f5f4008db 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -123,6 +123,14 @@ private function filterShouldReview(array $phids) { $reviewing = array( DifferentialReviewerStatus::STATUS_ADDED, DifferentialReviewerStatus::STATUS_COMMENTED, + + // If an author has used "Request Review" to put an accepted revision + // back into the "Needs Review" state, include "Accepted" reviewers + // whose reviews have been voided in the "Should Review" bucket. + + // If we don't do this, they end up in "Waiting on Other Reviewers", + // even if there are no other reviewers. + DifferentialReviewerStatus::STATUS_ACCEPTED, ); $reviewing = array_fuse($reviewing); @@ -130,7 +138,7 @@ private function filterShouldReview(array $phids) { $results = array(); foreach ($objects as $key => $object) { - if (!$this->hasReviewersWithStatus($object, $phids, $reviewing)) { + if (!$this->hasReviewersWithStatus($object, $phids, $reviewing, false)) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionResultBucket.php b/src/applications/differential/query/DifferentialRevisionResultBucket.php index 54705649eb..a0769ac349 100644 --- a/src/applications/differential/query/DifferentialRevisionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionResultBucket.php @@ -53,7 +53,8 @@ protected function getRevisionsNotAuthored(array $objects, array $phids) { protected function hasReviewersWithStatus( DifferentialRevision $revision, array $phids, - array $statuses) { + array $statuses, + $current = null) { foreach ($revision->getReviewers() as $reviewer) { $reviewer_phid = $reviewer->getReviewerPHID(); @@ -66,6 +67,16 @@ protected function hasReviewersWithStatus( continue; } + if ($current !== null) { + if ($status == DifferentialReviewerStatus::STATUS_ACCEPTED) { + $diff_phid = $revision->getActiveDiffPHID(); + $is_current = $reviewer->isAccepted($diff_phid); + if ($is_current !== $current) { + continue; + } + } + } + return true; } From 03d059dd26e27077de6c58847926cfe9d2e6590b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 1 Nov 2017 11:08:19 -0700 Subject: [PATCH 266/865] Don't include resigned reviewers in the Differential "To" list Summary: Ref T12689. See PHI178. This isn't a complete solution (you may still get mailed via packages/projects) but should fix the obvious issue, where "Resigned" reviewers are incorrectly always sent mail directly. Test Plan: Had Alice resign, interacted as Bailey, no mail to Alice. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12689 Differential Revision: https://secure.phabricator.com/D18758 --- .../differential/editor/DifferentialTransactionEditor.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 3e6cca1edb..26c440e866 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -630,6 +630,10 @@ protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); $phids[] = $object->getAuthorPHID(); foreach ($object->getReviewers() as $reviewer) { + if ($reviewer->isResigned()) { + continue; + } + $phids[] = $reviewer->getReviewerPHID(); } return $phids; From cb98b60033260998a51821f71add07285fa0d3ef Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 10:25:37 -0800 Subject: [PATCH 267/865] Fill in some straightforward Maniphest transactions for `transaction.search` Summary: See PHI197. Populates "status" transactions and a few other obvious types where there's no security/performance/payload/formatting issue I can come up with. The names here are the same as the names for editing with `maniphest.edit`. Test Plan: Used `transaction.search` to retrieve transactions of all new types. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18761 --- .../DifferentialRevisionStatusTransaction.php | 6 +++--- .../xaction/ManiphestTaskDescriptionTransaction.php | 10 ++++++++++ .../xaction/ManiphestTaskOwnerTransaction.php | 10 ++++++++++ .../xaction/ManiphestTaskPointsTransaction.php | 12 ++++++++++++ .../xaction/ManiphestTaskStatusTransaction.php | 11 +++++++++++ .../xaction/ManiphestTaskTitleTransaction.php | 11 +++++++++++ 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php index 615ce38bcf..9c8b3767df 100644 --- a/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionStatusTransaction.php @@ -74,10 +74,10 @@ public function getTransactionTypeForConduit($xaction) { return 'status'; } - public function getFieldValuesForConduit($object, $data) { + public function getFieldValuesForConduit($xaction, $data) { return array( - 'old' => $object->getOldValue(), - 'new' => $object->getNewValue(), + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), ); } diff --git a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php index 009327ed9b..fec1a87604 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php @@ -57,5 +57,15 @@ public function newRemarkupChanges() { return $changes; } + public function getTransactionTypeForConduit($xaction) { + return 'description'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } } diff --git a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php index d510fe8fbc..caaf84f542 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskOwnerTransaction.php @@ -154,5 +154,15 @@ public function getColor() { } + public function getTransactionTypeForConduit($xaction) { + return 'owner'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } } diff --git a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php index 8953664f27..5a69199874 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPointsTransaction.php @@ -107,4 +107,16 @@ private function getValueForPoints($value) { return $value; } + public function getTransactionTypeForConduit($xaction) { + return 'points'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + + } diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php index 5e1cd44611..a3780e81b9 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php @@ -229,4 +229,15 @@ public function getColor() { } + public function getTransactionTypeForConduit($xaction) { + return 'status'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + } diff --git a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php index 506817b0fc..e4ec2a132f 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskTitleTransaction.php @@ -72,4 +72,15 @@ public function validateTransactions($object, array $xactions) { return $errors; } + public function getTransactionTypeForConduit($xaction) { + return 'title'; + } + + public function getFieldValuesForConduit($xaction, $data) { + return array( + 'old' => $xaction->getOldValue(), + 'new' => $xaction->getNewValue(), + ); + } + } From cc865e549bcddb68544c6ae7415c4e19a7db095d Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 10:38:43 -0800 Subject: [PATCH 268/865] Provide revision parent/child edges in `edge.search`, and more information in `differential.revision.search` Summary: See PHI195. This bulks out these API methods since all the requests are pretty straightforward. Test Plan: Ran `edge.search` and `differential.revision.search`. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18762 --- ...entialRevisionDependedOnByRevisionEdgeType.php | 14 ++++++++++++++ ...ferentialRevisionDependsOnRevisionEdgeType.php | 13 +++++++++++++ .../differential/storage/DifferentialRevision.php | 15 +++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php b/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php index 02a24effcf..a6940b536e 100644 --- a/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php +++ b/src/applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php @@ -13,6 +13,20 @@ public function shouldWriteInverseTransactions() { return true; } + public function getConduitKey() { + return 'revision.child'; + } + + public function getConduitName() { + return pht('Revision Has Child'); + } + + public function getConduitDescription() { + return pht( + 'The source revision makes changes required by the destination '. + 'revision.'); + } + public function getTransactionAddString( $actor, $add_count, diff --git a/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php b/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php index 4613ad8a34..e826f554c6 100644 --- a/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php +++ b/src/applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php @@ -17,6 +17,19 @@ public function shouldPreventCycles() { return true; } + public function getConduitKey() { + return 'revision.parent'; + } + + public function getConduitName() { + return pht('Revision Has Parent'); + } + + public function getConduitDescription() { + return pht( + 'The source revision depends on changes in the destination revision.'); + } + public function getTransactionAddString( $actor, $add_count, diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 61f934f13b..bf4bec0abc 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -986,6 +986,18 @@ public function getFieldSpecificationsForConduit() { ->setKey('status') ->setType('map') ->setDescription(pht('Information about revision status.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('repositoryPHID') + ->setType('phid?') + ->setDescription(pht('Revision repository PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('diffPHID') + ->setType('phid') + ->setDescription(pht('Active diff PHID.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('summary') + ->setType('string') + ->setDescription(pht('Revision summary.')), ); } @@ -1002,6 +1014,9 @@ public function getFieldValuesForConduit() { 'title' => $this->getTitle(), 'authorPHID' => $this->getAuthorPHID(), 'status' => $status_info, + 'repositoryPHID' => $this->getRepositoryPHID(), + 'diffPHID' => $this->getActiveDiffPHID(), + 'summary' => $this->getSummary(), ); } From 587faa6f677c8d7e914051e5307ade00e2bf43b0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 10:53:14 -0800 Subject: [PATCH 269/865] Remove some defunct old-style transaction policy checks Summary: Ref PHI193. This method of enforcing policy checks is now (mostly) obsolete, and they're generally checked at the Controller/API level instead. Notably, this method does not call `adjustObjectForPolicyChecks(...)` properly, so it can not handle special cases like "creating a project and taking its newly created members into account" for object policies like "Project Members". Just remove these checks, which are redundant with checks elsewhere. Test Plan: - Set Project application default edit policy to "Administrators and Project Members". - Tried to create a project as a non-administrator, adding myself. - Before patch: policy fatal on a VOID object (the project with no PHID generated yet). - After patch: object created properly. Got a sensible policy error if I didn't include myself as a member. - Also verified that other edit rules are still enforced/respected (I can't edit stuff I shouldn't be able to edit). - There's at least a bit of unit test coverage of this, too, which I updated to work via API (which hits the new broad capability checks) instead of via low-level transactions (which enforce only a subset of policy operations now). Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18763 --- .../PhabricatorProjectCoreTestCase.php | 20 ++++++++++++------- .../PhabricatorProjectTransactionEditor.php | 10 ---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index d9356357b3..f34f224521 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -146,8 +146,7 @@ public function testEditProject() { $user = $this->createUser(); $user->save(); - $user2 = $this->createUser(); - $user2->save(); + $user->setAllowInlineCacheGeneration(true); $proj = $this->createProject($user); @@ -1289,12 +1288,19 @@ private function attemptProjectEdit( $new_name = $proj->getName().' '.mt_rand(); - $xaction = new PhabricatorProjectTransaction(); - $xaction->setTransactionType( - PhabricatorProjectNameTransaction::TRANSACTIONTYPE); - $xaction->setNewValue($new_name); + $params = array( + 'objectIdentifier' => $proj->getID(), + 'transactions' => array( + array( + 'type' => 'name', + 'value' => $new_name, + ), + ), + ); - $this->applyTransactions($proj, $user, array($xaction)); + id(new ConduitCall('project.edit', $params)) + ->setUser($user) + ->execute(); return true; } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index adf4a3138b..2764ce6322 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -120,16 +120,6 @@ protected function requireCapabilities( PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectNameTransaction::TRANSACTIONTYPE: - case PhabricatorProjectStatusTransaction::TRANSACTIONTYPE: - case PhabricatorProjectImageTransaction::TRANSACTIONTYPE: - case PhabricatorProjectIconTransaction::TRANSACTIONTYPE: - case PhabricatorProjectColorTransaction::TRANSACTIONTYPE: - PhabricatorPolicyFilter::requireCapability( - $this->requireActor(), - $object, - PhabricatorPolicyCapability::CAN_EDIT); - return; case PhabricatorProjectLockTransaction::TRANSACTIONTYPE: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), From 12e6106a5907c5160dfca8b997e7b6796d707260 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 6 Nov 2017 11:51:59 -0800 Subject: [PATCH 270/865] Refine bucketing and display rules for voided "Accepts" in Differential Summary: See PHI190. This clarifies the ruleset a bit: - If you accepted, then the author used "Request Review" explicitly, we now show "Accepted Earlier" instead of "Accepted" in the "Reviewers" list on the main revision page. This makes it sligthly more clear why the revision is back in your review queue without picking through the transaction log. - Instead of moving all non-current accepts into "Ready to Review", move only voided accepts into "Ready to Review". This stops us from pulling older accepts which haven't been voided (which could have been incorrectly pulled) and correctly pulls older, voided accepts from before an update (for example: accept, then request review, then update) and generally aligns better with intent/expectation. Test Plan: - Accepted, requested review. - Saw reviewer as "Accepted Earlier". - Saw review in "Ready to Review" bucket. - Accepted, updated (with sticky accept). - Saw reviewer as "Accepted Prior Diff". - Saw review as "Waiting on Authors". Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18764 --- .../DifferentialRevisionRequiredActionResultBucket.php | 2 +- .../query/DifferentialRevisionResultBucket.php | 9 ++++----- .../differential/view/DifferentialReviewersView.php | 10 ++++++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php index 5f5f4008db..711f70afb3 100644 --- a/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php @@ -138,7 +138,7 @@ private function filterShouldReview(array $phids) { $results = array(); foreach ($objects as $key => $object) { - if (!$this->hasReviewersWithStatus($object, $phids, $reviewing, false)) { + if (!$this->hasReviewersWithStatus($object, $phids, $reviewing, true)) { continue; } diff --git a/src/applications/differential/query/DifferentialRevisionResultBucket.php b/src/applications/differential/query/DifferentialRevisionResultBucket.php index a0769ac349..c5cc5c0e6c 100644 --- a/src/applications/differential/query/DifferentialRevisionResultBucket.php +++ b/src/applications/differential/query/DifferentialRevisionResultBucket.php @@ -54,7 +54,7 @@ protected function hasReviewersWithStatus( DifferentialRevision $revision, array $phids, array $statuses, - $current = null) { + $include_voided = null) { foreach ($revision->getReviewers() as $reviewer) { $reviewer_phid = $reviewer->getReviewerPHID(); @@ -67,11 +67,10 @@ protected function hasReviewersWithStatus( continue; } - if ($current !== null) { + if ($include_voided !== null) { if ($status == DifferentialReviewerStatus::STATUS_ACCEPTED) { - $diff_phid = $revision->getActiveDiffPHID(); - $is_current = $reviewer->isAccepted($diff_phid); - if ($is_current !== $current) { + $is_voided = (bool)$reviewer->getVoidedPHID(); + if ($is_voided !== $include_voided) { continue; } } diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index 33aad25289..f88669e539 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -47,6 +47,7 @@ public function render() { $action_phid = $reviewer->getLastActionDiffPHID(); $is_current_action = $this->isCurrent($action_phid); + $is_voided = (bool)$reviewer->getVoidedPHID(); $comment_phid = $reviewer->getLastCommentDiffPHID(); $is_current_comment = $this->isCurrent($comment_phid); @@ -86,7 +87,7 @@ public function render() { break; case DifferentialReviewerStatus::STATUS_ACCEPTED: - if ($is_current_action) { + if ($is_current_action && !$is_voided) { $icon = PHUIStatusItemView::ICON_ACCEPT; $color = 'green'; if ($authority_name !== null) { @@ -97,7 +98,12 @@ public function render() { } else { $icon = 'fa-check-circle-o'; $color = 'bluegrey'; - if ($authority_name !== null) { + + if (!$is_current_action && $is_voided) { + // The reviewer accepted the revision, but later the author + // used "Request Review" to request an updated review. + $label = pht('Accepted Earlier'); + } else if ($authority_name !== null) { $label = pht('Accepted Prior Diff (by %s)', $authority_name); } else { $label = pht('Accepted Prior Diff'); From 759c757264c84b35abe603026a29191a2ddf3def Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 7 Nov 2017 05:12:11 -0800 Subject: [PATCH 271/865] Include "Draft" revisions in Differential legacy status queries Summary: See PHI199. Ref T2543. When you run a RevisionQuery with a legacy status constraint (via `differential.query`), we currently don't match "Draft" revisions. Use the actual complete map from `DifferentialRevisionStatus` instead of hard coding the status list so "Draft" is included. Test Plan: - Ran `differential.query` with `ids` and `status` for a draft revision. - Before patch: revision not returned in results. - After patch: revision returned in results. (Note that it returns as "Needs Review", for compatibility.) Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18765 --- .../constants/DifferentialLegacyQuery.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/applications/differential/constants/DifferentialLegacyQuery.php b/src/applications/differential/constants/DifferentialLegacyQuery.php index 26d2c4aee2..ff7944afe9 100644 --- a/src/applications/differential/constants/DifferentialLegacyQuery.php +++ b/src/applications/differential/constants/DifferentialLegacyQuery.php @@ -32,14 +32,7 @@ public static function getModernValues($status) { } private static function getMap() { - $all = array( - DifferentialRevisionStatus::NEEDS_REVIEW, - DifferentialRevisionStatus::NEEDS_REVISION, - DifferentialRevisionStatus::CHANGES_PLANNED, - DifferentialRevisionStatus::ACCEPTED, - DifferentialRevisionStatus::PUBLISHED, - DifferentialRevisionStatus::ABANDONED, - ); + $all = array_keys(DifferentialRevisionStatus::getAll()); $open = array(); $closed = array(); @@ -61,6 +54,9 @@ private static function getMap() { ), self::STATUS_NEEDS_REVIEW => array( DifferentialRevisionStatus::NEEDS_REVIEW, + + // For legacy callers, "Draft" is treated as "Needs Review". + DifferentialRevisionStatus::DRAFT, ), self::STATUS_NEEDS_REVISION => array( DifferentialRevisionStatus::NEEDS_REVISION, From a7921a4448093d00defa8bd18f35b8c8f8bf3314 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 9 Nov 2017 07:23:40 -0800 Subject: [PATCH 272/865] Filter and reject "--config" and "--debugger" flags to Mercurial in any position Summary: Ref T13012. These flags can be exploited by attackers to execute code remotely. See T13012 for discussion and context. Additionally, harden some Mercurial commands where possible (by using additional quoting or embedding arguments in other constructs) so they resist these flags and behave properly when passed arguments with these values. Test Plan: - Added unit tests. - Verified "--config" and "--debugger" commands are rejected. - Verified more commands now work properly even with branches and files named `--debugger`, although not all of them do. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13012 Differential Revision: https://secure.phabricator.com/D18769 --- src/__phutil_library_map__.php | 2 + .../DiffusionExistsQueryConduitAPIMethod.php | 2 +- .../DiffusionHistoryQueryConduitAPIMethod.php | 18 +++--- ...sionMergedCommitsQueryConduitAPIMethod.php | 2 +- .../DiffusionSearchQueryConduitAPIMethod.php | 2 +- ...ffusionMercurialFlagInjectionException.php | 3 + .../DiffusionMercurialCommandEngine.php | 21 +++++++ .../DiffusionCommandEngineTestCase.php | 56 +++++++++++++++++++ .../DiffusionMercurialRawDiffQuery.php | 4 +- 9 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7180a7ae04..424d72dc72 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -768,6 +768,7 @@ 'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php', 'DiffusionMercurialCommandEngine' => 'applications/diffusion/protocol/DiffusionMercurialCommandEngine.php', 'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php', + 'DiffusionMercurialFlagInjectionException' => 'applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php', 'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php', 'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php', 'DiffusionMercurialResponse' => 'applications/diffusion/response/DiffusionMercurialResponse.php', @@ -5814,6 +5815,7 @@ 'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery', 'DiffusionMercurialCommandEngine' => 'DiffusionCommandEngine', 'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery', + 'DiffusionMercurialFlagInjectionException' => 'Exception', 'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionMercurialRequest' => 'DiffusionRequest', 'DiffusionMercurialResponse' => 'AphrontResponse', diff --git a/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php index 4280230002..2d4a221171 100644 --- a/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php @@ -47,7 +47,7 @@ protected function getMercurialResult(ConduitAPIRequest $request) { $commit = $request->getValue('commit'); list($err, $stdout) = $repository->execLocalCommand( 'id --rev %s', - $commit); + hgsprintf('%s', $commit)); return !$err; } diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index 200be8567b..e36c0a124e 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -123,21 +123,23 @@ protected function getMercurialResult(ConduitAPIRequest $request) { // branches). if (strlen($path)) { - $path_arg = csprintf('-- %s', $path); - $branch_arg = ''; + $path_arg = csprintf('%s', $path); + $revset_arg = hgsprintf( + 'reverse(ancestors(%s))', + $commit_hash); } else { $path_arg = ''; - // NOTE: --branch used to be called --only-branch; use -b for - // compatibility. - $branch_arg = csprintf('-b %s', $drequest->getBranch()); + $revset_arg = hgsprintf( + 'branch(%s) and reverse(ancestors(%s))', + $drequest->getBranch(), + $commit_hash); } list($stdout) = $repository->execxLocalCommand( - 'log --debug --template %s --limit %d %C --rev %s %C', + 'log --debug --template %s --limit %d --rev %s -- %C', '{node};{parents}\\n', ($offset + $limit), // No '--skip' in Mercurial. - $branch_arg, - hgsprintf('reverse(ancestors(%s))', $commit_hash), + $revset_arg, $path_arg); $stdout = DiffusionMercurialCommandEngine::filterMercurialDebugOutput( diff --git a/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php index 9d2d6caadc..79587a2e5e 100644 --- a/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php @@ -77,7 +77,7 @@ protected function getMercurialResult(ConduitAPIRequest $request) { list($parents) = $repository->execxLocalCommand( 'parents --template=%s --rev %s', '{node}\\n', - $commit); + hgsprintf('%s', $commit)); $parents = explode("\n", trim($parents)); if (count($parents) < 2) { diff --git a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php index 389f931c0f..af973f102d 100644 --- a/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php @@ -97,7 +97,7 @@ protected function getMercurialResult(ConduitAPIRequest $request) { $results = array(); $future = $repository->getLocalCommandFuture( - 'grep --rev %s --print0 --line-number %s %s', + 'grep --rev %s --print0 --line-number -- %s %s', hgsprintf('ancestors(%s)', $drequest->getStableCommit()), $grep, $path); diff --git a/src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php b/src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php new file mode 100644 index 0000000000..e60e735816 --- /dev/null +++ b/src/applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php @@ -0,0 +1,3 @@ +splitArguments($test_command); + + foreach ($test_args as $test_arg) { + if (preg_match('/^--(config|debugger)/i', $test_arg)) { + throw new DiffusionMercurialFlagInjectionException( + pht( + 'Mercurial command appears to contain unsafe injected "--config" '. + 'or "--debugger": %s', + $test_command)); + } + } + // NOTE: Here, and in Git and Subversion, we override the SSH command even // if the repository does not use an SSH remote, since our SSH wrapper // defuses an attack against older versions of Mercurial, Git and diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php index 1df3ea3522..50b7d4a5e2 100644 --- a/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php +++ b/src/applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php @@ -123,6 +123,62 @@ public function testCommandEngine() { 'argv' => 'xyz', 'protocol' => 'https', )); + + // Test that filtering defenses for "--config" and "--debugger" flag + // injections in Mercurial are functional. See T13012. + + $caught = null; + try { + $this->assertCommandEngineFormat( + '', + array(), + array( + 'vcs' => $type_hg, + 'argv' => '--debugger', + )); + } catch (DiffusionMercurialFlagInjectionException $ex) { + $caught = $ex; + } + + $this->assertTrue( + ($caught instanceof DiffusionMercurialFlagInjectionException), + pht('Expected "--debugger" injection in Mercurial to throw.')); + + + $caught = null; + try { + $this->assertCommandEngineFormat( + '', + array(), + array( + 'vcs' => $type_hg, + 'argv' => '--config=x', + )); + } catch (DiffusionMercurialFlagInjectionException $ex) { + $caught = $ex; + } + + $this->assertTrue( + ($caught instanceof DiffusionMercurialFlagInjectionException), + pht('Expected "--config" injection in Mercurial to throw.')); + + $caught = null; + try { + $this->assertCommandEngineFormat( + '', + array(), + array( + 'vcs' => $type_hg, + 'argv' => (string)csprintf('%s', '--config=x'), + )); + } catch (DiffusionMercurialFlagInjectionException $ex) { + $caught = $ex; + } + + $this->assertTrue( + ($caught instanceof DiffusionMercurialFlagInjectionException), + pht('Expected quoted "--config" injection in Mercurial to throw.')); + } private function assertCommandEngineFormat( diff --git a/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php b/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php index 39eed01226..9edcbb6380 100644 --- a/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php +++ b/src/applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php @@ -16,14 +16,14 @@ protected function newQueryFuture() { // If `$commit` has no parents (usually because it's the first commit // in the repository), we want to diff against `null`. This revset will // do that for us automatically. - $against = '('.$commit.'^ or null)'; + $against = hgsprintf('(%s^ or null)', $commit); } $future = $repository->getLocalCommandFuture( 'diff -U %d --git --rev %s --rev %s -- %s', $this->getLinesOfContext(), $against, - $commit, + hgsprintf('%s', $commit), $path); return $future; From 314e7266c3e5b071cb6e916d28c21cb143d6e013 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 14 Nov 2017 12:06:24 -0800 Subject: [PATCH 273/865] Restore "Summary" and "Test Plan" to initial mail for non-draft configurations Summary: See PHI210. Ref T2543. Currently, we don't set this flag if you have prototypes off and don't get any of the new draft stuff, so the mail drops some of the details it is supposed to have. Test Plan: Disabled prototypes, created a revision, saw summary / test plan in the initial mail. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18771 --- .../differential/editor/DifferentialTransactionEditor.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 26c440e866..7cea29359f 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1612,6 +1612,13 @@ protected function didApplyTransactions($object, array $xactions) { $xactions[] = $xaction; } + } else { + // If this revision is being created into some state other than "Draft", + // this is the first broadcast and should include sections like "SUMMARY" + // and "TEST PLAN". + if ($this->getIsNewObject()) { + $this->firstBroadcast = true; + } } return $xactions; From c098aec66931720b7de19b8ef628bb6d735c3fab Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 14 Nov 2017 12:06:24 -0800 Subject: [PATCH 274/865] (stable) Restore "Summary" and "Test Plan" to initial mail for non-draft configurations Summary: See PHI210. Ref T2543. Currently, we don't set this flag if you have prototypes off and don't get any of the new draft stuff, so the mail drops some of the details it is supposed to have. Test Plan: Disabled prototypes, created a revision, saw summary / test plan in the initial mail. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18771 --- .../differential/editor/DifferentialTransactionEditor.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 26c440e866..7cea29359f 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1612,6 +1612,13 @@ protected function didApplyTransactions($object, array $xactions) { $xactions[] = $xaction; } + } else { + // If this revision is being created into some state other than "Draft", + // this is the first broadcast and should include sections like "SUMMARY" + // and "TEST PLAN". + if ($this->getIsNewObject()) { + $this->firstBroadcast = true; + } } return $xactions; From 95a5b41b0ba2751bccdec0725b84561ba7000d09 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 14 Nov 2017 12:17:31 -0800 Subject: [PATCH 275/865] Allow "arc diff --draft" to create revisions as drafts more forcefully Summary: Depends on D18771. See PHI206. Currently, `arc diff --draft` only holds revisions in draft mode: it doesn't put them into draft mode if the install isn't configured to use draft mode. Instead, make it a bit more forceful so that `arc diff --draft` can create into draft mode explicitly even if protoypes are off. This aligns with expection a little more clearly. Test Plan: Ran `arc diff --draft` with prototypes off, got a revision held in draft mode. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18772 --- .../xaction/DifferentialRevisionHoldDraftTransaction.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php index 5bc257ab62..2562f18209 100644 --- a/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionHoldDraftTransaction.php @@ -16,6 +16,15 @@ public function generateNewValue($object, $value) { public function applyInternalEffects($object, $value) { $object->setHoldAsDraft($value); + + // If draft isn't the default state but we're creating a new revision + // and holding it as a draft, put it in draft mode. See PHI206. + // TODO: This can probably be removed once Draft is the universal default. + if ($this->isNewObject()) { + if ($object->isNeedsReview()) { + $object->setModernRevisionStatus(DifferentialRevisionStatus::DRAFT); + } + } } public function getTitle() { From a1f12b4ac7af59216b4a0feb1321542486b8be64 Mon Sep 17 00:00:00 2001 From: Mukunda Modell Date: Tue, 14 Nov 2017 17:03:16 -0600 Subject: [PATCH 276/865] Specify a null behavior for the callsign sort column. Summary: Fixes paging on the Diffusion Repository List. PhabricatorRepositoryQuery needs to specify a behavior for `null` on the OderableColumns definition for the `callsign` column. See https://phabricator.wikimedia.org/T180457 Test Plan: 1. On an instance with more than 100 repositories * some of which are missing a callsign 2. Attempt to sort by callsign. 3. See the sorted results Previously: 3. Exception: "Column "0" has null value, but does not specify a null behavior." Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18773 --- src/applications/repository/query/PhabricatorRepositoryQuery.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index ffa9fa1b59..035693ff75 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -425,6 +425,7 @@ public function getOrderableColumns() { 'type' => 'string', 'unique' => true, 'reverse' => true, + 'null' => 'tail', ), 'name' => array( 'table' => 'r', From 3700bcb638def188008839683feecd7b68729173 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 15 Nov 2017 09:21:52 -0800 Subject: [PATCH 277/865] Warn and prevent 1-up/2-up switch in Differential if the user is editing an inline Summary: See PHI180. Currently, if you begin creating or editing an inline and then swap display modes (for example, with "View Unified"), your edit is lost. Persisting the editor state is complicated and this is very rare, so just prevent the action and warn the user instead. Also make the warning persist for a little longer since a few of the messages, including this one, take a couple seconds to read now. Test Plan: - Edited a comment, tried to swap display modes, got a warning. - Swapped display modes normally with no comment being edited. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18774 --- resources/celerity/map.php | 14 +++++++------- .../view/DifferentialChangesetListView.php | 3 +++ .../js/application/diff/DiffChangesetList.js | 19 ++++++++++++++++++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 30c04ffc92..78ae8bd461 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', - 'differential.pkg.js' => 'ae6460e0', + 'differential.pkg.js' => '500a75c5', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -396,7 +396,7 @@ 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => '99abf4cd', - 'rsrc/js/application/diff/DiffChangesetList.js' => '8f1cd52c', + 'rsrc/js/application/diff/DiffChangesetList.js' => '3b77efdd', 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -775,7 +775,7 @@ 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => '99abf4cd', - 'phabricator-diff-changeset-list' => '8f1cd52c', + 'phabricator-diff-changeset-list' => '3b77efdd', 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -1137,6 +1137,10 @@ 'javelin-dom', 'javelin-magical-init', ), + '3b77efdd' => array( + 'javelin-install', + 'phuix-button-view', + ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', @@ -1610,10 +1614,6 @@ '8e1baf68' => array( 'phui-button-css', ), - '8f1cd52c' => array( - 'javelin-install', - 'phuix-button-view', - ), '8f29b364' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 307d424b0e..59f73b5465 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -301,6 +301,9 @@ public function render() { 'Hide or show all inline comments.' => pht('Hide or show all inline comments.'), + + 'Finish editing inline comments before changing display modes.' => + pht('Finish editing inline comments before changing display modes.'), ), )); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index 3f47f1ed35..ec0270ac12 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -465,7 +465,7 @@ JX.install('DiffChangesetList', { new JX.Notification() .setContent(message) .alterClassName('jx-notification-alert', true) - .setDuration(1000) + .setDuration(3000) .show(); }, @@ -691,6 +691,7 @@ JX.install('DiffChangesetList', { 'div', 'differential-changeset'); + var changeset_list = this; var changeset = this.getChangesetForNode(node); var menu = new JX.PHUIXDropdownMenu(button); @@ -738,6 +739,22 @@ JX.install('DiffChangesetList', { var up_item = new JX.PHUIXActionView() .setHandler(function(e) { if (changeset.isLoaded()) { + + // Don't let the user swap display modes if a comment is being + // edited, since they might lose their work. See PHI180. + var inlines = changeset.getInlines(); + for (var ii = 0; ii < inlines.length; ii++) { + if (inlines[ii].isEditing()) { + changeset_list._warnUser( + pht( + 'Finish editing inline comments before changing display ' + + 'modes.')); + e.prevent(); + menu.close(); + return; + } + } + var renderer = changeset.getRenderer(); if (renderer == '1up') { renderer = '2up'; From bea45e90d3240ac575f70d2ab46aef3d9c9b8634 Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Wed, 15 Nov 2017 11:28:47 -0800 Subject: [PATCH 278/865] Add yaml files to differential.whitespace-matters Summary: Whitespace has semantic meaning for yaml files, so we shouldn't suppress whitespace-only lines of diff by default. Test Plan: Edited local config to include yaml files, saw expected whitespace changes. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18775 --- .../differential/config/PhabricatorDifferentialConfigOptions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index 7ff886664f..0d00207b43 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -86,6 +86,7 @@ public function getOptions() { array( '/\.py$/', '/\.l?hs$/', + '/\.ya?ml$/', )) ->setDescription( pht( From d2cff6a2cf01396f6337edfadd1f7df7cce1277d Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Thu, 16 Nov 2017 00:43:35 -0800 Subject: [PATCH 279/865] Transcode the HTML part of incoming email into UTF-8 as well Summary: D1093 did this for just the text/plain part of incoming email. Most text/html parts choose to either use entity encoding //or// are already UTF-8, thus obviating the need to transcode the HTML part. However, this is not always the case, and leads to dropped messages, by way of: ``` EXCEPTION: (Exception) Failed to JSON encode value (#5: Malformed UTF-8 characters, possibly incorrectly encoded): Dictionary value at key "html" is not valid UTF8, and cannot be JSON encoded: [snip HTML part of message content]``` Generalize the charset transcoding to not apply to just the text/plain part, but both text/plain and text/html parts. Test Plan: Fed in a Windows-1252-encoded text/html part with 0x92 bytes in it; verified that $content only contained valid UTF-8 after this change. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D18776 --- scripts/mail/mail_handler.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php index 2ff23adb0f..b76b3910df 100755 --- a/scripts/mail/mail_handler.php +++ b/scripts/mail/mail_handler.php @@ -35,16 +35,19 @@ $parser = new MimeMailParser(); $parser->setText(file_get_contents('php://stdin')); -$text_body = $parser->getMessageBody('text'); - -$text_body_headers = $parser->getMessageBodyHeaders('text'); -$content_type = idx($text_body_headers, 'content-type'); -if ( - !phutil_is_utf8($text_body) && - (preg_match('/charset="(.*?)"/', $content_type, $matches) || - preg_match('/charset=(\S+)/', $content_type, $matches)) -) { - $text_body = phutil_utf8_convert($text_body, 'UTF-8', $matches[1]); +$content = array(); +foreach (array('text', 'html') as $part) { + $part_body = $parser->getMessageBody($part); + $part_headers = $parser->getMessageBodyHeaders($part); + $content_type = idx($part_headers, 'content-type'); + if ( + !phutil_is_utf8($part_body) && + (preg_match('/charset="(.*?)"/', $content_type, $matches) || + preg_match('/charset=(\S+)/', $content_type, $matches)) + ) { + $part_body = phutil_utf8_convert($part_body, 'UTF-8', $matches[1]); + } + $content[$part] = $part_body; } $headers = $parser->getHeaders(); @@ -57,10 +60,7 @@ $received = new PhabricatorMetaMTAReceivedMail(); $received->setHeaders($headers); -$received->setBodies(array( - 'text' => $text_body, - 'html' => $parser->getMessageBody('html'), -)); +$received->setBodies($content); $attachments = array(); foreach ($parser->getAttachments() as $attachment) { From d321cc810aab52be00d75c9dcfa8b9cabd34828e Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 06:09:41 -0800 Subject: [PATCH 280/865] Freeze "maniphest.gettasktransactions" and make status/priority transactions more consistent Summary: Ref T13020. See PHI221. Freeze legacy method `maniphest.gettasktransactions` in favor of modern method `transaction.search`. Remove legacy "null on create" behavior from Maniphest status and priority transactions. This behavior is obsolete with EditEngine, and leads to inconsistent transaction sets in the transaction record. The desired behavior is that transactions which don't do anything (e.g., default value was not changed) don't appear in the transaction log. Test Plan: - Viewed API UI and saw `maniphest.gettasktransactions` marked as "Frozen". - Created a new task via web UI (without changing status/priority), queried transactions with `maniphest.gettasktransacitons`/`transaction.search`, no longer saw "null on create" no-op transactions in record. - Web UI is unchanged, since these transactions were hidden before and now do not exist. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13020 Differential Revision: https://secure.phabricator.com/D18777 --- .../ManiphestGetTaskTransactionsConduitAPIMethod.php | 10 ++++++++++ .../xaction/ManiphestTaskPriorityTransaction.php | 9 +++------ .../xaction/ManiphestTaskStatusTransaction.php | 3 --- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php index 8b0d0496cf..357d118bc4 100644 --- a/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php +++ b/src/applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php @@ -21,6 +21,16 @@ protected function defineReturnType() { return 'nonempty list>'; } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "transaction.search" instead.'); + } + protected function execute(ConduitAPIRequest $request) { $results = array(); $task_ids = $request->getValue('ids'); diff --git a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php index 2ed7c4e15b..83f38fc659 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskPriorityTransaction.php @@ -6,20 +6,17 @@ final class ManiphestTaskPriorityTransaction const TRANSACTIONTYPE = 'priority'; public function generateOldValue($object) { - if ($this->isNewObject()) { - return null; - } - return $object->getPriority(); + return (string)$object->getPriority(); } public function generateNewValue($object, $value) { // `$value` is supposed to be a keyword, but if the priority // assigned to a task has been removed from the config, // no such keyword will be available. Other edits to the task - // should still be allowed, even if the priority is no longer + // should still be allowed, even if the priority is no longer // valid, so treat this as a no-op. if ($value === ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD) { - return $object->getPriority(); + return (string)$object->getPriority(); } return (string)ManiphestTaskPriority::getTaskPriorityFromKeyword($value); diff --git a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php index a3780e81b9..dd51a63799 100644 --- a/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php +++ b/src/applications/maniphest/xaction/ManiphestTaskStatusTransaction.php @@ -6,9 +6,6 @@ final class ManiphestTaskStatusTransaction const TRANSACTIONTYPE = 'status'; public function generateOldValue($object) { - if ($this->isNewObject()) { - return null; - } return $object->getStatus(); } From 1cb0d41367b7a79f0fdaa1be20e348ce5bd8b628 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 07:35:22 -0800 Subject: [PATCH 281/865] Fix inbound mail handling for messages with no HTML body part Summary: See D18776. See . The change in D18776 to improve handling of non-utf8 HTML parts broke handling of mail with //no// HTML parts. Partly, this is because MimeMailParser has a "traditional" PHP-style API where the return type is an exciting surprise. Test Plan: - Sent a text-only message in `Mail.app`. - Used "Show Raw" to copy it to `mail.txt`, verifying that the raw message contains ONLY a text body. - Ran `cat mail.txt | ./scripts/mail/mail_handler.php --trace --process-duplicates`. - Before patch: error about bad `idx()` on a non-array. - After patch: clean mail processing. - Did the same with a message with both HTML and text bodies to make sure I didn't break anything. Ideally we'd probably get test coverage on this, but it's been touched roughly once a year since 2013 so it'll probably hold. Reviewers: amckinley, alexmv Reviewed By: amckinley, alexmv Differential Revision: https://secure.phabricator.com/D18778 --- scripts/mail/mail_handler.php | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php index b76b3910df..1c3c71f305 100755 --- a/scripts/mail/mail_handler.php +++ b/scripts/mail/mail_handler.php @@ -38,15 +38,19 @@ $content = array(); foreach (array('text', 'html') as $part) { $part_body = $parser->getMessageBody($part); - $part_headers = $parser->getMessageBodyHeaders($part); - $content_type = idx($part_headers, 'content-type'); - if ( - !phutil_is_utf8($part_body) && - (preg_match('/charset="(.*?)"/', $content_type, $matches) || - preg_match('/charset=(\S+)/', $content_type, $matches)) - ) { - $part_body = phutil_utf8_convert($part_body, 'UTF-8', $matches[1]); + + if (strlen($part_body) && !phutil_is_utf8($part_body)) { + $part_headers = $parser->getMessageBodyHeaders($part); + if (!is_array($part_headers)) { + $part_headers = array(); + } + $content_type = idx($part_headers, 'content-type'); + if (preg_match('/charset="(.*?)"/', $content_type, $matches) || + preg_match('/charset=(\S+)/', $content_type, $matches)) { + $part_body = phutil_utf8_convert($part_body, 'UTF-8', $matches[1]); + } } + $content[$part] = $part_body; } From 2d4a158356ef7fd7931495defe3ef3675f59f333 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 07:52:20 -0800 Subject: [PATCH 282/865] Fix a bad link target in Diffusion content search results Summary: See . This links to the display path, which is incorrect. Test Plan: - In any repository, browsed into a directory. - Used pattern search to search for something that hits results. - Clicked the title (filename/path) of a result table. - Before patch: URL omits path context, 404 or wrong result. - After patch: taken to proper page. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18779 --- .../diffusion/view/DiffusionPatternSearchView.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/view/DiffusionPatternSearchView.php b/src/applications/diffusion/view/DiffusionPatternSearchView.php index a679b51400..93ef42d46d 100644 --- a/src/applications/diffusion/view/DiffusionPatternSearchView.php +++ b/src/applications/diffusion/view/DiffusionPatternSearchView.php @@ -96,10 +96,11 @@ public function render() { $path_title = Filesystem::readablePath($this->path, $drequest->getPath()); - $href = $drequest->generateURI(array( - 'action' => 'browse', - 'path' => $path_title, - )); + $href = $drequest->generateURI( + array( + 'action' => 'browse', + 'path' => $this->path, + )); $title = phutil_tag('a', array('href' => $href), $path_title); From 2c72c2b924ffa3f8a49dbec636a2cdca3bae004f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 09:21:37 -0800 Subject: [PATCH 283/865] Add basic support for OpenGraph header tags for public installs Summary: Ref T13018. This is easy to get working roughly, at least, and seems reasonable. Test Plan: Viewed page source, saw tags. Custom header logo still worked. Pretty hard to debug against a local install since Disqus / debugger tools can't hit it, but I'll see what it looks like in production and tweak it if I got anything horribly wrong. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13018 Differential Revision: https://secure.phabricator.com/D18780 --- .../PhabricatorCustomLogoConfigType.php | 33 +++++++++++++ src/view/page/PhabricatorStandardPageView.php | 46 ++++++++++++++++++- .../page/menu/PhabricatorMainMenuView.php | 34 +++----------- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php index ff29050602..cf4dfda2b5 100644 --- a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php +++ b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php @@ -13,6 +13,39 @@ public static function getLogoWordmark() { return idx($logo, 'wordmarkText'); } + public static function getLogoURI(PhabricatorUser $viewer) { + $logo_uri = null; + + $custom_header = self::getLogoImagePHID(); + if ($custom_header) { + $cache = PhabricatorCaches::getImmutableCache(); + $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; + $logo_uri = $cache->getKey($cache_key_logo); + + if (!$logo_uri) { + // NOTE: If the file policy has been changed to be restrictive, we'll + // miss here and just show the default logo. The cache will fill later + // when someone who can see the file loads the page. This might be a + // little spooky, see T11982. + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($custom_header)) + ->execute(); + $file = head($files); + if ($file) { + $logo_uri = $file->getViewURI(); + $cache->setKey($cache_key_logo, $logo_uri); + } + } + } + + if (!$logo_uri) { + $logo_uri = celerity_get_resource_uri('/rsrc/image/logo/light-eye.png'); + } + + return $logo_uri; + } + public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception( diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 78ff716a44..d2b4d0d2c2 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -426,10 +426,11 @@ protected function getHead() { } return hsprintf( - '%s%s%s', + '%s%s%s%s', parent::getHead(), $font_css, - $response->renderSingleResource('javelin-magical-init', 'phabricator')); + $response->renderSingleResource('javelin-magical-init', 'phabricator'), + $this->newOpenGraphTags()); } public function setGlyph($glyph) { @@ -911,4 +912,45 @@ public function produceAphrontResponse() { return $response; } + private function newOpenGraphTags() { + // If we don't allow public access, there's no point in emitting OpenGraph + // tags because external systems can't fetch pages. + if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) { + return array(); + } + + $viewer = $this->getViewer(); + + $properties = array( + array( + 'og:title', + $this->getTitle(), + ), + array( + 'og:type', + 'website', + ), + array( + 'og:url', + PhabricatorEnv::getProductionURI($this->getRequest()->getRequestURI()), + ), + array( + 'og:image', + PhabricatorCustomLogoConfigType::getLogoURI($viewer), + ), + ); + + $tags = array(); + foreach ($properties as $property) { + $tags[] = phutil_tag( + 'meta', + array( + 'property' => $property[0], + 'content' => $property[1], + )); + } + + return $tags; + } + } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index f9e4032d87..2e5b5614ec 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -262,35 +262,16 @@ private function renderPhabricatorSearchMenu() { } private function renderPhabricatorLogo() { - $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); - $logo_style = array(); + + $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); if ($custom_header) { - $cache = PhabricatorCaches::getImmutableCache(); - $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; - - $logo_uri = $cache->getKey($cache_key_logo); - if (!$logo_uri) { - // NOTE: If the file policy has been changed to be restrictive, we'll - // miss here and just show the default logo. The cache will fill later - // when someone who can see the file loads the page. This might be a - // little spooky, see T11982. - $files = id(new PhabricatorFileQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs(array($custom_header)) - ->execute(); - $file = head($files); - if ($file) { - $logo_uri = $file->getViewURI(); - $cache->setKey($cache_key_logo, $logo_uri); - } - } + $viewer = $this->getViewer(); + $logo_uri = PhabricatorCustomLogoConfigType::getLogoURI($viewer); - if ($logo_uri) { - $logo_style[] = 'background-size: 40px 40px;'; - $logo_style[] = 'background-position: 0 0;'; - $logo_style[] = 'background-image: url('http://github.com/.$logo_uri.')'; - } + $logo_style[] = 'background-size: 40px 40px;'; + $logo_style[] = 'background-position: 0 0;'; + $logo_style[] = 'background-image: url('http://github.com/.$logo_uri.')'; } $logo_node = phutil_tag( @@ -300,7 +281,6 @@ private function renderPhabricatorLogo() { 'style' => implode(' ', $logo_style), )); - $wordmark_text = PhabricatorCustomLogoConfigType::getLogoWordmark(); if (!strlen($wordmark_text)) { $wordmark_text = pht('Phabricator'); From c3d6c4b0ee8efed2ea20d4ece90b012ea19b7f00 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 11:25:51 -0800 Subject: [PATCH 284/865] Include OpenGraph prefix material in tag if OpenGraph is enabled Summary: Ref T13018. Discourse doesn't seem to be picking this up yet (see ) so maybe it really needs this meta-meta-XML stuff? Test Plan: Will push. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13018 Differential Revision: https://secure.phabricator.com/D18781 --- resources/celerity/map.php | 1 + src/view/page/AphrontPageView.php | 9 ++++++++- src/view/page/PhabricatorStandardPageView.php | 2 +- webroot/rsrc/favicons/opengraph-144x144.png | Bin 0 -> 13270 bytes 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 webroot/rsrc/favicons/opengraph-144x144.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 78ae8bd461..45da85c7f1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -292,6 +292,7 @@ 'rsrc/favicons/mstile-310x150.png' => '4a49d3ee', 'rsrc/favicons/mstile-310x310.png' => 'a52ab264', 'rsrc/favicons/mstile-70x70.png' => '5edce7b8', + 'rsrc/favicons/opengraph-144x144.png' => '648fb0fc', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', 'rsrc/image/avatar.png' => '17d346a4', diff --git a/src/view/page/AphrontPageView.php b/src/view/page/AphrontPageView.php index 8f3704dca2..bea516a8cf 100644 --- a/src/view/page/AphrontPageView.php +++ b/src/view/page/AphrontPageView.php @@ -59,9 +59,15 @@ public function render() { ), array($body, $tail)); + if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { + $html_open_tag = hsprintf(''); + } else { + $html_open_tag = hsprintf(''); + } + $response = hsprintf( ''. - ''. + '%s'. ''. ''. '%s'. @@ -69,6 +75,7 @@ public function render() { ''. '%s'. '', + $html_open_tag, $title, $head, $body); diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index d2b4d0d2c2..b4e706ab0b 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -936,7 +936,7 @@ private function newOpenGraphTags() { ), array( 'og:image', - PhabricatorCustomLogoConfigType::getLogoURI($viewer), + celerity_get_resource_uri('rsrc/favicons/opengraph-144x144.png'), ), ); diff --git a/webroot/rsrc/favicons/opengraph-144x144.png b/webroot/rsrc/favicons/opengraph-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..92f2114b205565d9029e10ede56cdf23827ee1e4 GIT binary patch literal 13270 zcmZ{rRajeH)UAWNOM?_|ad&sO0>z43i@SSEac?Q^6btU|f#4Lk;*#R-cEWd_bN;LU zBFRlMSF-ocoMVmg?#K^n@>uAk=l}o!OHn~a6ZV?$`ang7JwxR-n*jhSfTE1#NAJv| z06%}?{+1UnKBwcaR6V#Yq^LIxsPG#!@HB7mNI^)fDD_bdQKC5-kfWFaaBv! z{j8fQHotM$f}yXiJ(~tCvop}0pJ}kiK|8-Q4&y155ZSz=KZG+5)zFo3PZfn56k17hH*_kGcX{hqY zE&NFRT_H54zlAF%71|FSUA$oRB76%p`e?}Ic+=TO%dD<@g=k9Y-S%GZBQ;ZuF@qqM z1J@LeamuoT)=THgJkI{w!PcB^y~xQ-n6hMIOnk&Ud-R_+DIgF!HR5BWnvR?Xp>{bK zt8e($0?^3#QHP%0?XGk>Eytd9e6790d${L^lU%qqZyw3Lz-Pi@hMw#d;UE!ng|#L} z@}(xKG6%U(dqDqlB;nS~M4~ykRfc7wCm+5=A1M*f?GFFCz)_$CS!cYm7MuWum{!GzL7`o+Vhbm(eKTUYBdE4F;WZ8c+ zzu*x>7Q96ZJM9c_j^UT^C7}&;w%Xx3@6!d_FW>D%%+Ehwl=jJw$O)Y_RAqOX0K*90 zx})d%&0}XAr}G6qCwvXy6OP3ZCsxY?PC}B-HXywfYg`UGqft5n0{RDdC3B;cYOeb7U6o(^lMQ|0b)Qwz zg>#*5Caku$cGr0L-}8+t#{A2eI#;VIXYlrfR6cnDJ^+TM^r_GR--Da=Bwm2Q%|Oi; zpsI@@Yl1o^eVvfC#0ft|JN--K_}jWTVP$8R$PYbVU8CA$+I+c~Tzh6_`4M$0^~IrM z9vQn~(zc{kd^$)!%6{@*)AS9#N2U2)xr*6jN7&z7^s|V=?K1AEE|ei6f2Qc*`x}Hb^L|VP|{rBe{Yh z_2n2PG+rX7vYl~?**LI>Y-v7;vHw;ha&Qnu-eYsjCz_&}{-ty#Y5U$9Au{ms6TKLC ztiFYh4gM=w=$cY!?qRRLOecq>>hO(ChbGOaJ}*~lQZ;)@A!a=nS&jlH{9I$_=J{`b zM3Sc&S=6P5u?6mv4U%cUo59r+#0&0m4Z%q@N&2R4&HAs5N{n;F_L(jIv%<>KX1x9C z;=;#p4?B!(nT8yd2M$%f-kAhA*wO$;BxnyL_6>89XCRzxX%_F++@jWne1+wl7-`Uq zW_%9SPCq)`+$=Ah$ivjWxZ`4{SMr?vTyBP-v45l}N3mDz#`!RU*SbNOk8@4+CWYF&{V&dX-gk?-&oO5(uEfj{;`T35ZEA{!}<5Q!88w#1jR~b%LVPZvM zy6wGXtzvE{oBvUTQ(Ox4>`sj$HWk^6C7YQPu9z1Xf7T~2H>RJmFmAnV=A-L1%w#BV z)5&;L)mm#f>l7i3WS^$l9NPjEXhdZ9ncm)(Irt^p9;2(Ry%_ZG;IGdFOHdmKRP>Xx zNuoB12*FE?vr+A_)66nm`j4fG@5s!S`it+}Z!yujn*b+-NCWhnxW|>vUrtCNu@&iM zan@BcBX9e*9;?_Yk`i!(j`?vWGPy)fmQ>kT(q+bo75rZcj()!BcpO+OQTq89xyciG zC-9Ti-cW!H%Mm^d!rQ_Ie-hCc&Vng#cpuI1`#CZExY?~&HDq44&i1+&QJ@R*xs|0z zNnQBp?y`-nai!C8acMRQ1SkKcr$*S)2Bl>DXSKMw=;L9JThc$!zl`QRZkL{gbVeIt zJObFoT**E1{n>n)yihUPIB@I4Rxb|JRoOxoJM8P7SqJXJn998{@V=##`o0J6g{R}vZc)MDyBoV4Q0Os z-l677jPRACeYeO!+PnhSLJ_3%k`LcT#k+P)NIjnI&HWn1$pu9dYl2_O&?W5PA~ME%wPkE!TUz*kcsWQf*G zI`tI>Mc0?!>YjOf7Uroe9%zBsj!^L?`2glc2Kp4(kxoVf4>jHtdW*Ym)`OSItt}u2S|92IT z?$$w?e%xBu%%Vw!0)p@4#!K0ddJxxLkD*{Rt`o8ffu8$htHeUg=|VH+Pv9xgxfB?kJ}J} zQzvn*UyxGA2yqG7~OHqzyeDirO%`ubcqUc0c0rMb|oz1v0SG(OvDvQR@)JSj_#r3Ut z#Wjh_AIfKIt!4E~clqKKPb{r*x9drcn1xj7cdnoL9mik$P|5P^E^QTDw?U&)KR+}e zk$nkl3^-x=08&)UGfKSYgyYpksRY_((^A^*{LIe&Oh$gDmn@Gesc8-$cmwVLisUx^ zYInkYdt#jaiRQ~QQYW=A7;vuwn2#MKlcAWoDIXvijO%couu(gF6gfPr|5nDEc4 ziRZC06aWQfRpR^L6G~^-hyY{)ivxM{pNp~Tu7{h(NT{`{rI!AG=C8agqEYvtm)W>>X94mC9J(K$BaeH!14ZdvGxiQ1uE2i< zxp5<+qg&ZH**Q5^`~A{f=}8V4 z&2h;~`ZQIJ33zTlKN8$2R)f|m$(mJsTU>*Oey>LwkP)Miv*!UIl1X1vaFStk)&w$g zQd4Zn8Z<%j9jsHI-^fMT+y2T~EmLma1Hsjpjm&3KP~5g|>Wi|0~fU3nwpdl97= zc3I3n3-bs=P;M4#fbIGBLHbm2g-Bu|$U5yj3#hrB)Ie_frGrfCiHkmY4MOFvGzX}; z;DB*5%F&ZgXFMY2S#NjTVvXI)+qNj>%6NtZhVjWHO%{`@Ybg#$nih@x6zAvlD1_)I z#=Z!j+E#`?e{e5r{5@9n9@wrvGbH`a3hUuO&eYSPp4&lR3&#$C+;kzb5i*SEc65Aj zPwx*17F4XwC|ULl6wKeYUIdOL(JIlUb!HYK^9P(+;dfG`w%$`gesHun=nBlkC0iuZ zQ^*fPBCAZq!g4lej7xX6{vyi9bjJ@?XJX=uy^|jqc-8i(fDCZk>0x{DD2jRK!~})@ zW75U`H>!K2FVm%!osvI$3opPu=5+U5Y6wTaStidDvmh5uc+`zVOZ@}wox3^erJz=Q zOe!w)h(Mf4r^Ppc&sN4mleLM+7QKXWYh>42eC#bdL&H6@8#r191}B0XfdOgJ{SZcV zN68Z9vxmMOk77NNi#wGKzGrUM-l)QQ_B57yt27;HX^s&k@@DC*6>xb>d2ml366R z*?8Vs+o+v&(*9&Dr26OUE_uX>I;Sx7 z>jVPgp&wvmqdO-NyCwbW0r8&JZ@i4Z(pwEbw_q1D1_m;(dRd-NTF|$i&V}#A@FeJ+ zJ|{oq2~PP(doT}{;{>IoIB<+nagMMERJ$@GQvjt3W?t$RWS6B*(&*xea99O=%} z1RcnaJKf;*oZ~;;BQJ4Mp1Sp%N=os=7QmpC_-~uoIt$m0lqpN!+FHc6S1=~)C5*$= z)Z{^2V?IhzS5)X$ApE|gaQ6HbZ95=`12*XTMW>xO+u}2Hz{fF&(&k|3>t|r@!Mn(> zV1Pj_!qWLu)|CN0KM&$E5S#g8w55JmognoYtt|0I7brKPuBWvPUXwksZpbHB5@AjE_3ip>ZP12=!DPc8Zr??e`&cEs&+?2iR& zmZ*rExWx<+77qeQOVHmj8Ee+|nAAYb4JlRD>$d`7@#3ttqoiTKTrx=>&G_t>eCA=Y zWX^Wcmnt!+0{XE(jZge}?_13P*W1j2Z}TdW84h5MC5_@Zhe>vR$lTpyjY0PqqXPZb zRv6x06wrV8RR+@?-)7kHf+`@&GeO^yEkb`ldbRMF!!S4#aOwx(;B|5Fmj?y#o_Il4)GUE zWVa;@h^2o)#DO_Cf5hhGsL>32h`)2K&DSC~mjg|wn)7N7NKm47hj04mlY#6cD@E-J z)8BDb(!4|73G5Nlxu*+e4)B$O(1fF~V?GlPo_>vrQXa5rK%n~c=MO4Z&nNNp1;PTjow-xHnGcZj~n~mdV zn@H&;jEpR&{DvqchTESipB>_PA}oTRNmb2qRQ`T^kdIcwu*L?NK9({pXtI!c8a*;F z%mcH*$TD^10XyE(CR7uYlvO?m5;2;cu8kqcVyDFdj`*)Wl0dB?jEsuSdtuKhf2m^$ zrFpqyF=iVCbhTq!Taj;l=(aS655JS}9ts&Iu1X14$L6IK`I7*)7&TB-cJ@HoR`krZ z0P6_^hlOV82M?@hUg|l~6h7AYkX?M!(FiyNHMK`$fr&RDyR*gXfCX)T)OYgA8pbZj zc^?)S)Rk2SX0vJ&mg_LWFb_ju8%b)Cs*$+OEre77PgI?!fRIvC>@CF}#ylAuVA$K& zeF;VIw_p%xcx2?|OZD|5rx({Ox8>GJ(u79J`1vxnDq6GbH#{QUWL@7{_?M^M@K>2Z z8WQ^nszBJ;+DiOz9TXby(A7)k6!868*C>)wwG)qEV4GTO^*K8==f2-FLg#}>vYCun zK2d&>IqZCUZ&k$Oy82@&k*RF{@vOnZbUQsg>j9v{tcw|Gd8LOsnAyr*f~Y8=nC=95 z^qE~2xW7^~=qqrcD(GnztitM%d)%vf>F#)W_A6g&M(7zxpj+QfK>l!jf+r!G6)wpR#L>XdlT)fWt~yTiA#?)bQBd;_V;ucBs1&~6)(@s zp>U5ClE&<$Hv(v-vIA(b_okt2>l^eb`G=NBY*#;nk~GPmL6|tA*Z6U7#TkF4c;F}2 z$D;xpOOwoZ(yzRqab9Ab^*Xj4EDfqTzZYjes3-YrBX|3iKbnm;&d;K7k1(q>X(*|G zbZT!5mR93yt7OsT<{_}|I#wFnB>|HRIuT;xvccK-!$?;+e6argyqN$ zJJ+Wn<%r*YDN_9LPPH^9aB2M)CE9y(owLi$`5Lp&XtMG9qja+L4@XsxO z_k-KFT35EjHIKM1f4%ADe2jET~JTU#jNSi zdPb3XzRp-N4q~yS&7&)Wv+=Ns?BR`V`k^E?%@~lT$jy|k{o-|~(Ptx_UigScDl^N} zSC2dC=gzOi&nYfJvTkEa5grp=t12>>kb}`hj8yIut0Zz(k0V3=s5$ogk$o?h^7_9? zv8h}JO9La6?>;X^1+81&*%TaGKc-nTmV>-E;)AAhP|RDt9fGrWP^9TnUu!G%@43A# z|0>}uDdk)=GJ5f{95&Yu0+EWGZ!<4=ou(FUObQJeLjm3Vg*&gi|Bf*!j##IktDfRr zivPk&tDL%XbSp1dEy{^emc^`V*yUfNGlu?_&|TiBIK-*IW+4O7+gI}(MniMo0o;V6V|a}43s+EYc%%{ zcwCO&>rSE z+@$T?S{_g=YZFB?C!Glf|y*LzDpRA!>Lji%kO`fa>hVHtzKcE^2;FT!|B3mT#zm=<-Ai z7vV152n+2E3tFGk%p;-A8VuwE+{!u|ePeC;)^E~d1Wk;VKb2ON+S%AfHy0$6sYK;b%>ZreRm3)Iby0=+*QRl}w> z87gQH1Z@w#1=zv4c`DkMIde*Bfc z*;YrDBo*eaWs3*>ZZ@AVoy$2g%!10!y@o7q6Rlz&#T`|SLw<2wH*dwrgt`%T=wEK3 z`t>^?ICvwkP9xkUWNnc95cAzYp}gJ+X9bUM5^xSJxKZZ4fBI~$*+=b$JEWx6Ag^AG z>4k-oCrpYO5P)-uZkSkfz?lt6rQsBVCVw=)Ru5UHJ7Op5sQ3)^CWSycX%S&7948!& zVHcn1TO@Rhw>VlQEw!ai1rL5;5Wg5tr|8ImpVvYS+?t|hR|63kb>yTDWiZ70k)9fX zXZjo8?^Hv=W-xWt+imZk1i~qv0uN_`scBEi%yV99k!k z7Y%z23P<||(>62m<(+YQG3rhoMP*enC*)XnAIq<_Jgm+Pj#P-^neMcKXLXc{wAHn0 z7i9L9?kl`2>9}vrLnZ{o-fNlRdQj( z($XRCYw>602S99yznVilc>z?53YH2=wd89@yKY5tcjrg$YWc9>XsXtCqGLgH3($gv zoyaU0;+L`H#3rz|+_6V8o(nSaa5t&SLPEEfs5_S&Ad5L_Gn@Oae9|1D9jI}^XQ2?< z;HxNXz6j?hxoFH_jGxs^!9BX#HyFTd96_jb_`cA@z{xnI*zB}Rr<<1LpQrHjj)J=+ zh+X7n`$<7naqc{lK{$q!YrodP71L$?XWAr4#U^ET7g!~d={?7@aTBbq0TwZsFnc{I z=`Lk<^by>14FvIsO7dTRllW-A-xeD|825~GlE6f;R|TEfkl;(Jv(F0lZuDN8zDDu+6Cbk%%R zdwv(b-|~uIi=IiMQx>*ALj0b?-Dn1g#D$iJ&=&@C-t zNZ>)%Cc+F_5nIoHqA7?DML)YW=)3Yr4UnTvm#8ILD*t@n1 zZSP%vaamj2A2OdECG2-U{lh>RZJrD{{SzSGzJ2sNi3auuVM%|Xnwr+CINE|>UN~D}=Brq8`K5~>6o05I! zRX;MEARKP0n>zCT5`h|#yEwM50B5CC@IYg5lv4OkmMdo2W&ZHm`?+b!T@EwN$^y0E zXdK97%JAbSK2t3hH`A|X0ogHF8jOQM4i_O7Jm64a&;)J||3bs2R>M2xK*=aa2TN9j zO+v+40}%-z*hm_L5ml@q3g4^^(@%1)k1E0WcR1_E!t_9=LOI$x1G01}!NsE*=VSH6 z1l$w+$54(=wyn_y(Kfd>Q5Tn}Yqu*=S0Q4bPr0Z~t*yBh#YXm&3LAt~yn-tv{&`OHn z$@wi*weUwlVNn;tg%8|va}SPOLH3G}1O4|BIKaP^t$itn=P&(97WC9ihgL)y>#sI_ zl7T$1Q_>BO8tg{3!r}0#*kTw=#$mOI1*qrOXP=p&A=gR#kr`1xvmH^C1k1D_sty>; zC_ForB_iQRZPyuMF%h92eiHSAAF2QHubm{yJ)HAWW3o_xPL zSq>0o`K;kw0_gkdibY2{-rV@&KS52-F1)k!XJm5n`30`d0V?M2wVSEqh=>$=2H$MZ zuQpy!G~MNNbEV=$$d`isN)rgka&}oVBV+VT{ov|5$0G~Vj0$S+>QAk^3r_JSXWcc) z@He=bsQ8nGb!3CE=nV%SM)n1Jf&*RSTgd&*ed?Iu08XsQBuLg{;y6p((|AB|$It0N zd^K`O=p=r7z*Uhfx@-Z5lKw?@fS&YaVl2h=B2vxYz@}3V&3n!2CUS4F;{sAqW$m+C zEdjH=4=tE!!!83b5V60~75kr56jiGEc`X-iS!FF}0;?`)OGj|#N{`fb9MSha(yO01 zE+GG!N)@*ed_@>8g?KS!T5!Jn6{vTDgOlPqx_HWr2)SbNc8Wf`AK%$!HdIn6kARY4 z@@?i&I>q}AI4==Wxo`dHMEZDLLj?Gs-B7Dub%R;PZO5lB6~f)9h-76J{u`Eha4?HZ5}zPwlj;5C@M^*=U*CCe5a5qLl~n!u%quLh*7U+G`L(+yFDy!+ zjNn(2M)`|%TWrDn)gZ@r+{ee_rVSK=Ffv^-x01BOnA@^REL!9(OCR&joQuruvuMU8 zgKtD-YtmG>;kUuhv%Lb>4O0E*I=5@<-A#wNRE)A1w&o?!0?(R`jw4rhCga~i-sb(~*U~Q1wqL8TgU|-Jxy|jd&hfKhnN46OevQSI z&g4k2$Yq>NL5U|x9%L@@6twU>68sK}!sR&@21#6c_6_b)h?$*m0KL@`exe(yA(t)y4>s+m`+A}+Zqn2Oqgfc} ze;BN@&3SO%J72INl6|{w?5l7xHEow-@x(mb+UfoB>AB=hs$&TVRE_mV>;_C~dlv}X z+l8*U3OOO7wq)I~x z(*$6G=?=Sa)mIqCvXkqC8{xhJa~I`|I06=y7RBbA6fFjpBTnC1!zUiI_rm?9t898X zKX-aGhfO`&e24=0u=+D>PeZI|7>J$pSh-`Ox(h2pOH-OjO68GveydiV9Flzuuk95=ge}{ zZnW&PO_9ng1*6RHanWvsYb?bq(-;nBq#6q+|Khb`45}GYE&2^coR9AsG=>mA$zQi} zzA&{W;bcOjN|dvBEv5{+_is+gcDk1Ay=Q$ttyxH{9p~VX{&~Q{eX773*+=bFLgT~q zb4`T&6CE~-acnfdbc?JpuQTtxcVZS2tZ|GhPo4h@beXXDflsjB$`zWxPO;2k&6OjG z;M>Ldismeid8+YuuLa|vYRE>Hnp>?UC5zXT-Tv8zbCyt&VI?PGb+G&7^&llc69fOs<1-UljcQ0BD>( z#3@I_m0B-XaZMQ^5?;=|h}DKnE@hZV8cRjMUORVWQu4YNAR#3s0;ZyPA?1Y|eV)6p z_ZtDG`Yj$T$bg;w(U2Tr70g~sHL!TCRO?Z3Du7eQGWi^biYBgA#I;@C&kM2Yw?Y>5 zITB3QzfnJPmTl@JoEs%7eTh|ZDwSIp;T%bxnRZ!t$J25vEU4OEE#{Q${~-l z)bFbquf@p4Jxe$+NYXm8Im^%#8Wus(z(jVkHk!Ag|I;|n7)M6nz+>&xz!RvXahp=~ADZoJKP@HnFrW{4lx<&1^(D z9tr{(Eysu^BK^iKpnAgsxx$j%3vS*SdOeg~PftPSC(i|O9%swD75#e6mPAr{&R~xr zjz{(q*(H=jNAOqmwnh@aSCEOZsU~GFq8C2$JMJ&;8oq-9bsJk(O>^hfKJFrv%}v_| zB1$|yR6kZ#haD+WY#Vx!Nik!n$MlpPMriNt3e4WJxPjBX>whYOEp)rd)Q4lYKd?{J zxYLw9F|-+5Q?x^dsY1n8>G@i(xX3!*t~s`=gtk$FW}1!D8f?Pb(P|RlJQri|CECQT z5t+J<{gcvT_f=t-?K$iS)1`-IW;PzpXsaw35j5$L3rp1~Cz{8->J+-Tig6TBnPD zdoeU;&Z2kOY9&N)@MEZNx&>rg$%ax!j|_;!U5Lf!W;t2fd8MwB?gUhDz+|PwG4--y zrA0+-HrE#Hj*mr`AG7u3cr}bp`r3Zo^@IBvfkF&#v>aRDV13^{gs$@aViL^H>DpSs z8J*BPwNYkzV6YAU#$qp>C1EEJhAk7xB8HadlH~m!k8#;WNyNvy$h4N#B5Jb z?DB<0@8@ZiXzsJTewoz5&AlThNwvIu3s z#5jfEGmQJtJH72#8q+|r#SE=>fTnnVdD4LaJ#gBk-4Gy2hmo&1Dc7kpN!ZmdVHhlX zbUOtN5^R3b)=j<;qLNGR=W`I&M96CJTkm{CJ*&r$(y{M> z@~C^S4(h%P& zi#2TqfIy0*U-aag1c%JCMu#Kw*Y5*GUxog^#SZT5hxplr8oRC-xNdmIfeK(ss|d$i z;rjmRuI$jVrO&3z(AI`Uw?W^N@%MF9I1i>dv~o$1!oHT)i2NCL@5gMkK4gnpf6ieEF2utNG0L}uZ&Xc4L5zSH9I_fNx*>2#S!-HbaP(+0Ii?&(KP zO6!?vCngsq=gASL$Jjd=M4d8#UX~_5W)O?HrJDOCaK)>Y&VyPhMlXdoo*N?rVm&Hw zf>$FoWA^+6EFMo=fF3q!$Nrfd9KzA?u*?)1cyT()*mqI6vb}rI%DUCTF?Kw8Bz!vC zaFMP5>^XavuEv+=kBU3S^=ty9>af~BD-II1(_~5BceHG4vqhzEdDw6CVmg@S_FMb! zGIfJAhUNzQ#G`N|_a(P0aQF%~2P6UUX~l>!ucB+}KC5G;t78aR#1wYM{a;cFc5MqZ z>85lf#=Tj7czYAwSX5qGs_c7RI2-h6|L~^HeCWotWt08QC=7373EF%!9Yis7gf{6B z`8`I0rYj#GA=3WX0-Nx~GQCH^xz$^&JX1636@_9mxf-8x7Rbb}<1M31uv=#vNPW+* z!d~}x6R$=`#n903sGOu}JUd<7w$Pr%9!?^jbBEKMYVwj9UFGbp&&p8}46$ zoaQA{lbl65*Fh)ryV3*mVFJMhyNsGIuU+}(IFS{j&QDWoAWa7Ul(kp*^$MfTWPY0PrrzWLn5ElziYUXt~`L1U_7Hj_a`OQC&B_yl8&5 zi!KU!*YnEcG`+$%!^~<>zN09pY5)t1{BQq!hzzI%QDS*??tj2srS&(lsFa5IPPsza zgjRos(6rWYG0P3>0>?8B_{Olpdu{xUxEgRrumZ8qH4Bn_O&?EfDqG7zTOV}|sVeWU zhiKo7!^8!epPv+6%?Zhw81B00Kp;ml^efmWp^2R9%kZ8~7|`7vgYW!d%c@wY{S|9@ z+jhMMCt>zgaQTFxwy^r0GeorDBA_=ob*a~g5kCe7GFSZvGNWBj!= zN`OF2uegax=l)UXE7akw%9rB1_?H}bmVWa880wK#Z7)+l#lc;fhYnS|QjbVNX~zD> zER#6zfHCnLA^+d*bo6j>k7m!x}x?ST+Z)X(C=qG;rsrh z#fUyrti%j3Mc=$vX|n}A5U+us6l%W8qm0R^Io)(y)Tc^>*$Ry(OF2zNrdtL54{<7C z&p&x$ybn%f5>5X)zd0zV`S`4V zny*ZS_>!T!mvn$MSshU#BEX1vc8t`XaG!`HQZe!uf9$`P`PZ$>QToa=b2sVVE!G4r zz#p8jx58#J;*c`7d3t;+wfcy}G?|;#h8$?hg5WH#ha#CcYkIrc^mtpRh_ih;NIW^e z!1>IuO`#7?M>F_Cj`1SdKpgq)eF@opmGYLO`0ZYf9+Gc{W=cQGX3E*x8zF6%qOsRV zp5KW%+qHT-W714pwRwiCMI$pG(a-bb@hpNmV6D}~dhJ_dS_LzEgG!(xajyM2L1b;z zqp76_Pbdx`Q_!z-CoUhMAQ~2eAFX^e_oz3kfuj&GuLQL%TPvX=wq*HLQsJ| bFYpXZa^goDzqnxc>i`sG)nqEAOuzjf>K7hx literal 0 HcmV?d00001 From 49b57eae7df52c189aef1d973823c697fc97fd4b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 22 Nov 2017 14:27:28 -0800 Subject: [PATCH 285/865] Revert partial/nonfunctional OpenGraph support Summary: Ref T13018. See that task and the Discourse thread for discussion. This doesn't work as-is and we need to `og:description` everything to make it work. I don't want to sink any more time into this so just back all the changes out for now. (The `` change is unnecessary anyway.) Test Plan: Strict revert. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13018 Differential Revision: https://secure.phabricator.com/D18782 --- resources/celerity/map.php | 1 - .../PhabricatorCustomLogoConfigType.php | 33 ------------- src/view/page/AphrontPageView.php | 9 +--- src/view/page/PhabricatorStandardPageView.php | 46 +----------------- .../page/menu/PhabricatorMainMenuView.php | 34 ++++++++++--- webroot/rsrc/favicons/opengraph-144x144.png | Bin 13270 -> 0 bytes 6 files changed, 30 insertions(+), 93 deletions(-) delete mode 100644 webroot/rsrc/favicons/opengraph-144x144.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 45da85c7f1..78ae8bd461 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -292,7 +292,6 @@ 'rsrc/favicons/mstile-310x150.png' => '4a49d3ee', 'rsrc/favicons/mstile-310x310.png' => 'a52ab264', 'rsrc/favicons/mstile-70x70.png' => '5edce7b8', - 'rsrc/favicons/opengraph-144x144.png' => '648fb0fc', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', 'rsrc/image/avatar.png' => '17d346a4', diff --git a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php index cf4dfda2b5..ff29050602 100644 --- a/src/applications/config/custom/PhabricatorCustomLogoConfigType.php +++ b/src/applications/config/custom/PhabricatorCustomLogoConfigType.php @@ -13,39 +13,6 @@ public static function getLogoWordmark() { return idx($logo, 'wordmarkText'); } - public static function getLogoURI(PhabricatorUser $viewer) { - $logo_uri = null; - - $custom_header = self::getLogoImagePHID(); - if ($custom_header) { - $cache = PhabricatorCaches::getImmutableCache(); - $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; - $logo_uri = $cache->getKey($cache_key_logo); - - if (!$logo_uri) { - // NOTE: If the file policy has been changed to be restrictive, we'll - // miss here and just show the default logo. The cache will fill later - // when someone who can see the file loads the page. This might be a - // little spooky, see T11982. - $files = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($custom_header)) - ->execute(); - $file = head($files); - if ($file) { - $logo_uri = $file->getViewURI(); - $cache->setKey($cache_key_logo, $logo_uri); - } - } - } - - if (!$logo_uri) { - $logo_uri = celerity_get_resource_uri('/rsrc/image/logo/light-eye.png'); - } - - return $logo_uri; - } - public function validateOption(PhabricatorConfigOption $option, $value) { if (!is_array($value)) { throw new Exception( diff --git a/src/view/page/AphrontPageView.php b/src/view/page/AphrontPageView.php index bea516a8cf..8f3704dca2 100644 --- a/src/view/page/AphrontPageView.php +++ b/src/view/page/AphrontPageView.php @@ -59,15 +59,9 @@ public function render() { ), array($body, $tail)); - if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { - $html_open_tag = hsprintf(''); - } else { - $html_open_tag = hsprintf(''); - } - $response = hsprintf( ''. - '%s'. + ''. ''. ''. '%s'. @@ -75,7 +69,6 @@ public function render() { ''. '%s'. '', - $html_open_tag, $title, $head, $body); diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index b4e706ab0b..78ff716a44 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -426,11 +426,10 @@ protected function getHead() { } return hsprintf( - '%s%s%s%s', + '%s%s%s', parent::getHead(), $font_css, - $response->renderSingleResource('javelin-magical-init', 'phabricator'), - $this->newOpenGraphTags()); + $response->renderSingleResource('javelin-magical-init', 'phabricator')); } public function setGlyph($glyph) { @@ -912,45 +911,4 @@ public function produceAphrontResponse() { return $response; } - private function newOpenGraphTags() { - // If we don't allow public access, there's no point in emitting OpenGraph - // tags because external systems can't fetch pages. - if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) { - return array(); - } - - $viewer = $this->getViewer(); - - $properties = array( - array( - 'og:title', - $this->getTitle(), - ), - array( - 'og:type', - 'website', - ), - array( - 'og:url', - PhabricatorEnv::getProductionURI($this->getRequest()->getRequestURI()), - ), - array( - 'og:image', - celerity_get_resource_uri('rsrc/favicons/opengraph-144x144.png'), - ), - ); - - $tags = array(); - foreach ($properties as $property) { - $tags[] = phutil_tag( - 'meta', - array( - 'property' => $property[0], - 'content' => $property[1], - )); - } - - return $tags; - } - } diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 2e5b5614ec..f9e4032d87 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -262,16 +262,35 @@ private function renderPhabricatorSearchMenu() { } private function renderPhabricatorLogo() { - $logo_style = array(); - $custom_header = PhabricatorCustomLogoConfigType::getLogoImagePHID(); + + $logo_style = array(); if ($custom_header) { - $viewer = $this->getViewer(); - $logo_uri = PhabricatorCustomLogoConfigType::getLogoURI($viewer); + $cache = PhabricatorCaches::getImmutableCache(); + $cache_key_logo = 'ui.custom-header.logo-phid.v3.'.$custom_header; + + $logo_uri = $cache->getKey($cache_key_logo); + if (!$logo_uri) { + // NOTE: If the file policy has been changed to be restrictive, we'll + // miss here and just show the default logo. The cache will fill later + // when someone who can see the file loads the page. This might be a + // little spooky, see T11982. + $files = id(new PhabricatorFileQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(array($custom_header)) + ->execute(); + $file = head($files); + if ($file) { + $logo_uri = $file->getViewURI(); + $cache->setKey($cache_key_logo, $logo_uri); + } + } - $logo_style[] = 'background-size: 40px 40px;'; - $logo_style[] = 'background-position: 0 0;'; - $logo_style[] = 'background-image: url('http://github.com/.$logo_uri.')'; + if ($logo_uri) { + $logo_style[] = 'background-size: 40px 40px;'; + $logo_style[] = 'background-position: 0 0;'; + $logo_style[] = 'background-image: url('http://github.com/.$logo_uri.')'; + } } $logo_node = phutil_tag( @@ -281,6 +300,7 @@ private function renderPhabricatorLogo() { 'style' => implode(' ', $logo_style), )); + $wordmark_text = PhabricatorCustomLogoConfigType::getLogoWordmark(); if (!strlen($wordmark_text)) { $wordmark_text = pht('Phabricator'); diff --git a/webroot/rsrc/favicons/opengraph-144x144.png b/webroot/rsrc/favicons/opengraph-144x144.png deleted file mode 100644 index 92f2114b205565d9029e10ede56cdf23827ee1e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13270 zcmZ{rRajeH)UAWNOM?_|ad&sO0>z43i@SSEac?Q^6btU|f#4Lk;*#R-cEWd_bN;LU zBFRlMSF-ocoMVmg?#K^n@>uAk=l}o!OHn~a6ZV?$`ang7JwxR-n*jhSfTE1#NAJv| z06%}?{+1UnKBwcaR6V#Yq^LIxsPG#!@HB7mNI^)fDD_bdQKC5-kfWFaaBv! z{j8fQHotM$f}yXiJ(~tCvop}0pJ}kiK|8-Q4&y155ZSz=KZG+5)zFo3PZfn56k17hH*_kGcX{hqY zE&NFRT_H54zlAF%71|FSUA$oRB76%p`e?}Ic+=TO%dD<@g=k9Y-S%GZBQ;ZuF@qqM z1J@LeamuoT)=THgJkI{w!PcB^y~xQ-n6hMIOnk&Ud-R_+DIgF!HR5BWnvR?Xp>{bK zt8e($0?^3#QHP%0?XGk>Eytd9e6790d${L^lU%qqZyw3Lz-Pi@hMw#d;UE!ng|#L} z@}(xKG6%U(dqDqlB;nS~M4~ykRfc7wCm+5=A1M*f?GFFCz)_$CS!cYm7MuWum{!GzL7`o+Vhbm(eKTUYBdE4F;WZ8c+ zzu*x>7Q96ZJM9c_j^UT^C7}&;w%Xx3@6!d_FW>D%%+Ehwl=jJw$O)Y_RAqOX0K*90 zx})d%&0}XAr}G6qCwvXy6OP3ZCsxY?PC}B-HXywfYg`UGqft5n0{RDdC3B;cYOeb7U6o(^lMQ|0b)Qwz zg>#*5Caku$cGr0L-}8+t#{A2eI#;VIXYlrfR6cnDJ^+TM^r_GR--Da=Bwm2Q%|Oi; zpsI@@Yl1o^eVvfC#0ft|JN--K_}jWTVP$8R$PYbVU8CA$+I+c~Tzh6_`4M$0^~IrM z9vQn~(zc{kd^$)!%6{@*)AS9#N2U2)xr*6jN7&z7^s|V=?K1AEE|ei6f2Qc*`x}Hb^L|VP|{rBe{Yh z_2n2PG+rX7vYl~?**LI>Y-v7;vHw;ha&Qnu-eYsjCz_&}{-ty#Y5U$9Au{ms6TKLC ztiFYh4gM=w=$cY!?qRRLOecq>>hO(ChbGOaJ}*~lQZ;)@A!a=nS&jlH{9I$_=J{`b zM3Sc&S=6P5u?6mv4U%cUo59r+#0&0m4Z%q@N&2R4&HAs5N{n;F_L(jIv%<>KX1x9C z;=;#p4?B!(nT8yd2M$%f-kAhA*wO$;BxnyL_6>89XCRzxX%_F++@jWne1+wl7-`Uq zW_%9SPCq)`+$=Ah$ivjWxZ`4{SMr?vTyBP-v45l}N3mDz#`!RU*SbNOk8@4+CWYF&{V&dX-gk?-&oO5(uEfj{;`T35ZEA{!}<5Q!88w#1jR~b%LVPZvM zy6wGXtzvE{oBvUTQ(Ox4>`sj$HWk^6C7YQPu9z1Xf7T~2H>RJmFmAnV=A-L1%w#BV z)5&;L)mm#f>l7i3WS^$l9NPjEXhdZ9ncm)(Irt^p9;2(Ry%_ZG;IGdFOHdmKRP>Xx zNuoB12*FE?vr+A_)66nm`j4fG@5s!S`it+}Z!yujn*b+-NCWhnxW|>vUrtCNu@&iM zan@BcBX9e*9;?_Yk`i!(j`?vWGPy)fmQ>kT(q+bo75rZcj()!BcpO+OQTq89xyciG zC-9Ti-cW!H%Mm^d!rQ_Ie-hCc&Vng#cpuI1`#CZExY?~&HDq44&i1+&QJ@R*xs|0z zNnQBp?y`-nai!C8acMRQ1SkKcr$*S)2Bl>DXSKMw=;L9JThc$!zl`QRZkL{gbVeIt zJObFoT**E1{n>n)yihUPIB@I4Rxb|JRoOxoJM8P7SqJXJn998{@V=##`o0J6g{R}vZc)MDyBoV4Q0Os z-l677jPRACeYeO!+PnhSLJ_3%k`LcT#k+P)NIjnI&HWn1$pu9dYl2_O&?W5PA~ME%wPkE!TUz*kcsWQf*G zI`tI>Mc0?!>YjOf7Uroe9%zBsj!^L?`2glc2Kp4(kxoVf4>jHtdW*Ym)`OSItt}u2S|92IT z?$$w?e%xBu%%Vw!0)p@4#!K0ddJxxLkD*{Rt`o8ffu8$htHeUg=|VH+Pv9xgxfB?kJ}J} zQzvn*UyxGA2yqG7~OHqzyeDirO%`ubcqUc0c0rMb|oz1v0SG(OvDvQR@)JSj_#r3Ut z#Wjh_AIfKIt!4E~clqKKPb{r*x9drcn1xj7cdnoL9mik$P|5P^E^QTDw?U&)KR+}e zk$nkl3^-x=08&)UGfKSYgyYpksRY_((^A^*{LIe&Oh$gDmn@Gesc8-$cmwVLisUx^ zYInkYdt#jaiRQ~QQYW=A7;vuwn2#MKlcAWoDIXvijO%couu(gF6gfPr|5nDEc4 ziRZC06aWQfRpR^L6G~^-hyY{)ivxM{pNp~Tu7{h(NT{`{rI!AG=C8agqEYvtm)W>>X94mC9J(K$BaeH!14ZdvGxiQ1uE2i< zxp5<+qg&ZH**Q5^`~A{f=}8V4 z&2h;~`ZQIJ33zTlKN8$2R)f|m$(mJsTU>*Oey>LwkP)Miv*!UIl1X1vaFStk)&w$g zQd4Zn8Z<%j9jsHI-^fMT+y2T~EmLma1Hsjpjm&3KP~5g|>Wi|0~fU3nwpdl97= zc3I3n3-bs=P;M4#fbIGBLHbm2g-Bu|$U5yj3#hrB)Ie_frGrfCiHkmY4MOFvGzX}; z;DB*5%F&ZgXFMY2S#NjTVvXI)+qNj>%6NtZhVjWHO%{`@Ybg#$nih@x6zAvlD1_)I z#=Z!j+E#`?e{e5r{5@9n9@wrvGbH`a3hUuO&eYSPp4&lR3&#$C+;kzb5i*SEc65Aj zPwx*17F4XwC|ULl6wKeYUIdOL(JIlUb!HYK^9P(+;dfG`w%$`gesHun=nBlkC0iuZ zQ^*fPBCAZq!g4lej7xX6{vyi9bjJ@?XJX=uy^|jqc-8i(fDCZk>0x{DD2jRK!~})@ zW75U`H>!K2FVm%!osvI$3opPu=5+U5Y6wTaStidDvmh5uc+`zVOZ@}wox3^erJz=Q zOe!w)h(Mf4r^Ppc&sN4mleLM+7QKXWYh>42eC#bdL&H6@8#r191}B0XfdOgJ{SZcV zN68Z9vxmMOk77NNi#wGKzGrUM-l)QQ_B57yt27;HX^s&k@@DC*6>xb>d2ml366R z*?8Vs+o+v&(*9&Dr26OUE_uX>I;Sx7 z>jVPgp&wvmqdO-NyCwbW0r8&JZ@i4Z(pwEbw_q1D1_m;(dRd-NTF|$i&V}#A@FeJ+ zJ|{oq2~PP(doT}{;{>IoIB<+nagMMERJ$@GQvjt3W?t$RWS6B*(&*xea99O=%} z1RcnaJKf;*oZ~;;BQJ4Mp1Sp%N=os=7QmpC_-~uoIt$m0lqpN!+FHc6S1=~)C5*$= z)Z{^2V?IhzS5)X$ApE|gaQ6HbZ95=`12*XTMW>xO+u}2Hz{fF&(&k|3>t|r@!Mn(> zV1Pj_!qWLu)|CN0KM&$E5S#g8w55JmognoYtt|0I7brKPuBWvPUXwksZpbHB5@AjE_3ip>ZP12=!DPc8Zr??e`&cEs&+?2iR& zmZ*rExWx<+77qeQOVHmj8Ee+|nAAYb4JlRD>$d`7@#3ttqoiTKTrx=>&G_t>eCA=Y zWX^Wcmnt!+0{XE(jZge}?_13P*W1j2Z}TdW84h5MC5_@Zhe>vR$lTpyjY0PqqXPZb zRv6x06wrV8RR+@?-)7kHf+`@&GeO^yEkb`ldbRMF!!S4#aOwx(;B|5Fmj?y#o_Il4)GUE zWVa;@h^2o)#DO_Cf5hhGsL>32h`)2K&DSC~mjg|wn)7N7NKm47hj04mlY#6cD@E-J z)8BDb(!4|73G5Nlxu*+e4)B$O(1fF~V?GlPo_>vrQXa5rK%n~c=MO4Z&nNNp1;PTjow-xHnGcZj~n~mdV zn@H&;jEpR&{DvqchTESipB>_PA}oTRNmb2qRQ`T^kdIcwu*L?NK9({pXtI!c8a*;F z%mcH*$TD^10XyE(CR7uYlvO?m5;2;cu8kqcVyDFdj`*)Wl0dB?jEsuSdtuKhf2m^$ zrFpqyF=iVCbhTq!Taj;l=(aS655JS}9ts&Iu1X14$L6IK`I7*)7&TB-cJ@HoR`krZ z0P6_^hlOV82M?@hUg|l~6h7AYkX?M!(FiyNHMK`$fr&RDyR*gXfCX)T)OYgA8pbZj zc^?)S)Rk2SX0vJ&mg_LWFb_ju8%b)Cs*$+OEre77PgI?!fRIvC>@CF}#ylAuVA$K& zeF;VIw_p%xcx2?|OZD|5rx({Ox8>GJ(u79J`1vxnDq6GbH#{QUWL@7{_?M^M@K>2Z z8WQ^nszBJ;+DiOz9TXby(A7)k6!868*C>)wwG)qEV4GTO^*K8==f2-FLg#}>vYCun zK2d&>IqZCUZ&k$Oy82@&k*RF{@vOnZbUQsg>j9v{tcw|Gd8LOsnAyr*f~Y8=nC=95 z^qE~2xW7^~=qqrcD(GnztitM%d)%vf>F#)W_A6g&M(7zxpj+QfK>l!jf+r!G6)wpR#L>XdlT)fWt~yTiA#?)bQBd;_V;ucBs1&~6)(@s zp>U5ClE&<$Hv(v-vIA(b_okt2>l^eb`G=NBY*#;nk~GPmL6|tA*Z6U7#TkF4c;F}2 z$D;xpOOwoZ(yzRqab9Ab^*Xj4EDfqTzZYjes3-YrBX|3iKbnm;&d;K7k1(q>X(*|G zbZT!5mR93yt7OsT<{_}|I#wFnB>|HRIuT;xvccK-!$?;+e6argyqN$ zJJ+Wn<%r*YDN_9LPPH^9aB2M)CE9y(owLi$`5Lp&XtMG9qja+L4@XsxO z_k-KFT35EjHIKM1f4%ADe2jET~JTU#jNSi zdPb3XzRp-N4q~yS&7&)Wv+=Ns?BR`V`k^E?%@~lT$jy|k{o-|~(Ptx_UigScDl^N} zSC2dC=gzOi&nYfJvTkEa5grp=t12>>kb}`hj8yIut0Zz(k0V3=s5$ogk$o?h^7_9? zv8h}JO9La6?>;X^1+81&*%TaGKc-nTmV>-E;)AAhP|RDt9fGrWP^9TnUu!G%@43A# z|0>}uDdk)=GJ5f{95&Yu0+EWGZ!<4=ou(FUObQJeLjm3Vg*&gi|Bf*!j##IktDfRr zivPk&tDL%XbSp1dEy{^emc^`V*yUfNGlu?_&|TiBIK-*IW+4O7+gI}(MniMo0o;V6V|a}43s+EYc%%{ zcwCO&>rSE z+@$T?S{_g=YZFB?C!Glf|y*LzDpRA!>Lji%kO`fa>hVHtzKcE^2;FT!|B3mT#zm=<-Ai z7vV152n+2E3tFGk%p;-A8VuwE+{!u|ePeC;)^E~d1Wk;VKb2ON+S%AfHy0$6sYK;b%>ZreRm3)Iby0=+*QRl}w> z87gQH1Z@w#1=zv4c`DkMIde*Bfc z*;YrDBo*eaWs3*>ZZ@AVoy$2g%!10!y@o7q6Rlz&#T`|SLw<2wH*dwrgt`%T=wEK3 z`t>^?ICvwkP9xkUWNnc95cAzYp}gJ+X9bUM5^xSJxKZZ4fBI~$*+=b$JEWx6Ag^AG z>4k-oCrpYO5P)-uZkSkfz?lt6rQsBVCVw=)Ru5UHJ7Op5sQ3)^CWSycX%S&7948!& zVHcn1TO@Rhw>VlQEw!ai1rL5;5Wg5tr|8ImpVvYS+?t|hR|63kb>yTDWiZ70k)9fX zXZjo8?^Hv=W-xWt+imZk1i~qv0uN_`scBEi%yV99k!k z7Y%z23P<||(>62m<(+YQG3rhoMP*enC*)XnAIq<_Jgm+Pj#P-^neMcKXLXc{wAHn0 z7i9L9?kl`2>9}vrLnZ{o-fNlRdQj( z($XRCYw>602S99yznVilc>z?53YH2=wd89@yKY5tcjrg$YWc9>XsXtCqGLgH3($gv zoyaU0;+L`H#3rz|+_6V8o(nSaa5t&SLPEEfs5_S&Ad5L_Gn@Oae9|1D9jI}^XQ2?< z;HxNXz6j?hxoFH_jGxs^!9BX#HyFTd96_jb_`cA@z{xnI*zB}Rr<<1LpQrHjj)J=+ zh+X7n`$<7naqc{lK{$q!YrodP71L$?XWAr4#U^ET7g!~d={?7@aTBbq0TwZsFnc{I z=`Lk<^by>14FvIsO7dTRllW-A-xeD|825~GlE6f;R|TEfkl;(Jv(F0lZuDN8zDDu+6Cbk%%R zdwv(b-|~uIi=IiMQx>*ALj0b?-Dn1g#D$iJ&=&@C-t zNZ>)%Cc+F_5nIoHqA7?DML)YW=)3Yr4UnTvm#8ILD*t@n1 zZSP%vaamj2A2OdECG2-U{lh>RZJrD{{SzSGzJ2sNi3auuVM%|Xnwr+CINE|>UN~D}=Brq8`K5~>6o05I! zRX;MEARKP0n>zCT5`h|#yEwM50B5CC@IYg5lv4OkmMdo2W&ZHm`?+b!T@EwN$^y0E zXdK97%JAbSK2t3hH`A|X0ogHF8jOQM4i_O7Jm64a&;)J||3bs2R>M2xK*=aa2TN9j zO+v+40}%-z*hm_L5ml@q3g4^^(@%1)k1E0WcR1_E!t_9=LOI$x1G01}!NsE*=VSH6 z1l$w+$54(=wyn_y(Kfd>Q5Tn}Yqu*=S0Q4bPr0Z~t*yBh#YXm&3LAt~yn-tv{&`OHn z$@wi*weUwlVNn;tg%8|va}SPOLH3G}1O4|BIKaP^t$itn=P&(97WC9ihgL)y>#sI_ zl7T$1Q_>BO8tg{3!r}0#*kTw=#$mOI1*qrOXP=p&A=gR#kr`1xvmH^C1k1D_sty>; zC_ForB_iQRZPyuMF%h92eiHSAAF2QHubm{yJ)HAWW3o_xPL zSq>0o`K;kw0_gkdibY2{-rV@&KS52-F1)k!XJm5n`30`d0V?M2wVSEqh=>$=2H$MZ zuQpy!G~MNNbEV=$$d`isN)rgka&}oVBV+VT{ov|5$0G~Vj0$S+>QAk^3r_JSXWcc) z@He=bsQ8nGb!3CE=nV%SM)n1Jf&*RSTgd&*ed?Iu08XsQBuLg{;y6p((|AB|$It0N zd^K`O=p=r7z*Uhfx@-Z5lKw?@fS&YaVl2h=B2vxYz@}3V&3n!2CUS4F;{sAqW$m+C zEdjH=4=tE!!!83b5V60~75kr56jiGEc`X-iS!FF}0;?`)OGj|#N{`fb9MSha(yO01 zE+GG!N)@*ed_@>8g?KS!T5!Jn6{vTDgOlPqx_HWr2)SbNc8Wf`AK%$!HdIn6kARY4 z@@?i&I>q}AI4==Wxo`dHMEZDLLj?Gs-B7Dub%R;PZO5lB6~f)9h-76J{u`Eha4?HZ5}zPwlj;5C@M^*=U*CCe5a5qLl~n!u%quLh*7U+G`L(+yFDy!+ zjNn(2M)`|%TWrDn)gZ@r+{ee_rVSK=Ffv^-x01BOnA@^REL!9(OCR&joQuruvuMU8 zgKtD-YtmG>;kUuhv%Lb>4O0E*I=5@<-A#wNRE)A1w&o?!0?(R`jw4rhCga~i-sb(~*U~Q1wqL8TgU|-Jxy|jd&hfKhnN46OevQSI z&g4k2$Yq>NL5U|x9%L@@6twU>68sK}!sR&@21#6c_6_b)h?$*m0KL@`exe(yA(t)y4>s+m`+A}+Zqn2Oqgfc} ze;BN@&3SO%J72INl6|{w?5l7xHEow-@x(mb+UfoB>AB=hs$&TVRE_mV>;_C~dlv}X z+l8*U3OOO7wq)I~x z(*$6G=?=Sa)mIqCvXkqC8{xhJa~I`|I06=y7RBbA6fFjpBTnC1!zUiI_rm?9t898X zKX-aGhfO`&e24=0u=+D>PeZI|7>J$pSh-`Ox(h2pOH-OjO68GveydiV9Flzuuk95=ge}{ zZnW&PO_9ng1*6RHanWvsYb?bq(-;nBq#6q+|Khb`45}GYE&2^coR9AsG=>mA$zQi} zzA&{W;bcOjN|dvBEv5{+_is+gcDk1Ay=Q$ttyxH{9p~VX{&~Q{eX773*+=bFLgT~q zb4`T&6CE~-acnfdbc?JpuQTtxcVZS2tZ|GhPo4h@beXXDflsjB$`zWxPO;2k&6OjG z;M>Ldismeid8+YuuLa|vYRE>Hnp>?UC5zXT-Tv8zbCyt&VI?PGb+G&7^&llc69fOs<1-UljcQ0BD>( z#3@I_m0B-XaZMQ^5?;=|h}DKnE@hZV8cRjMUORVWQu4YNAR#3s0;ZyPA?1Y|eV)6p z_ZtDG`Yj$T$bg;w(U2Tr70g~sHL!TCRO?Z3Du7eQGWi^biYBgA#I;@C&kM2Yw?Y>5 zITB3QzfnJPmTl@JoEs%7eTh|ZDwSIp;T%bxnRZ!t$J25vEU4OEE#{Q${~-l z)bFbquf@p4Jxe$+NYXm8Im^%#8Wus(z(jVkHk!Ag|I;|n7)M6nz+>&xz!RvXahp=~ADZoJKP@HnFrW{4lx<&1^(D z9tr{(Eysu^BK^iKpnAgsxx$j%3vS*SdOeg~PftPSC(i|O9%swD75#e6mPAr{&R~xr zjz{(q*(H=jNAOqmwnh@aSCEOZsU~GFq8C2$JMJ&;8oq-9bsJk(O>^hfKJFrv%}v_| zB1$|yR6kZ#haD+WY#Vx!Nik!n$MlpPMriNt3e4WJxPjBX>whYOEp)rd)Q4lYKd?{J zxYLw9F|-+5Q?x^dsY1n8>G@i(xX3!*t~s`=gtk$FW}1!D8f?Pb(P|RlJQri|CECQT z5t+J<{gcvT_f=t-?K$iS)1`-IW;PzpXsaw35j5$L3rp1~Cz{8->J+-Tig6TBnPD zdoeU;&Z2kOY9&N)@MEZNx&>rg$%ax!j|_;!U5Lf!W;t2fd8MwB?gUhDz+|PwG4--y zrA0+-HrE#Hj*mr`AG7u3cr}bp`r3Zo^@IBvfkF&#v>aRDV13^{gs$@aViL^H>DpSs z8J*BPwNYkzV6YAU#$qp>C1EEJhAk7xB8HadlH~m!k8#;WNyNvy$h4N#B5Jb z?DB<0@8@ZiXzsJTewoz5&AlThNwvIu3s z#5jfEGmQJtJH72#8q+|r#SE=>fTnnVdD4LaJ#gBk-4Gy2hmo&1Dc7kpN!ZmdVHhlX zbUOtN5^R3b)=j<;qLNGR=W`I&M96CJTkm{CJ*&r$(y{M> z@~C^S4(h%P& zi#2TqfIy0*U-aag1c%JCMu#Kw*Y5*GUxog^#SZT5hxplr8oRC-xNdmIfeK(ss|d$i z;rjmRuI$jVrO&3z(AI`Uw?W^N@%MF9I1i>dv~o$1!oHT)i2NCL@5gMkK4gnpf6ieEF2utNG0L}uZ&Xc4L5zSH9I_fNx*>2#S!-HbaP(+0Ii?&(KP zO6!?vCngsq=gASL$Jjd=M4d8#UX~_5W)O?HrJDOCaK)>Y&VyPhMlXdoo*N?rVm&Hw zf>$FoWA^+6EFMo=fF3q!$Nrfd9KzA?u*?)1cyT()*mqI6vb}rI%DUCTF?Kw8Bz!vC zaFMP5>^XavuEv+=kBU3S^=ty9>af~BD-II1(_~5BceHG4vqhzEdDw6CVmg@S_FMb! zGIfJAhUNzQ#G`N|_a(P0aQF%~2P6UUX~l>!ucB+}KC5G;t78aR#1wYM{a;cFc5MqZ z>85lf#=Tj7czYAwSX5qGs_c7RI2-h6|L~^HeCWotWt08QC=7373EF%!9Yis7gf{6B z`8`I0rYj#GA=3WX0-Nx~GQCH^xz$^&JX1636@_9mxf-8x7Rbb}<1M31uv=#vNPW+* z!d~}x6R$=`#n903sGOu}JUd<7w$Pr%9!?^jbBEKMYVwj9UFGbp&&p8}46$ zoaQA{lbl65*Fh)ryV3*mVFJMhyNsGIuU+}(IFS{j&QDWoAWa7Ul(kp*^$MfTWPY0PrrzWLn5ElziYUXt~`L1U_7Hj_a`OQC&B_yl8&5 zi!KU!*YnEcG`+$%!^~<>zN09pY5)t1{BQq!hzzI%QDS*??tj2srS&(lsFa5IPPsza zgjRos(6rWYG0P3>0>?8B_{Olpdu{xUxEgRrumZ8qH4Bn_O&?EfDqG7zTOV}|sVeWU zhiKo7!^8!epPv+6%?Zhw81B00Kp;ml^efmWp^2R9%kZ8~7|`7vgYW!d%c@wY{S|9@ z+jhMMCt>zgaQTFxwy^r0GeorDBA_=ob*a~g5kCe7GFSZvGNWBj!= zN`OF2uegax=l)UXE7akw%9rB1_?H}bmVWa880wK#Z7)+l#lc;fhYnS|QjbVNX~zD> zER#6zfHCnLA^+d*bo6j>k7m!x}x?ST+Z)X(C=qG;rsrh z#fUyrti%j3Mc=$vX|n}A5U+us6l%W8qm0R^Io)(y)Tc^>*$Ry(OF2zNrdtL54{<7C z&p&x$ybn%f5>5X)zd0zV`S`4V zny*ZS_>!T!mvn$MSshU#BEX1vc8t`XaG!`HQZe!uf9$`P`PZ$>QToa=b2sVVE!G4r zz#p8jx58#J;*c`7d3t;+wfcy}G?|;#h8$?hg5WH#ha#CcYkIrc^mtpRh_ih;NIW^e z!1>IuO`#7?M>F_Cj`1SdKpgq)eF@opmGYLO`0ZYf9+Gc{W=cQGX3E*x8zF6%qOsRV zp5KW%+qHT-W714pwRwiCMI$pG(a-bb@hpNmV6D}~dhJ_dS_LzEgG!(xajyM2L1b;z zqp76_Pbdx`Q_!z-CoUhMAQ~2eAFX^e_oz3kfuj&GuLQL%TPvX=wq*HLQsJ| bFYpXZa^goDzqnxc>i`sG)nqEAOuzjf>K7hx From 00baa3c1dd9ba8b38a588c7dd1ca53fe5d666550 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 14:08:40 -0800 Subject: [PATCH 286/865] Hide archived projects only on workboards, not hovercards Summary: See PHI225. Previously, see D15335 / T10413. On workboards, we hide archived project tags since they aren't terribly useful in that context, at least most of the time. Originally, see T10349#159916 and D15297. However, hovercards reuse this display logic, and it's inconsistent/confusing to hide them there, since the actual "Tags" elements on task pages show them. Narrow the scope of this rule. Test Plan: - Viewed a hovercard for a task with an archived project tagged, saw archived project. - Viewed a workboard for the same task, saw only unarchived projects other than the current board tagged (this behavior is unchanged). Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18783 --- .../PhabricatorBoardRenderingEngine.php | 4 +++- .../project/view/ProjectBoardTaskCard.php | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/applications/project/engine/PhabricatorBoardRenderingEngine.php b/src/applications/project/engine/PhabricatorBoardRenderingEngine.php index ca8b0633da..d76497bc21 100644 --- a/src/applications/project/engine/PhabricatorBoardRenderingEngine.php +++ b/src/applications/project/engine/PhabricatorBoardRenderingEngine.php @@ -67,7 +67,9 @@ public function renderCard($phid) { $project_phids = $object->getProjectPHIDs(); $project_handles = array_select_keys($this->handles, $project_phids); if ($project_handles) { - $card->setProjectHandles($project_handles); + $card + ->setHideArchivedProjects(true) + ->setProjectHandles($project_handles); } $cover_phid = $object->getCoverImageThumbnailPHID(); diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index 1a6807ec45..3a7016ca74 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -8,6 +8,7 @@ final class ProjectBoardTaskCard extends Phobject { private $owner; private $canEdit; private $coverImageFile; + private $hideArchivedProjects; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -35,6 +36,15 @@ public function getCoverImageFile() { return $this->coverImageFile; } + public function setHideArchivedProjects($hide_archived_projects) { + $this->hideArchivedProjects = $hide_archived_projects; + return $this; + } + + public function getHideArchivedProjects() { + return $this->hideArchivedProjects; + } + public function setTask(ManiphestTask $task) { $this->task = $task; return $this; @@ -126,10 +136,12 @@ public function getItem() { $project_handles = $this->getProjectHandles(); // Remove any archived projects from the list. - if ($project_handles) { - foreach ($project_handles as $key => $handle) { - if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { - unset($project_handles[$key]); + if ($this->hideArchivedProjects) { + if ($project_handles) { + foreach ($project_handles as $key => $handle) { + if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { + unset($project_handles[$key]); + } } } } From 3d742eb50baf54433e41090d1ba9d06277fedebc Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:10:33 -0800 Subject: [PATCH 287/865] Lightly modernize LegalpadDocumentQuery Summary: Ref T13024. Updates LegalpadDocumentQuery to use slightly more modern constructions. Test Plan: Queried for Legalpad documents, got the same results. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18785 --- .../legalpad/query/LegalpadDocumentQuery.php | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/src/applications/legalpad/query/LegalpadDocumentQuery.php b/src/applications/legalpad/query/LegalpadDocumentQuery.php index 655da955e4..3d79e9f3a1 100644 --- a/src/applications/legalpad/query/LegalpadDocumentQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentQuery.php @@ -77,23 +77,12 @@ public function needViewerSignatures($need) { return $this; } - protected function loadPage() { - $table = new LegalpadDocument(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT d.* FROM %T d %Q %Q %Q %Q %Q', - $table->getTableName(), - $this->buildJoinClause($conn_r), - $this->buildWhereClause($conn_r), - $this->buildGroupClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - $documents = $table->loadAllFromArray($data); + public function newResultObject() { + return new LegalpadDocument(); + } - return $documents; + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $documents) { @@ -134,12 +123,12 @@ protected function willFilterPage(array $documents) { return $documents; } - protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { - $joins = array(); + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); if ($this->contributorPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN edge contributor ON contributor.src = d.phid AND contributor.type = %d', PhabricatorObjectHasContributorEdgeType::EDGECONST); @@ -147,79 +136,81 @@ protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { if ($this->signerPHIDs !== null) { $joins[] = qsprintf( - $conn_r, + $conn, 'JOIN %T signer ON signer.documentPHID = d.phid AND signer.signerPHID IN (%Ls)', id(new LegalpadDocumentSignature())->getTableName(), $this->signerPHIDs); } - return implode(' ', $joins); + return $joins; } - protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - if ($this->contributorPHIDs || $this->signerPHIDs) { - return 'GROUP BY d.id'; - } else { - return ''; + protected function shouldGroupQueryResultRows() { + if ($this->contributorPHIDs) { + return true; } + + if ($this->signerPHIDs) { + return true; + } + + return parent::shouldGroupQueryResultRows(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.phid IN (%Ls)', $this->phids); } if ($this->creatorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.creatorPHID IN (%Ls)', $this->creatorPHIDs); } if ($this->dateCreatedAfter !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->contributorPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'contributor.dst IN (%Ls)', $this->contributorPHIDs); } if ($this->signatureRequired !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'd.requireSignature = %d', $this->signatureRequired); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } private function loadDocumentBodies(array $documents) { @@ -275,4 +266,8 @@ public function getQueryApplicationClass() { return 'PhabricatorLegalpadApplication'; } + protected function getPrimaryTableAlias() { + return 'd'; + } + } From 71406ca93bf4dbda8a3e0afd0115ed5e28a6c120 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:42:40 -0800 Subject: [PATCH 288/865] Lightly modernize LegalpadDocumentSearchEngine Summary: Depends on D18785. Ref T13024. While I'm in here, update this a bit to use the newer stuff. Test Plan: Searched for Legalpad documents, saw more modern support (subscribers, order by, viewer()). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18786 --- .../query/LegalpadDocumentSearchEngine.php | 136 ++++++------------ 1 file changed, 46 insertions(+), 90 deletions(-) diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php index 8b218cf821..1b608b02e3 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php @@ -11,105 +11,66 @@ public function getApplicationClassName() { return 'PhabricatorLegalpadApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - $saved->setParameter( - 'creatorPHIDs', - $this->readUsersFromRequest($request, 'creators')); - - $saved->setParameter( - 'contributorPHIDs', - $this->readUsersFromRequest($request, 'contributors')); - - $saved->setParameter( - 'withViewerSignature', - $request->getBool('withViewerSignature')); - - $saved->setParameter('createdStart', $request->getStr('createdStart')); - $saved->setParameter('createdEnd', $request->getStr('createdEnd')); + public function newQuery() { + return id(new LegalpadDocumentQuery()) + ->needViewerSignatures(true); + } - return $saved; + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Signed By')) + ->setKey('signerPHIDs') + ->setAliases(array('signer', 'signers', 'signerPHID')) + ->setDescription( + pht('Search for documents signed by given users.')), + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Creators')) + ->setKey('creatorPHIDs') + ->setAliases(array('creator', 'creators', 'creatorPHID')) + ->setDescription( + pht('Search for documents with given creators.')), + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Contributors')) + ->setKey('contributorPHIDs') + ->setAliases(array('contributor', 'contributors', 'contributorPHID')) + ->setDescription( + pht('Search for documents with given contributors.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd'), + ); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new LegalpadDocumentQuery()) - ->needViewerSignatures(true); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - $creator_phids = $saved->getParameter('creatorPHIDs', array()); - if ($creator_phids) { - $query->withCreatorPHIDs($creator_phids); + if ($map['signerPHIDs']) { + $query->withSignerPHIDs($map['signerPHIDs']); } - $contributor_phids = $saved->getParameter('contributorPHIDs', array()); - if ($contributor_phids) { - $query->withContributorPHIDs($contributor_phids); + if ($map['contributorPHIDs']) { + $query->withContributorPHIDs($map['creatorPHIDs']); } - if ($saved->getParameter('withViewerSignature')) { - $viewer_phid = $this->requireViewer()->getPHID(); - if ($viewer_phid) { - $query->withSignerPHIDs(array($viewer_phid)); - } + if ($map['creatorPHIDs']) { + $query->withCreatorPHIDs($map['creatorPHIDs']); } - $start = $this->parseDateTime($saved->getParameter('createdStart')); - $end = $this->parseDateTime($saved->getParameter('createdEnd')); - - if ($start) { - $query->withDateCreatedAfter($start); + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); } - if ($end) { - $query->withDateCreatedBefore($end); + if ($map['createdEnd']) { + $query->withDateCreatedAfter($map['createdStart']); } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $creator_phids = $saved_query->getParameter('creatorPHIDs', array()); - $contributor_phids = $saved_query->getParameter( - 'contributorPHIDs', array()); - - $viewer_signature = $saved_query->getParameter('withViewerSignature'); - if (!$this->requireViewer()->getPHID()) { - $viewer_signature = false; - } - - $form - ->appendChild( - id(new AphrontFormCheckboxControl()) - ->addCheckbox( - 'withViewerSignature', - 1, - pht('Show only documents I have signed.'), - $viewer_signature) - ->setDisabled(!$this->requireViewer()->getPHID())) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('creators') - ->setLabel(pht('Creators')) - ->setValue($creator_phids)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('contributors') - ->setLabel(pht('Contributors')) - ->setValue($contributor_phids)); - - $this->buildDateRange( - $form, - $saved_query, - 'createdStart', - pht('Created After'), - 'createdEnd', - pht('Created Before')); - } - protected function getURI($path) { return '/legalpad/'.$path; } @@ -130,10 +91,11 @@ public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); + $viewer = $this->requireViewer(); + switch ($query_key) { case 'signed': - return $query - ->setParameter('withViewerSignature', true); + return $query->setParameter('signerPHIDs', array($viewer->getPHID())); case 'all': return $query; } @@ -141,12 +103,6 @@ public function buildSavedQueryFromBuiltin($query_key) { return parent::buildSavedQueryFromBuiltin($query_key); } - protected function getRequiredHandlePHIDsForResultList( - array $documents, - PhabricatorSavedQuery $query) { - return array(); - } - protected function renderResultList( array $documents, PhabricatorSavedQuery $query, From ba4b9f71849f501fc95a204f16de7db871a3ce5a Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:10:38 -0800 Subject: [PATCH 289/865] Refactor on-login Legalpad document signature requirement Summary: Depends on D18786. Ref T13024. I'm going to change the order this occurs in, but move it to a separate method and clean it up a little first. Test Plan: Added a new document as required, reloaded, signed it, got logged into a session. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18788 --- .../base/controller/PhabricatorController.php | 112 ++++++++++++------ 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index f923eb7b73..961e85effe 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -227,45 +227,9 @@ public function willBeginExecution() { } - if (!$this->shouldAllowLegallyNonCompliantUsers()) { - $legalpad_class = 'PhabricatorLegalpadApplication'; - $legalpad = id(new PhabricatorApplicationQuery()) - ->setViewer($user) - ->withClasses(array($legalpad_class)) - ->withInstalled(true) - ->execute(); - $legalpad = head($legalpad); - - $doc_query = id(new LegalpadDocumentQuery()) - ->setViewer($user) - ->withSignatureRequired(1) - ->needViewerSignatures(true); - - if ($user->hasSession() && - !$user->getSession()->getIsPartial() && - !$user->getSession()->getSignedLegalpadDocuments() && - $user->isLoggedIn() && - $legalpad) { - - $sign_docs = $doc_query->execute(); - $must_sign_docs = array(); - foreach ($sign_docs as $sign_doc) { - if (!$sign_doc->getUserSignature($user->getPHID())) { - $must_sign_docs[] = $sign_doc; - } - } - if ($must_sign_docs) { - $controller = new LegalpadDocumentSignController(); - $this->getRequest()->setURIMap(array( - 'id' => head($must_sign_docs)->getID(), - )); - $this->setCurrentApplication($legalpad); - return $this->delegateToController($controller); - } else { - $engine = id(new PhabricatorAuthSessionEngine()) - ->signLegalpadDocuments($user, $sign_docs); - } - } + $result = $this->requireLegalpadSignatures(); + if ($result !== null) { + return $result; } // NOTE: We do this last so that users get a login page instead of a 403 @@ -558,6 +522,76 @@ public function buildApplicationCrumbsForEditEngine() { return $this->buildApplicationCrumbs(); } + private function requireLegalpadSignatures() { + if ($this->shouldAllowLegallyNonCompliantUsers()) { + return null; + } + + $viewer = $this->getViewer(); + + if (!$viewer->hasSession()) { + return null; + } + + $session = $viewer->getSession(); + if ($session->getIsPartial()) { + // If the user hasn't made it through MFA yet, require they survive + // MFA first. + return null; + } + + if ($session->getSignedLegalpadDocuments()) { + return null; + } + + if (!$viewer->isLoggedIn()) { + return null; + } + + $legalpad_class = 'PhabricatorLegalpadApplication'; + $legalpad_installed = PhabricatorApplication::isClassInstalledForViewer( + $legalpad_class, + $viewer); + if (!$legalpad_installed) { + return null; + } + + $sign_docs = id(new LegalpadDocumentQuery()) + ->setViewer($viewer) + ->withSignatureRequired(1) + ->needViewerSignatures(true) + ->execute(); + + $must_sign_docs = array(); + foreach ($sign_docs as $sign_doc) { + if (!$sign_doc->getUserSignature($viewer->getPHID())) { + $must_sign_docs[] = $sign_doc; + } + } + + if (!$must_sign_docs) { + // If nothing needs to be signed (either because there are no documents + // which require a signature, or because the user has already signed + // all of them) mark the session as good and continue. + $engine = id(new PhabricatorAuthSessionEngine()) + ->signLegalpadDocuments($viewer, $sign_docs); + + return null; + } + + $request = $this->getRequest(); + $request->setURIMap( + array( + 'id' => head($must_sign_docs)->getID(), + )); + + $application = PhabricatorApplication::getByClass($legalpad_class); + $this->setCurrentApplication($application); + + $controller = new LegalpadDocumentSignController(); + return $this->delegateToController($controller); + } + /* -( Deprecated )--------------------------------------------------------- */ From e850bc6b957a8ac6c6faa02d57716ce5d60d42f6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 16:54:26 -0800 Subject: [PATCH 290/865] When requiring login signatures, order documents from oldest to newest Summary: Depends on D18788. Ref T13024. Currently, we prompt users to sign from newest to oldest. This seems less intuitive than oldest to newest. Test Plan: Dumped document order, saw it swap to oldest-first. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18789 --- src/applications/base/controller/PhabricatorController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 961e85effe..fb9c402083 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -560,6 +560,7 @@ private function requireLegalpadSignatures() { ->setViewer($viewer) ->withSignatureRequired(1) ->needViewerSignatures(true) + ->setOrder('oldest') ->execute(); $must_sign_docs = array(); From c7718d3a2126fe77da17467c933aa6bd95f59779 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:02:09 -0800 Subject: [PATCH 291/865] Ask users to sign Legalpad documents before requiring they enroll in MFA Summary: Depends on D18789. Ref T13024. See PHI223. Currently, if `security.require-multi-factor-auth` and Legalpad "Signature Required" documents are //both// set, it's not possible to survive account registration, since MFA is requiried to sign and signatures are required to add MFA. Instead, check for signatures before requiring MFA enrollment. This makes logical sense, since it's silly to add MFA if you don't agree to a Terms of Service or whatever. (Note that if you already have MFA, we prompt for that first, before either of these steps, which also makes sense.) Test Plan: Configured `security.require-multi-factor-auth`. Added a signature-required document. Loaded a page as a new user. Went through signature workflow, then through the MFA enrollment workflow. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18790 --- .../base/controller/PhabricatorController.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index fb9c402083..09aa24a42e 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -160,6 +160,15 @@ public function willBeginExecution() { } } + // Require users sign Legalpad documents before we check if they have + // MFA. If we don't do this, they can get stuck in a state where they + // can't add MFA until they sign, and can't sign until they add MFA. + // See T13024 and PHI223. + $result = $this->requireLegalpadSignatures(); + if ($result !== null) { + return $result; + } + // Check if the user needs to configure MFA. $need_mfa = $this->shouldRequireMultiFactorEnrollment(); $have_mfa = $user->getIsEnrolledInMultiFactor(); @@ -226,12 +235,6 @@ public function willBeginExecution() { } } - - $result = $this->requireLegalpadSignatures(); - if ($result !== null) { - return $result; - } - // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { @@ -523,6 +526,10 @@ public function buildApplicationCrumbsForEditEngine() { } private function requireLegalpadSignatures() { + if (!$this->shouldRequireLogin()) { + return null; + } + if ($this->shouldAllowLegallyNonCompliantUsers()) { return null; } From ab0f61aa323d3557b6eff806f257a53e2ca5e759 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:16:09 -0800 Subject: [PATCH 292/865] Tell users to "Wait Patiently" for admin account verification later in the registration process Summary: Depends on D18790. Ref T13024. Fixes T8335. Currently, "unapproved" and "disabled" users are bundled together. This prevents users from completing some registration steps (verification, legalpad documents, MFA enrollment) before approval. Separate approval out and move it to the end so users can do all the required enrollment stuff on their end before we roadblock them. Test Plan: Required approval, email verification, signatures, and MFA. Registered an account. Verified email, signed documents, enrolled in MFA, and then got prompted to wait for approval. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024, T8335 Differential Revision: https://secure.phabricator.com/D18791 --- .../PhabricatorAuthNeedsMultiFactorController.php | 12 ++++++++++++ .../base/controller/PhabricatorController.php | 15 +++++++++++---- .../PhabricatorAccessControlTestCase.php | 6 +++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php index f3e2562a1c..d295812d3e 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php @@ -9,9 +9,21 @@ public function shouldRequireMultiFactorEnrollment() { return false; } + public function shouldRequireEnabledUser() { + // Users who haven't been approved yet are allowed to enroll in MFA. We'll + // kick disabled users out later. + return false; + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); + if ($viewer->getIsDisabled()) { + // We allowed unapproved and disabled users to hit this controller, but + // want to kick out disabled users now. + return new Aphront400Response(); + } + $panel = id(new PhabricatorMultiFactorSettingsPanel()) ->setUser($viewer) ->setViewer($viewer) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 09aa24a42e..0fee880378 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -137,10 +137,6 @@ public function willBeginExecution() { } if ($this->shouldRequireEnabledUser()) { - if ($user->isLoggedIn() && !$user->getIsApproved()) { - $controller = new PhabricatorAuthNeedsApprovalController(); - return $this->delegateToController($controller); - } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); @@ -233,6 +229,17 @@ public function willBeginExecution() { ->withPHIDs(array($application->getPHID())) ->executeOne(); } + + // If users need approval, require they wait here. We do this near the + // end so they can take other actions (like verifying email, signing + // documents, and enrolling in MFA) while waiting for an admin to take a + // look at things. See T13024 for more discussion. + if ($this->shouldRequireEnabledUser()) { + if ($user->isLoggedIn() && !$user->getIsApproved()) { + $controller = new PhabricatorAuthNeedsApprovalController(); + return $this->delegateToController($controller); + } + } } // NOTE: We do this last so that users get a login page instead of a 403 diff --git a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php index b6c268d88c..98fa948722 100644 --- a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php +++ b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php @@ -159,10 +159,10 @@ public function testControllerAccessControls() { $u_unverified, $u_admin, $u_public, + $u_notapproved, ), array( $u_disabled, - $u_notapproved, )); @@ -224,7 +224,7 @@ public function testControllerAccessControls() { )); $this->checkAccess( - pht('Application Controller'), + pht('Application Controller, No Login Required'), id(clone $app_controller)->setConfig('login', false), $request, array( @@ -232,10 +232,10 @@ public function testControllerAccessControls() { $u_unverified, $u_admin, $u_public, + $u_notapproved, ), array( $u_disabled, - $u_notapproved, )); } From dc62d18b473624a2547efb5751f2713ce0047256 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:34:43 -0800 Subject: [PATCH 293/865] Allow MFA enrollment before email verification Summary: Depends on D18791. Ref T13024. This clears up another initialization order issue, where an unverified address could prevent MFA enrollment. Test Plan: Configured both verification required and MFA required, clicked "Add Factor", got a dialog for the workflow. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18792 --- .../PhabricatorAuthNeedsMultiFactorController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php index d295812d3e..27e03485ca 100644 --- a/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php +++ b/src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php @@ -15,6 +15,12 @@ public function shouldRequireEnabledUser() { return false; } + public function shouldRequireEmailVerification() { + // Users who haven't verified their email addresses yet can still enroll + // in MFA. + return false; + } + public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); From e919233b3182d55b1d257df3b8ec369d702fff97 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 17:53:58 -0800 Subject: [PATCH 294/865] Don't show personalized menu items until users establish a full session Summary: Depends on D18792. Fixes T13024. Fixes T89198. Currently, when users are logging in initially (for example, need to enter MFA) we show more menu items than we should. Notably, we may show some personalized/private account details, like the number of unread notifications (probably not relevant) or a user's saved queries (possibly sensitive). At best these are misleading (they won't work yet) and there's an outside possibility they leak a little bit of private data. Instead, nuke everything except "Log Out" when users have partial sessions. Test Plan: Hit a partial session (MFA required, email verification required) and looked at the menu. Only saw "Log Out". {F5297713} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18793 --- .../PhabricatorFileDataController.php | 4 ++ .../PeopleMainMenuBarExtension.php | 72 ++++++++++--------- .../menu/PhabricatorMainMenuBarExtension.php | 14 ++++ .../page/menu/PhabricatorMainMenuView.php | 58 +++++++++++++-- 4 files changed, 111 insertions(+), 37 deletions(-) diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php index da438730cd..98f2cdbd51 100644 --- a/src/applications/files/controller/PhabricatorFileDataController.php +++ b/src/applications/files/controller/PhabricatorFileDataController.php @@ -10,6 +10,10 @@ public function shouldRequireLogin() { return false; } + public function shouldAllowPartialSessions() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $this->phid = $request->getURIData('phid'); diff --git a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php index cd1cffa7a2..01f1ba7cc1 100644 --- a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php +++ b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php @@ -9,6 +9,10 @@ public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { return $viewer->isLoggedIn(); } + public function shouldAllowPartialSessions() { + return true; + } + public function getExtensionOrder() { return 1200; } @@ -65,42 +69,44 @@ private function newDropdown( $view = id(new PhabricatorActionListView()) ->setViewer($viewer); - $view->addAction( - id(new PhabricatorActionView()) - ->appendChild($user_view)); - - $view->addAction( - id(new PhabricatorActionView()) - ->setType(PhabricatorActionView::TYPE_DIVIDER)); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Profile')) - ->setHref('/p/'.$viewer->getUsername().'/')); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Settings')) - ->setHref('/settings/user/'.$viewer->getUsername().'/')); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Manage')) - ->setHref('/people/manage/'.$viewer->getID().'/')); - - if ($application) { - $help_links = $application->getHelpMenuItems($viewer); - if ($help_links) { - foreach ($help_links as $link) { - $view->addAction($link); + if ($this->getIsFullSession()) { + $view->addAction( + id(new PhabricatorActionView()) + ->appendChild($user_view)); + + $view->addAction( + id(new PhabricatorActionView()) + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Profile')) + ->setHref('/p/'.$viewer->getUsername().'/')); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Settings')) + ->setHref('/settings/user/'.$viewer->getUsername().'/')); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage')) + ->setHref('/people/manage/'.$viewer->getID().'/')); + + if ($application) { + $help_links = $application->getHelpMenuItems($viewer); + if ($help_links) { + foreach ($help_links as $link) { + $view->addAction($link); + } } } - } - $view->addAction( - id(new PhabricatorActionView()) - ->addSigil('logout-item') - ->setType(PhabricatorActionView::TYPE_DIVIDER)); + $view->addAction( + id(new PhabricatorActionView()) + ->addSigil('logout-item') + ->setType(PhabricatorActionView::TYPE_DIVIDER)); + } $view->addAction( id(new PhabricatorActionView()) diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php index fede2fdb85..d893cccac9 100644 --- a/src/view/page/menu/PhabricatorMainMenuBarExtension.php +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -5,6 +5,7 @@ abstract class PhabricatorMainMenuBarExtension extends Phobject { private $viewer; private $application; private $controller; + private $isFullSession; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -33,6 +34,15 @@ public function getController() { return $this->controller; } + public function setIsFullSession($is_full_session) { + $this->isFullSession = $is_full_session; + return $this; + } + + public function getIsFullSession() { + return $this->isFullSession; + } + final public function getExtensionKey() { return $this->getPhobjectClassConstant('MAINMENUBARKEY'); } @@ -41,6 +51,10 @@ public function isExtensionEnabled() { return true; } + public function shouldAllowPartialSessions() { + return false; + } + public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { if (!$viewer->isLoggedIn()) { return false; diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index f9e4032d87..ba4bda41ea 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -46,7 +46,9 @@ public function render() { $app_button = ''; $aural = null; - if ($viewer->isLoggedIn() && $viewer->isUserActivated()) { + $is_full = $this->isFullSession($viewer); + + if ($is_full) { list($menu, $dropdowns, $aural) = $this->renderNotificationMenu(); if (array_filter($menu)) { $alerts[] = $menu; @@ -54,14 +56,18 @@ public function render() { $menu_bar = array_merge($menu_bar, $dropdowns); $app_button = $this->renderApplicationMenuButton(); $search_button = $this->renderSearchMenuButton($header_id); - } else { + } else if (!$viewer->isLoggedIn()) { $app_button = $this->renderApplicationMenuButton(); if (PhabricatorEnv::getEnvConfig('policy.allow-public')) { $search_button = $this->renderSearchMenuButton($header_id); } } - $search_menu = $this->renderPhabricatorSearchMenu(); + if ($search_button) { + $search_menu = $this->renderPhabricatorSearchMenu(); + } else { + $search_menu = null; + } if ($alerts) { $alerts = javelin_tag( @@ -84,7 +90,9 @@ public function render() { $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { - $extension->setViewer($viewer); + $extension + ->setViewer($viewer) + ->setIsFullSession($is_full); $controller = $this->getController(); if ($controller) { @@ -96,6 +104,14 @@ public function render() { } } + if (!$is_full) { + foreach ($extensions as $key => $extension) { + if (!$extension->shouldAllowPartialSessions()) { + unset($extensions[$key]); + } + } + } + foreach ($extensions as $key => $extension) { if (!$extension->isExtensionEnabledForViewer($extension->getViewer())) { unset($extensions[$key]); @@ -677,4 +693,38 @@ private function renderNotificationMenu() { ); } + private function isFullSession(PhabricatorUser $viewer) { + if (!$viewer->isLoggedIn()) { + return false; + } + + if (!$viewer->isUserActivated()) { + return false; + } + + if (!$viewer->hasSession()) { + return false; + } + + $session = $viewer->getSession(); + if ($session->getIsPartial()) { + return false; + } + + if (!$session->getSignedLegalpadDocuments()) { + return false; + } + + $mfa_key = 'security.require-multi-factor-auth'; + $need_mfa = PhabricatorEnv::getEnvConfig($mfa_key); + if ($need_mfa) { + $have_mfa = $viewer->getIsEnrolledInMultiFactor(); + if (!$have_mfa) { + return false; + } + } + + return true; + } + } From c7d6fd198cc98cf8b860288031eb4274bdcd94e0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 27 Nov 2017 15:11:06 -0800 Subject: [PATCH 295/865] Support "Set X to" as an action in Herald for tokenizer/datasource custom fields Summary: See PHI173. Adds custom field support for Herald actions, and implements actions for "Datasource/Tokenizer" fields. The only action available for now is "set field to...". Other actions ("Add values", "Remove values") might make sense in the future for these fields, but there's currently no use case. For most other field types (text, select, checkbox, etc) only "Set to" makes sense. Test Plan: - Added a "datasource" custom field to the custom field definition in Config. - Added a "if field is empty, set field to default value X" rule to Herald. - Created a task with a nonempty field: no Herald trigger. - Created a task with an empty field: Herald fired. - Reviewed rule and transcripts for text strings. {F5297615} {F5297616} {F5297617} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18784 --- src/__phutil_library_map__.php | 4 + .../field/PhabricatorCustomField.php | 55 +++++++++ .../PhabricatorCustomFieldHeraldAction.php | 105 ++++++++++++++++++ ...habricatorCustomFieldHeraldActionGroup.php | 16 +++ ...habricatorStandardCustomFieldTokenizer.php | 36 ++++++ 5 files changed, 216 insertions(+) create mode 100644 src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php create mode 100644 src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 424d72dc72..c4890116cf 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2535,6 +2535,8 @@ 'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php', 'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php', 'PhabricatorCustomFieldFulltextEngineExtension' => 'infrastructure/customfield/engineextension/PhabricatorCustomFieldFulltextEngineExtension.php', + 'PhabricatorCustomFieldHeraldAction' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php', + 'PhabricatorCustomFieldHeraldActionGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php', 'PhabricatorCustomFieldHeraldField' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php', 'PhabricatorCustomFieldHeraldFieldGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldFieldGroup.php', 'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php', @@ -7868,6 +7870,8 @@ 'PhabricatorCustomFieldEditField' => 'PhabricatorEditField', 'PhabricatorCustomFieldEditType' => 'PhabricatorEditType', 'PhabricatorCustomFieldFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', + 'PhabricatorCustomFieldHeraldAction' => 'HeraldAction', + 'PhabricatorCustomFieldHeraldActionGroup' => 'HeraldActionGroup', 'PhabricatorCustomFieldHeraldField' => 'HeraldField', 'PhabricatorCustomFieldHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index fba299fc8b..f7f4403402 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -34,6 +34,7 @@ abstract class PhabricatorCustomField extends Phobject { const ROLE_CONDUIT = 'conduit'; const ROLE_HERALD = 'herald'; const ROLE_EDITENGINE = 'EditEngine'; + const ROLE_HERALDACTION = 'herald.action'; /* -( Building Applications with Custom Fields )--------------------------- */ @@ -293,6 +294,8 @@ public function shouldEnableForRole($role) { return $this->shouldAppearInTransactionMail(); case self::ROLE_HERALD: return $this->shouldAppearInHerald(); + case self::ROLE_HERALDACTION: + return $this->shouldAppearInHeraldActions(); case self::ROLE_EDITENGINE: return $this->shouldAppearInEditView() || $this->shouldAppearInEditEngine(); @@ -1476,4 +1479,56 @@ public function getHeraldDatasource() { } + public function shouldAppearInHeraldActions() { + if ($this->proxy) { + return $this->proxy->shouldAppearInHeraldActions(); + } + return false; + } + + + public function getHeraldActionName() { + if ($this->proxy) { + return $this->proxy->getHeraldActionName(); + } + + return null; + } + + + public function getHeraldActionStandardType() { + if ($this->proxy) { + return $this->proxy->getHeraldActionStandardType(); + } + + return null; + } + + + public function getHeraldActionDescription($value) { + if ($this->proxy) { + return $this->proxy->getHeraldActionDescription($value); + } + + return null; + } + + + public function getHeraldActionEffectDescription($value) { + if ($this->proxy) { + return $this->proxy->getHeraldActionEffectDescription($value); + } + + return null; + } + + + public function getHeraldActionDatasource() { + if ($this->proxy) { + return $this->proxy->getHeraldActionDatasource(); + } + + return null; + } + } diff --git a/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php new file mode 100644 index 0000000000..1d35ba59a2 --- /dev/null +++ b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldAction.php @@ -0,0 +1,105 @@ +customField = $custom_field; + return $this; + } + + public function getCustomField() { + return $this->customField; + } + + public function getActionGroupKey() { + return PhabricatorCustomFieldHeraldActionGroup::ACTIONGROUPKEY; + } + + public function supportsObject($object) { + return ($object instanceof PhabricatorCustomFieldInterface); + } + + public function supportsRuleType($rule_type) { + return true; + } + + public function getActionsForObject($object) { + $viewer = PhabricatorUser::getOmnipotentUser(); + $role = PhabricatorCustomField::ROLE_HERALDACTION; + + $field_list = PhabricatorCustomField::getObjectFields($object, $role) + ->setViewer($viewer) + ->readFieldsFromStorage($object); + + $map = array(); + foreach ($field_list->getFields() as $field) { + $key = $field->getFieldKey(); + $map[$key] = id(new self()) + ->setCustomField($field); + } + + return $map; + } + + public function applyEffect($object, HeraldEffect $effect) { + $field = $this->getCustomField(); + $value = $effect->getTarget(); + $adapter = $this->getAdapter(); + + $old_value = $field->getOldValueForApplicationTransactions(); + $new_value = id(clone $field) + ->setValueFromApplicationTransactions($value) + ->getValueForStorage(); + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) + ->setMetadataValue('customfield:key', $field->getFieldKey()) + ->setOldValue($old_value) + ->setNewValue($new_value); + + $adapter->queueTransaction($xaction); + + $this->logEffect(self::DO_SET_FIELD, $value); + } + + public function getHeraldActionName() { + return $this->getCustomField()->getHeraldActionName(); + } + + public function getHeraldActionStandardType() { + return $this->getCustomField()->getHeraldActionStandardType(); + } + + protected function getDatasource() { + return $this->getCustomField()->getHeraldActionDatasource(); + } + + public function renderActionDescription($value) { + return $this->getCustomField()->getHeraldActionDescription($value); + } + + protected function getActionEffectMap() { + return array( + self::DO_SET_FIELD => array( + 'icon' => 'fa-pencil', + 'color' => 'green', + 'name' => pht('Set Field Value'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_SET_FIELD: + return $this->getCustomField()->getHeraldActionEffectDescription($data); + } + } + + +} diff --git a/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php new file mode 100644 index 0000000000..b8f16ab202 --- /dev/null +++ b/src/infrastructure/customfield/herald/PhabricatorCustomFieldHeraldActionGroup.php @@ -0,0 +1,16 @@ +getFieldName()); + } + + public function getHeraldActionDescription($value) { + $list = $this->renderHeraldHandleList($value); + return pht('Set "%s" to: %s.', $this->getFieldName(), $list); + } + + public function getHeraldActionEffectDescription($value) { + return $this->renderHeraldHandleList($value); + } + + public function getHeraldActionStandardType() { + return HeraldAction::STANDARD_PHID_LIST; + } + + public function getHeraldActionDatasource() { + return $this->getDatasource(); + } + + private function renderHeraldHandleList($value) { + if (!is_array($value)) { + return pht('(Invalid List)'); + } else { + return $this->getViewer() + ->renderHandleList($value) + ->setAsInline(true) + ->render(); + } + } + } From 54f131dc4baad39f54c164ea57135151c4d38542 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 28 Nov 2017 10:42:16 -0800 Subject: [PATCH 296/865] Make "first broadcast" rules for Differential drafts more general Summary: See PHI228. Ref T2543. The current logic gets this slightly wrong: prototypes are off, you create a draft with `--draft`, then promote it with "Request Review". This misses both branches. Instead, test these conditions a little more broadly. We also need to store broadcast state since `getIsNewObject()` isn't good enough with this workflow. Test Plan: - With prototypes on and autopromotion, got a rich email after builds finished. - With prototypes off, got a rich email immediately. - With prototypes off and `--draft`, got a rich email after "Request Review". Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18801 --- .../editor/DifferentialTransactionEditor.php | 36 ++++++++++++++----- .../storage/DifferentialRevision.php | 10 ++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 7cea29359f..ae652ba0ec 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -10,6 +10,7 @@ final class DifferentialTransactionEditor private $hasReviewTransaction = false; private $affectedPaths; private $firstBroadcast = false; + private $wasDraft = false; public function getEditorApplicationClass() { return 'PhabricatorDifferentialApplication'; @@ -166,6 +167,8 @@ protected function expandTransactions( } } + $this->wasDraft = $object->isDraft(); + return parent::expandTransactions($object, $xactions); } @@ -1581,10 +1584,6 @@ protected function didApplyTransactions($object, array $xactions) { $this->setActingAsPHID($author_phid); } - // Mark this as the first broadcast we're sending about the revision - // so mail can generate specially. - $this->firstBroadcast = true; - $xaction = $object->getApplicationTransactionTemplate() ->setAuthorPHID($author_phid) ->setTransactionType( @@ -1612,12 +1611,31 @@ protected function didApplyTransactions($object, array $xactions) { $xactions[] = $xaction; } - } else { - // If this revision is being created into some state other than "Draft", - // this is the first broadcast and should include sections like "SUMMARY" - // and "TEST PLAN". - if ($this->getIsNewObject()) { + } + + // If the revision is new or was a draft, and is no longer a draft, we + // might be sending the first email about it. + + // This might mean it was created directly into a non-draft state, or + // it just automatically undrafted after builds finished, or a user + // explicitly promoted it out of the draft state with an action like + // "Request Review". + + // If we haven't sent any email about it yet, mark this email as the first + // email so the mail gets enriched with "SUMMARY" and "TEST PLAN". + + $is_new = $this->getIsNewObject(); + $was_draft = $this->wasDraft; + + if (!$object->isDraft() && ($was_draft || $is_new)) { + if (!$object->getHasBroadcast()) { + // Mark this as the first broadcast we're sending about the revision + // so mail can generate specially. $this->firstBroadcast = true; + + $object + ->setHasBroadcast(true) + ->save(); } } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index bf4bec0abc..73f22c91e4 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -60,6 +60,7 @@ final class DifferentialRevision extends DifferentialDAO const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; const PROPERTY_DRAFT_HOLD = 'draft.hold'; + const PROPERTY_HAS_BROADCAST = 'draft.broadcast'; public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -719,6 +720,15 @@ public function setHoldAsDraft($hold) { return $this->setProperty(self::PROPERTY_DRAFT_HOLD, $hold); } + public function getHasBroadcast() { + return $this->getProperty(self::PROPERTY_HAS_BROADCAST, false); + } + + public function setHasBroadcast($has_broadcast) { + return $this->setProperty(self::PROPERTY_HAS_BROADCAST, $has_broadcast); + } + + public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); From 1b76250f8995744c016c4b8149263e0bc4727b73 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 28 Nov 2017 11:05:15 -0800 Subject: [PATCH 297/865] Disallow "Accept" on drafts, allow "Resign" Summary: Depends on D18801. Ref T2543. See PHI229. I missed "Accept" before, but intended to disallow it (like "Reject") since I don't want drafts to be reviewable. However, "Resign" seems fine to allow? So let's allow that for now. Test Plan: Was no longer offered "Accept" on draft revisions. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T2543 Differential Revision: https://secure.phabricator.com/D18802 --- .../xaction/DifferentialRevisionAcceptTransaction.php | 5 +++++ .../xaction/DifferentialRevisionResignTransaction.php | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index d52e2dbeb7..190a6a4920 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -162,6 +162,11 @@ protected function validateAction($object, PhabricatorUser $viewer) { 'closed. Only open revisions can be accepted.')); } + if ($object->isDraft()) { + throw new Exception( + pht('You can not accept a draft revision.')); + } + $config_key = 'differential.allow-self-accept'; if (!PhabricatorEnv::getEnvConfig($config_key)) { if ($this->isViewerRevisionAuthor($object, $viewer)) { diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index 40a512202d..53b0b8da01 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -64,11 +64,6 @@ protected function validateAction($object, PhabricatorUser $viewer) { 'been closed. You can only resign from open revisions.')); } - if ($object->isDraft()) { - throw new Exception( - pht('You can not resign from a draft revision.')); - } - $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; if ($this->getViewerReviewerStatus($object, $viewer) == $resigned) { throw new Exception( From d8f2630d5c0f9eb5221964944e45b9197753a988 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Thu, 30 Nov 2017 15:07:49 +0000 Subject: [PATCH 298/865] Modernize QuickSearch typeahead Summary: Use ClassQuery to find datasources for the quick-search. Mostly, this allows extensions to add quicksearches. Test Plan: using `/typeahead/class/`, tested several search terms that make sense. Removed the tag interface from a datasource, which removed it from results. Reviewers: epriestley, amckinley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18760 --- src/__phutil_library_map__.php | 14 ++++++++++++++ .../DiffusionQuickSearchEngineExtension.php | 12 ++++++++++++ ...orQuickSearchApplicationEngineExtension.php | 11 +++++++++++ ...ricatorPeopleQuickSearchEngineExtension.php | 11 +++++++++++ .../ProjectQuickSearchEngineExtension.php | 11 +++++++++++ .../engine/PhabricatorQuickSearchEngine.php | 8 ++++++++ .../PhabricatorQuickSearchEngineExtension.php | 18 ++++++++++++++++++ .../typeahead/PhabricatorSearchDatasource.php | 10 ++-------- ...catorMonogramQuickSearchEngineExtension.php | 11 +++++++++++ 9 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php create mode 100644 src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php create mode 100644 src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php create mode 100644 src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php create mode 100644 src/applications/search/engine/PhabricatorQuickSearchEngine.php create mode 100644 src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php create mode 100644 src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c4890116cf..53bbf9a749 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -831,6 +831,7 @@ 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', 'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php', + 'DiffusionQuickSearchEngineExtension' => 'applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php', 'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php', 'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php', 'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php', @@ -3202,6 +3203,7 @@ 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php', 'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php', + 'PhabricatorMonogramQuickSearchEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php', 'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php', 'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php', 'PhabricatorMotivatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php', @@ -3525,6 +3527,7 @@ 'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php', 'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', + 'PhabricatorPeopleQuickSearchEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', @@ -3785,6 +3788,9 @@ 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', + 'PhabricatorQuickSearchApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php', + 'PhabricatorQuickSearchEngine' => 'applications/search/engine/PhabricatorQuickSearchEngine.php', + 'PhabricatorQuickSearchEngineExtension' => 'applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', @@ -4817,6 +4823,7 @@ 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', + 'ProjectQuickSearchEngineExtension' => 'applications/project/engineextension/ProjectQuickSearchEngineExtension.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', @@ -5880,6 +5887,7 @@ 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'DiffusionRawDiffQuery' => 'DiffusionFileFutureQuery', 'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionReadmeView' => 'DiffusionView', @@ -8612,6 +8620,7 @@ 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorModularTransactionType' => 'Phobject', + 'PhabricatorMonogramQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting', 'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting', 'PhabricatorMotivatorProfileMenuItem' => 'PhabricatorProfileMenuItem', @@ -9004,6 +9013,7 @@ 'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorPeopleQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -9319,6 +9329,9 @@ 'Phobject', 'Iterator', ), + 'PhabricatorQuickSearchApplicationEngineExtension' => 'PhabricatorQuickSearchEngineExtension', + 'PhabricatorQuickSearchEngine' => 'Phobject', + 'PhabricatorQuickSearchEngineExtension' => 'Phobject', 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', @@ -10605,6 +10618,7 @@ 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', + 'ProjectQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php new file mode 100644 index 0000000000..80c705b4f8 --- /dev/null +++ b/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php @@ -0,0 +1,12 @@ +setAncestorClass(__CLASS__) + ->execute(); + + $datasources = array(); + foreach ($extensions as $extension) { + $datasources[] = $extension->newQuickSearchDatasources(); + } + return array_mergev($datasources); + } +} diff --git a/src/applications/search/typeahead/PhabricatorSearchDatasource.php b/src/applications/search/typeahead/PhabricatorSearchDatasource.php index 2a17ad01eb..ed17a31185 100644 --- a/src/applications/search/typeahead/PhabricatorSearchDatasource.php +++ b/src/applications/search/typeahead/PhabricatorSearchDatasource.php @@ -16,14 +16,8 @@ public function getDatasourceApplicationClass() { } public function getComponentDatasources() { - $sources = array( - new PhabricatorPeopleDatasource(), - new PhabricatorProjectDatasource(), - new PhabricatorApplicationDatasource(), - new PhabricatorTypeaheadMonogramDatasource(), - new DiffusionRepositoryDatasource(), - new DiffusionSymbolDatasource(), - ); + $sources = id(new PhabricatorQuickSearchEngine()) + ->getAllDatasources(); // These results are always rendered in the full browse display mode, so // set the browse flag on all component sources. diff --git a/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php b/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php new file mode 100644 index 0000000000..078cb44685 --- /dev/null +++ b/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php @@ -0,0 +1,11 @@ + Date: Thu, 30 Nov 2017 08:13:10 -0800 Subject: [PATCH 299/865] Add an explicit warning in the Differential transaction log when users skip review Summary: Ref T10233. See PHI231. When users ignore the `arc land` prompt about bad revision states, make it explicitly clear in the transaction log that they broke the rules. You can currently figure this out by noticing that there's no "This revision is accepted and ready to land." message, but it's unrealistic to expect non-expert users to look for the //absence// of a message to indicate something, and this state change is often relevant. Test Plan: {F5302351} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T10233 Differential Revision: https://secure.phabricator.com/D18808 --- resources/celerity/map.php | 6 +-- src/__phutil_library_map__.php | 2 + .../DifferentialDiffExtractionEngine.php | 10 +++++ ...ferentialRevisionWrongStateTransaction.php | 41 +++++++++++++++++++ webroot/rsrc/css/phui/phui-timeline-view.css | 4 ++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 78ae8bd461..0ea66bec26 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '1a4e0c25', + 'core.pkg.css' => 'fdb27ef9', 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -176,7 +176,7 @@ 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => 'b4719c50', - 'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca', + 'rsrc/css/phui/phui-timeline-view.css' => 'e2ef62b1', 'rsrc/css/phui/phui-two-column-view.css' => '44ec4951', 'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5', 'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455', @@ -871,7 +871,7 @@ 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => 'b4719c50', 'phui-theme-css' => '9f261c6b', - 'phui-timeline-view-css' => 'f21db7ca', + 'phui-timeline-view-css' => 'e2ef62b1', 'phui-two-column-view-css' => '44ec4951', 'phui-workboard-color-css' => '783cdff5', 'phui-workboard-view-css' => '3bc85455', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 53bbf9a749..11be8a50f0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -594,6 +594,7 @@ 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/DifferentialRevisionUpdateHistoryView.php', 'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php', 'DifferentialRevisionVoidTransaction' => 'applications/differential/xaction/DifferentialRevisionVoidTransaction.php', + 'DifferentialRevisionWrongStateTransaction' => 'applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php', 'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php', 'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php', @@ -5647,6 +5648,7 @@ 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialRevisionVoidTransaction' => 'DifferentialRevisionTransactionType', + 'DifferentialRevisionWrongStateTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialStoredCustomField' => 'DifferentialCustomField', diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index 952c76c63f..c426c411e7 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -268,6 +268,16 @@ public function updateRevisionWithCommit( $xactions = array(); + // If the revision isn't closed or "Accepted", write a warning into the + // transaction log. This makes it more clear when users bend the rules. + if (!$revision->isClosed() && !$revision->isAccepted()) { + $wrong_type = DifferentialRevisionWrongStateTransaction::TRANSACTIONTYPE; + + $xactions[] = id(new DifferentialTransaction()) + ->setTransactionType($wrong_type) + ->setNewValue($revision->getModernRevisionStatus()); + } + $xactions[] = id(new DifferentialTransaction()) ->setTransactionType(DifferentialTransaction::TYPE_UPDATE) ->setIgnoreOnNoEffect(true) diff --git a/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php b/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php new file mode 100644 index 0000000000..e19e54199c --- /dev/null +++ b/src/applications/differential/xaction/DifferentialRevisionWrongStateTransaction.php @@ -0,0 +1,41 @@ +getNewValue(); + + $status = DifferentialRevisionStatus::newForStatus($new_value); + + return pht( + 'This revision was not accepted when it landed; it landed in state %s.', + $this->renderValue($status->getDisplayName())); + } + + public function getTitleForFeed() { + return null; + } +} diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index 33e0f8ed0c..b0ae9c0044 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -315,6 +315,10 @@ background-color: {$violet}; } +.phui-timeline-icon-fill-pink { + background-color: {$pink}; +} + .phui-timeline-icon-fill-grey { background-color: #888; } From f786c86a6a428b580ccd5f2ce8c5df3bf7a19fa8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 13:41:44 -0800 Subject: [PATCH 300/865] Don't require the "gd" extension be installed in order to run unit tests Summary: Ref T10405. If `gd` is not installed, a number of unit tests currently fail because they generate synthetic users and try to composite profile pictures for them. Instead, just fall back to a static default if `gd` is not available. Test Plan: Faked this pathway, created a new user, saw a default profile picture. Reviewers: amckinley, lpriestley, avivey Reviewed By: avivey Maniphest Tasks: T10405 Differential Revision: https://secure.phabricator.com/D18812 --- .../builtin/PhabricatorFilesComposeAvatarBuiltinFile.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php index 4e461ec3f5..fcd6f97601 100644 --- a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php +++ b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php @@ -58,6 +58,14 @@ public function loadBuiltinFileData() { } private function composeImage($color, $image, $border) { + // If we don't have the GD extension installed, just return a static + // default profile image rather than trying to compose a dynamic one. + if (!function_exists('imagecreatefromstring')) { + $root = dirname(phutil_get_library_root('phabricator')); + $default_path = $root.'/resources/builtin/profile.png'; + return Filesystem::readFile($default_path); + } + $color_const = hexdec(trim($color, '#')); $true_border = self::rgba2gd($border); $image_map = self::getImageMap(); From 14cc0abeb37fd411aceed31db22a820537264fe5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 12:13:07 -0800 Subject: [PATCH 301/865] Fix several safety issues with repository URIs Summary: See PHI234. Several issues here: - The warning about observing a repository in Read/Write mode checks the raw I/O type, not the effective I/O type. That means we can fail to warn if other URIs are set to "Default", and "Default" is "Read/Write" in practice. - There's just an actual typo which prevents the "Observe" version of this error from triggering properly. Additionally, add more forceful warnings that "Observe" and "Mirror" mean that you want to //replace// a repository with another one, not that we somehow merge branches selectively. It isn't necessarily obvious that "Observe" doesn't mean "merge/union", since the reasons it can't in the general case are somewhat subtle (conflicts between refs with the same names, detecting ref deletion). Test Plan: Read documentation. Hit the error locally by trying to "Observe" while in Read/Write mode: {F5302655} Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18810 --- .../diffusion/editor/DiffusionURIEditor.php | 4 ++-- src/docs/user/userguide/diffusion_uris.diviner | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 674efbc158..90ced865f0 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -347,11 +347,11 @@ protected function validateTransaction( continue; } - $io_type = $uri->getIoType(); + $io_type = $uri->getEffectiveIOType(); if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) { if ($no_readwrite) { - $readwite_conflict = $uri; + $readwrite_conflict = $uri; break; } } diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index 140948f55e..a19d38d3a5 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -239,6 +239,11 @@ You can not add a URI in Observe mode if an existing builtin URI is in authorities: the observed remote copy and the hosted local copy. Take the other URI out of //Read/Write// mode first. +WARNING: If you observe a remote repository, the entire state of the working +copy that Phabricator maintains will be deleted and replaced with the state of +the remote. If some changes are present only in Phabricator's working copy, +they will be unrecoverably destroyed. + **Mirror**: Phabricator will push any changes made to this repository to the remote URI, keeping a read-only mirror hosted at that URI up to date. @@ -251,6 +256,11 @@ It is possible to mirror a repository to another repository that is also hosted by Phabricator by adding that other repository's URI, although this is silly and probably very rarely of any use. +WARNING: If you mirror to a remote repository, the entire state of that remote +will be replaced with the state of the working copy Phabricator maintains. If +some changes are present only in the remote, they will be unrecoverably +destroyed. + **None**: Phabricator will not fetch changes from or push changes to this URI. For builtin URIs, it will not let users fetch changes from or push changes to this URI. From 5240cffd9c20c4f759b4ab2327ec4f6fbe2063ab Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 12:33:02 -0800 Subject: [PATCH 302/865] Fix an issue where Diffusion could fatal if the default branch was deleted Summary: See PHI234. In T12931 we improved the behavior of Diffusion when a repository's default branch is set to a branch that does not exist, but in T11823 the way refcursors work changed, and we can now get a cursor (just with no positions) back for a deleted branch. When we did, we didn't handle things gracefully. Test Plan: - Set default branch to a deleted branch, saw nice error instead of fatal. - Set default branch to a nonexistent branch which never existed, saw nice error. - Set default branch to existing "master", saw repository normally. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18811 --- .../controller/DiffusionRepositoryController.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index f64b72e1f8..35fce7f03d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -46,8 +46,20 @@ public function handleRequest(AphrontRequest $request) { ->withRepositoryPHIDs(array($repository->getPHID())) ->withRefTypes(array(PhabricatorRepositoryRefCursor::TYPE_BRANCH)) ->withRefNames(array($drequest->getBranch())) + ->needPositions(true) ->execute(); - if ($ref_cursors) { + + // It's possible that this branch previously existed, but has been + // deleted. Make sure we have valid cursor positions, not just cursors. + $any_positions = false; + foreach ($ref_cursors as $ref_cursor) { + if ($ref_cursor->getPositions()) { + $any_positions = true; + break; + } + } + + if ($any_positions) { // This is a valid branch, so we necessarily have some content. $page_has_content = true; } else { From 42034e6739eb2507839dda708963219c430c7e76 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 1 Dec 2017 18:16:26 +0000 Subject: [PATCH 303/865] Property list view on Diffusion commits should show build status but not Subscriptions, Projects, or Tokens Summary: Ref T13019, adds build status back to Diffusion commits Test Plan: Open a Diffusion commit that has a build status, property list view should show the build status, but not Subscriptions, Projects, or Tokens. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T13019 Differential Revision: https://secure.phabricator.com/D18813 --- .../diffusion/controller/DiffusionCommitController.php | 3 ++- .../project/events/PhabricatorProjectUIEventListener.php | 8 ++++++++ .../events/PhabricatorSubscriptionsUIEventListener.php | 8 ++++++++ .../tokens/event/PhabricatorTokenUIEventListener.php | 8 ++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index d7734856f0..cdce8702ca 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -438,7 +438,8 @@ private function buildPropertyListView( $repository = $drequest->getRepository(); $view = id(new PHUIPropertyListView()) - ->setUser($this->getRequest()->getUser()); + ->setUser($this->getRequest()->getUser()) + ->setObject($commit); $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) diff --git a/src/applications/project/events/PhabricatorProjectUIEventListener.php b/src/applications/project/events/PhabricatorProjectUIEventListener.php index afbc0aab4e..104084bbf7 100644 --- a/src/applications/project/events/PhabricatorProjectUIEventListener.php +++ b/src/applications/project/events/PhabricatorProjectUIEventListener.php @@ -8,8 +8,16 @@ public function register() { } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php index 09b26819c0..5f371d69f3 100644 --- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php +++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php @@ -9,11 +9,19 @@ public function register() { } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: $this->handleActionEvent($event); break; case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } diff --git a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php index bbf3438b62..047c5504bf 100644 --- a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php +++ b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php @@ -9,11 +9,19 @@ public function register() { } public function handleEvent(PhutilEvent $event) { + $object = $event->getValue('object'); + switch ($event->getType()) { case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS: $this->handleActionEvent($event); break; case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES: + // Hacky solution so that property list view on Diffusion + // commits shows build status, but not Projects, Subscriptions, + // or Tokens. + if ($object instanceof PhabricatorRepositoryCommit) { + return; + } $this->handlePropertyEvent($event); break; } From c924351a58e40fe391c587899e322be006e98c7f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Dec 2017 16:11:58 -0800 Subject: [PATCH 304/865] Mark sessions as "signed all documents" when Legalpad has been uninstalled See PHI238. When an install uninstalls "Legalpad", we were incorrectly failing to mark sessions as "Signed All Required Documents" by bailing early. Test Plan: Uninstalled Legalpad, logged in. --- .../base/controller/PhabricatorController.php | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 0fee880378..5952a806ed 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -562,25 +562,25 @@ private function requireLegalpadSignatures() { return null; } + $must_sign_docs = array(); + $sign_docs = array(); + $legalpad_class = 'PhabricatorLegalpadApplication'; $legalpad_installed = PhabricatorApplication::isClassInstalledForViewer( $legalpad_class, $viewer); - if (!$legalpad_installed) { - return null; - } - - $sign_docs = id(new LegalpadDocumentQuery()) - ->setViewer($viewer) - ->withSignatureRequired(1) - ->needViewerSignatures(true) - ->setOrder('oldest') - ->execute(); - - $must_sign_docs = array(); - foreach ($sign_docs as $sign_doc) { - if (!$sign_doc->getUserSignature($viewer->getPHID())) { - $must_sign_docs[] = $sign_doc; + if ($legalpad_installed) { + $sign_docs = id(new LegalpadDocumentQuery()) + ->setViewer($viewer) + ->withSignatureRequired(1) + ->needViewerSignatures(true) + ->setOrder('oldest') + ->execute(); + + foreach ($sign_docs as $sign_doc) { + if (!$sign_doc->getUserSignature($viewer->getPHID())) { + $must_sign_docs[] = $sign_doc; + } } } From 9fdeb2d8ac95bddfb918f6541ba1f0fbcd5b0c24 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Dec 2017 16:11:58 -0800 Subject: [PATCH 305/865] (stable) Mark sessions as "signed all documents" when Legalpad has been uninstalled See PHI238. When an install uninstalls "Legalpad", we were incorrectly failing to mark sessions as "Signed All Required Documents" by bailing early. Test Plan: Uninstalled Legalpad, logged in. --- .../base/controller/PhabricatorController.php | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index 0fee880378..5952a806ed 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -562,25 +562,25 @@ private function requireLegalpadSignatures() { return null; } + $must_sign_docs = array(); + $sign_docs = array(); + $legalpad_class = 'PhabricatorLegalpadApplication'; $legalpad_installed = PhabricatorApplication::isClassInstalledForViewer( $legalpad_class, $viewer); - if (!$legalpad_installed) { - return null; - } - - $sign_docs = id(new LegalpadDocumentQuery()) - ->setViewer($viewer) - ->withSignatureRequired(1) - ->needViewerSignatures(true) - ->setOrder('oldest') - ->execute(); - - $must_sign_docs = array(); - foreach ($sign_docs as $sign_doc) { - if (!$sign_doc->getUserSignature($viewer->getPHID())) { - $must_sign_docs[] = $sign_doc; + if ($legalpad_installed) { + $sign_docs = id(new LegalpadDocumentQuery()) + ->setViewer($viewer) + ->withSignatureRequired(1) + ->needViewerSignatures(true) + ->setOrder('oldest') + ->execute(); + + foreach ($sign_docs as $sign_doc) { + if (!$sign_doc->getUserSignature($viewer->getPHID())) { + $must_sign_docs[] = $sign_doc; + } } } From 46d496b8cc061db11954bcd2c03b882a5573eaad Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 5 Dec 2017 19:24:57 +0000 Subject: [PATCH 306/865] Fix error for URL's that could mean several commits Summary: Ref T13001, URLs that return multiple commits should show a list of those commits. Not sure if the actual list looks very pretty this way, but was wondering if this approach was vaguely correct. Test Plan: - Navigate to `install/rPbd3c23` - User should see a list view providing links to `install/rPbd3c2355e8e2b220ae5e3cbfe4a057c8088c6a38` and `install/rPbd3c239d5aada68a31db5742bbb8ec099074a561` Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T13001 Differential Revision: https://secure.phabricator.com/D18816 --- .../controller/DiffusionCommitController.php | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index cdce8702ca..11463ef97d 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -39,20 +39,23 @@ public function handleRequest(AphrontRequest $request) { return $this->buildRawDiffResponse($drequest); } - $commit = id(new DiffusionCommitQuery()) + $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers(array($commit_identifier)) ->needCommitData(true) ->needAuditRequests(true) - ->executeOne(); + ->setLimit(100) + ->execute(); + + $multiple_results = count($commits) > 1; $crumbs = $this->buildCrumbs(array( - 'commit' => true, + 'commit' => !$multiple_results, )); $crumbs->setBorder(true); - if (!$commit) { + if (!$commits) { if (!$this->getCommitExists()) { return new Aphront404Response(); } @@ -70,7 +73,40 @@ public function handleRequest(AphrontRequest $request) { ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($error); + } else if ($multiple_results) { + + $warning_message = + pht( + 'The identifier %s is ambiguous and matches more than one commit.', + phutil_tag( + 'strong', + array(), + $commit_identifier)); + + $error = id(new PHUIInfoView()) + ->setTitle(pht('Ambiguous Commit')) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->appendChild($warning_message); + + $list = id(new DiffusionCommitListView()) + ->setViewer($viewer) + ->setCommits($commits) + ->setNoDataString(pht('No recent commits.')); + + $crumbs->addTextCrumb(pht('Ambiguous Commit')); + + $matched_commits = id(new PHUITwoColumnView()) + ->setFooter(array( + $error, + $list, + )); + return $this->newPage() + ->setTitle(pht('Ambiguous Commit')) + ->setCrumbs($crumbs) + ->appendChild($matched_commits); + } else { + $commit = head($commits); } $audit_requests = $commit->getAudits(); From a989dd181dd1f1115d728595c2adb482784c4ce0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 05:35:06 -0800 Subject: [PATCH 307/865] Fix Mercurial commit history ordering Summary: See . In D18769, I rewrote this from using the `--branch` flag (which is unsafe and does not function on branches named `--config=x.y` and such). However, this rewrite accidentally changed the result order, which impacted Mercurial commit hisotry lists and graphs. Swap the order of the constraints so we get newest-to-oldest again, as expected. Test Plan: Viewed a Mercurial repository's history graph, saw sensible chronology after the patch. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18817 --- .../diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index e36c0a124e..4c1d39e8c8 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -130,7 +130,7 @@ protected function getMercurialResult(ConduitAPIRequest $request) { } else { $path_arg = ''; $revset_arg = hgsprintf( - 'branch(%s) and reverse(ancestors(%s))', + 'reverse(ancestors(%s)) and branch(%s)', $drequest->getBranch(), $commit_hash); } From 4f8340c05f2bf51d6b86e1e589153fc82ddbb708 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 05:42:03 -0800 Subject: [PATCH 308/865] Restore the "Log In" menubar action Summary: See . In T13024, I rewrote the main menu bar to hide potentially sensitive items (like notification and message counts and saved search filters) until users fully log in. However, the "Log In" item got caught in this too. For clarity, rename `shouldAllowPartialSessions()` to `shouldRequireFullSession()` (since logged-out users don't have any session at all, so it would be a bit misleading to say that "Log In" "allows" a partial session). Then let "Log In" work again for logged-out users. (In most cases, users are prompted to log in when they take an action which requires them to be logged in -- like creating or editing an object, or adding comments -- so this item doesn't really need to exist. However, it aligns better with user expectations in many cases to have it present, and some reasonable operations like "Check if I have notifications/messages" don't have an obvious thing to click otherwise.) Test Plan: Viewed site in an incognito window, saw "Log In" button again. Browsed normally, saw normal menu. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18818 --- .../auth/extension/PhabricatorAuthMainMenuBarExtension.php | 4 ++++ .../people/engineextension/PeopleMainMenuBarExtension.php | 4 ++-- src/view/page/menu/PhabricatorMainMenuBarExtension.php | 4 ++-- src/view/page/menu/PhabricatorMainMenuView.php | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php index d9d251ab07..d9fb5d013b 100644 --- a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php +++ b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php @@ -9,6 +9,10 @@ public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { return true; } + public function shouldRequireFullSession() { + return false; + } + public function getExtensionOrder() { return 900; } diff --git a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php index 01f1ba7cc1..bed9dde44e 100644 --- a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php +++ b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php @@ -9,8 +9,8 @@ public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { return $viewer->isLoggedIn(); } - public function shouldAllowPartialSessions() { - return true; + public function shouldRequireFullSession() { + return false; } public function getExtensionOrder() { diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php index d893cccac9..e66b1f2c7d 100644 --- a/src/view/page/menu/PhabricatorMainMenuBarExtension.php +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -51,8 +51,8 @@ public function isExtensionEnabled() { return true; } - public function shouldAllowPartialSessions() { - return false; + public function shouldRequireFullSession() { + return true; } public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index ba4bda41ea..ad9510f348 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -106,7 +106,7 @@ public function render() { if (!$is_full) { foreach ($extensions as $key => $extension) { - if (!$extension->shouldAllowPartialSessions()) { + if ($extension->shouldRequireFullSession()) { unset($extensions[$key]); } } From 861bb08141a93131a5aeffdb49f509e33d0b875f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 05:35:06 -0800 Subject: [PATCH 309/865] (stable) Fix Mercurial commit history ordering Summary: See . In D18769, I rewrote this from using the `--branch` flag (which is unsafe and does not function on branches named `--config=x.y` and such). However, this rewrite accidentally changed the result order, which impacted Mercurial commit hisotry lists and graphs. Swap the order of the constraints so we get newest-to-oldest again, as expected. Test Plan: Viewed a Mercurial repository's history graph, saw sensible chronology after the patch. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18817 --- .../diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index e36c0a124e..4c1d39e8c8 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -130,7 +130,7 @@ protected function getMercurialResult(ConduitAPIRequest $request) { } else { $path_arg = ''; $revset_arg = hgsprintf( - 'branch(%s) and reverse(ancestors(%s))', + 'reverse(ancestors(%s)) and branch(%s)', $drequest->getBranch(), $commit_hash); } From 50d5fa87a3fc0821cf7475a76d5aa1167cbc0949 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 05:42:03 -0800 Subject: [PATCH 310/865] (stable) Restore the "Log In" menubar action Summary: See . In T13024, I rewrote the main menu bar to hide potentially sensitive items (like notification and message counts and saved search filters) until users fully log in. However, the "Log In" item got caught in this too. For clarity, rename `shouldAllowPartialSessions()` to `shouldRequireFullSession()` (since logged-out users don't have any session at all, so it would be a bit misleading to say that "Log In" "allows" a partial session). Then let "Log In" work again for logged-out users. (In most cases, users are prompted to log in when they take an action which requires them to be logged in -- like creating or editing an object, or adding comments -- so this item doesn't really need to exist. However, it aligns better with user expectations in many cases to have it present, and some reasonable operations like "Check if I have notifications/messages" don't have an obvious thing to click otherwise.) Test Plan: Viewed site in an incognito window, saw "Log In" button again. Browsed normally, saw normal menu. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18818 --- .../auth/extension/PhabricatorAuthMainMenuBarExtension.php | 4 ++++ .../people/engineextension/PeopleMainMenuBarExtension.php | 4 ++-- src/view/page/menu/PhabricatorMainMenuBarExtension.php | 4 ++-- src/view/page/menu/PhabricatorMainMenuView.php | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php index d9d251ab07..d9fb5d013b 100644 --- a/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php +++ b/src/applications/auth/extension/PhabricatorAuthMainMenuBarExtension.php @@ -9,6 +9,10 @@ public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { return true; } + public function shouldRequireFullSession() { + return false; + } + public function getExtensionOrder() { return 900; } diff --git a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php index 01f1ba7cc1..bed9dde44e 100644 --- a/src/applications/people/engineextension/PeopleMainMenuBarExtension.php +++ b/src/applications/people/engineextension/PeopleMainMenuBarExtension.php @@ -9,8 +9,8 @@ public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { return $viewer->isLoggedIn(); } - public function shouldAllowPartialSessions() { - return true; + public function shouldRequireFullSession() { + return false; } public function getExtensionOrder() { diff --git a/src/view/page/menu/PhabricatorMainMenuBarExtension.php b/src/view/page/menu/PhabricatorMainMenuBarExtension.php index d893cccac9..e66b1f2c7d 100644 --- a/src/view/page/menu/PhabricatorMainMenuBarExtension.php +++ b/src/view/page/menu/PhabricatorMainMenuBarExtension.php @@ -51,8 +51,8 @@ public function isExtensionEnabled() { return true; } - public function shouldAllowPartialSessions() { - return false; + public function shouldRequireFullSession() { + return true; } public function isExtensionEnabledForViewer(PhabricatorUser $viewer) { diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index ba4bda41ea..ad9510f348 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -106,7 +106,7 @@ public function render() { if (!$is_full) { foreach ($extensions as $key => $extension) { - if (!$extension->shouldAllowPartialSessions()) { + if ($extension->shouldRequireFullSession()) { unset($extensions[$key]); } } From 6d3baa92f90813b77b2acbc1e6860ada5f28f60c Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Dec 2017 06:47:48 -0800 Subject: [PATCH 311/865] Execute Herald again after promoting revisions out of the "Draft" state Summary: Fixes T13027. Ref T2543. When revisions promote from "Draft" because builds finish or no builds are configured, the status currently switches from "Draft" to "Needs Review" without re-running Herald. This means that some rules -- notably, "Send me an email" rules -- don't fire as soon as they should. Instead of applying this promotion in a hacky way inline, queue it and apply it normally in a second edit, after the current group finishes. Test Plan: - Created a revision, reviewed Herald transcripts. - Saw three Herald passes: - First pass (revision creation) triggered builds and no email. - Second pass (builds finished) did not trigger builds (no update) and did not trigger email (revision still a draft). - Third pass (after promotion out of 'draft') did not trigger builds (no update) but did trigger email (revision no longer a draft). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13027, T2543 Differential Revision: https://secure.phabricator.com/D18819 --- .../editor/DifferentialTransactionEditor.php | 16 ++------ ...entialRevisionRequestReviewTransaction.php | 14 ++++--- ...habricatorApplicationTransactionEditor.php | 39 +++++++++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index ae652ba0ec..b2fc179002 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -1588,11 +1588,8 @@ protected function didApplyTransactions($object, array $xactions) { ->setAuthorPHID($author_phid) ->setTransactionType( DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE) - ->setOldValue(false) ->setNewValue(true); - $xaction = $this->populateTransaction($object, $xaction); - // If we're creating this revision and immediately moving it out of // the draft state, mark this as a create transaction so it gets // hidden in the timeline and mail, since it isn't interesting: it @@ -1601,15 +1598,10 @@ protected function didApplyTransactions($object, array $xactions) { $xaction->setIsCreateTransaction(true); } - $object->openTransaction(); - $object - ->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW) - ->save(); - - $xaction->save(); - $object->saveTransaction(); - - $xactions[] = $xaction; + // Queue this transaction and apply it separately after the current + // batch of transactions finishes so that Herald can fire on the new + // revision state. See T13027 for discussion. + $this->queueTransaction($xaction); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 465b5234d5..ed0d20a0b6 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -57,11 +57,15 @@ protected function validateAction($object, PhabricatorUser $viewer) { 'revisions.')); } - if (!$this->isViewerRevisionAuthor($object, $viewer)) { - throw new Exception( - pht( - 'You can not request review of this revision because you are not '. - 'the author of the revision.')); + // When revisions automatically promote out of "Draft" after builds finish, + // the viewer may be acting as the Harbormaster application. + if (!$viewer->isOmnipotent()) { + if (!$this->isViewerRevisionAuthor($object, $viewer)) { + throw new Exception( + pht( + 'You can not request review of this revision because you are not '. + 'the author of the revision.')); + } } } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 175a5775ee..8e6d816769 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -70,6 +70,8 @@ abstract class PhabricatorApplicationTransactionEditor private $feedRelatedPHIDs = array(); private $modularTypes; + private $transactionQueue = array(); + const STORAGE_ENCODING_BINARY = 'binary'; /** @@ -1174,6 +1176,8 @@ final public function applyTransactions( 'priority' => PhabricatorWorker::PRIORITY_ALERTS, )); + $this->flushTransactionQueue($object); + return $xactions; } @@ -3864,4 +3868,39 @@ public function getCreateObjectTitleForFeed($author, $object) { return pht('%s created an object: %s.', $author, $object); } +/* -( Queue )-------------------------------------------------------------- */ + + protected function queueTransaction( + PhabricatorApplicationTransaction $xaction) { + $this->transactionQueue[] = $xaction; + return $this; + } + + private function flushTransactionQueue($object) { + if (!$this->transactionQueue) { + return; + } + + $xactions = $this->transactionQueue; + $this->transactionQueue = array(); + + $editor = $this->newQueueEditor(); + + return $editor->applyTransactions($object, $xactions); + } + + private function newQueueEditor() { + $editor = id(newv(get_class($this), array())) + ->setActor($this->getActor()) + ->setContentSource($this->getContentSource()) + ->setContinueOnNoEffect($this->getContinueOnNoEffect()) + ->setContinueOnMissingFields($this->getContinueOnMissingFields()); + + if ($this->actingAsPHID !== null) { + $editor->setActingAsPHID($this->actingAsPHID); + } + + return $editor; + } + } From 60e5c0ec1b3e13cb2b2f9b73525f8354c206c23f Mon Sep 17 00:00:00 2001 From: Tim Hirsh Date: Fri, 8 Dec 2017 11:54:42 -0500 Subject: [PATCH 312/865] Add drydock.blueprint.edit Conduit method Summary: Ref: https://admin.phacility.com/PHI243 Since our use case primarily focuses on transaction editing, this patch implements the `drydock.blueprint.edit` api method with the understanding that: a) this is a work in progress b) object editing is supported, but object creation is not yet implemented Test Plan: * updated existing blueprints via Conduit UI * regression tested `maniphest.edit` by creating new and updating existing tasks Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, yelirekim, jcox Differential Revision: https://secure.phabricator.com/D18822 --- src/__phutil_library_map__.php | 2 ++ .../DrydockBlueprintEditConduitAPIMethod.php | 20 +++++++++++++++++++ .../editor/DrydockBlueprintEditEngine.php | 11 ++++++++++ .../editengine/PhabricatorEditEngine.php | 11 +++++++++- 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 11be8a50f0..35be621604 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1004,6 +1004,7 @@ 'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php', 'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php', 'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php', + 'DrydockBlueprintEditConduitAPIMethod' => 'applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php', 'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php', 'DrydockBlueprintEditEngine' => 'applications/drydock/editor/DrydockBlueprintEditEngine.php', 'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php', @@ -6090,6 +6091,7 @@ 'DrydockBlueprintCustomField' => 'PhabricatorCustomField', 'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockBlueprintDisableController' => 'DrydockBlueprintController', + 'DrydockBlueprintEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DrydockBlueprintEditController' => 'DrydockBlueprintController', 'DrydockBlueprintEditEngine' => 'PhabricatorEditEngine', 'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php b/src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php new file mode 100644 index 0000000000..764c077d87 --- /dev/null +++ b/src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php @@ -0,0 +1,20 @@ +setBlueprintImplementation($impl); + return $this->newEditableObject(); + } + protected function newObjectQuery() { return new DrydockBlueprintQuery(); } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 6c9cb5e201..8ba49f4d77 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -630,6 +630,15 @@ final public function getIsCreate() { return $this->isCreate; } + /** + * Initialize a new object for documentation creation. + * + * @return object Newly initialized object. + * @task load + */ + protected function newEditableObjectForDocumentation() { + return $this->newEditableObject(); + } /** * Flag this workflow as a create or edit. @@ -2198,7 +2207,7 @@ public function getConduitEditTypes() { return array(); } - $object = $this->newEditableObject(); + $object = $this->newEditableObjectForDocumentation(); $fields = $this->buildEditFields($object); return $this->getConduitEditTypesFromFields($fields); } From c9a0d68340ba131d8c8c0b7180293607a19da625 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 11 Dec 2017 11:33:15 -0800 Subject: [PATCH 313/865] Allow Herald rules to add comments Summary: See PHI242. All use cases for this that I know of are pretty hacky, but they don't seem perilous, and it's easier than webhooks. See P1895, T10183, and T9853 for me previously refusing to implement this since all those use cases were also pretty bad. Test Plan: - Wrote a rule to add comments, saw it add comments. - Reviewed summary, re-edited rule, reviewed transcript to check that all the strings worked OK. - Wrote a new rule for a non-commentable object (a blog) to make sure I wasn't offered the "Add a comment" action. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18823 --- resources/celerity/map.php | 26 +++--- src/__phutil_library_map__.php | 4 + .../herald/action/HeraldAction.php | 3 + .../herald/action/HeraldCommentAction.php | 79 +++++++++++++++++++ .../herald/value/HeraldFieldValue.php | 1 + .../herald/value/HeraldRemarkupFieldValue.php | 22 ++++++ .../rsrc/css/application/herald/herald.css | 6 ++ .../js/application/herald/HeraldRuleEditor.js | 5 ++ 8 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 src/applications/herald/action/HeraldCommentAction.php create mode 100644 src/applications/herald/value/HeraldRemarkupFieldValue.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0ea66bec26..d937b91e4f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -80,7 +80,7 @@ 'rsrc/css/application/flag/flag.css' => 'bba8f811', 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', - 'rsrc/css/application/herald/herald.css' => 'dc31f6e9', + 'rsrc/css/application/herald/herald.css' => 'cd8d0134', 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', @@ -416,7 +416,7 @@ 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', - 'rsrc/js/application/herald/HeraldRuleEditor.js' => 'd6a7e717', + 'rsrc/js/application/herald/HeraldRuleEditor.js' => '2dff5579', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', @@ -578,8 +578,8 @@ 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => 'b556a948', 'harbormaster-css' => 'f491c9f4', - 'herald-css' => 'dc31f6e9', - 'herald-rule-editor' => 'd6a7e717', + 'herald-css' => 'cd8d0134', + 'herald-rule-editor' => '2dff5579', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => 'f23d4e8f', 'javelin-aphlict' => 'e1d4b11a', @@ -1106,6 +1106,15 @@ 'javelin-install', 'javelin-event', ), + '2dff5579' => array( + 'multirow-row-manager', + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-json', + 'phabricator-prefab', + ), '2ee659ce' => array( 'javelin-install', ), @@ -2001,15 +2010,6 @@ 'javelin-dom', 'javelin-stratcom', ), - 'd6a7e717' => array( - 'multirow-row-manager', - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-json', - 'phabricator-prefab', - ), 'd7a74243' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 35be621604..924e81a5ff 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1337,6 +1337,7 @@ 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php', 'HeraldBasicFieldGroup' => 'applications/herald/field/HeraldBasicFieldGroup.php', 'HeraldBuildableState' => 'applications/herald/state/HeraldBuildableState.php', + 'HeraldCommentAction' => 'applications/herald/action/HeraldCommentAction.php', 'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php', 'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', @@ -1378,6 +1379,7 @@ 'HeraldProjectsField' => 'applications/project/herald/HeraldProjectsField.php', 'HeraldRecursiveConditionsException' => 'applications/herald/engine/exception/HeraldRecursiveConditionsException.php', 'HeraldRelatedFieldGroup' => 'applications/herald/field/HeraldRelatedFieldGroup.php', + 'HeraldRemarkupFieldValue' => 'applications/herald/value/HeraldRemarkupFieldValue.php', 'HeraldRemarkupRule' => 'applications/herald/remarkup/HeraldRemarkupRule.php', 'HeraldRepetitionPolicyConfig' => 'applications/herald/config/HeraldRepetitionPolicyConfig.php', 'HeraldRule' => 'applications/herald/storage/HeraldRule.php', @@ -6488,6 +6490,7 @@ 'HeraldApplyTranscript' => 'Phobject', 'HeraldBasicFieldGroup' => 'HeraldFieldGroup', 'HeraldBuildableState' => 'HeraldState', + 'HeraldCommentAction' => 'HeraldAction', 'HeraldCommitAdapter' => array( 'HeraldAdapter', 'HarbormasterBuildableAdapterInterface', @@ -6535,6 +6538,7 @@ 'HeraldProjectsField' => 'HeraldField', 'HeraldRecursiveConditionsException' => 'Exception', 'HeraldRelatedFieldGroup' => 'HeraldFieldGroup', + 'HeraldRemarkupFieldValue' => 'HeraldFieldValue', 'HeraldRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'HeraldRepetitionPolicyConfig' => 'Phobject', 'HeraldRule' => array( diff --git a/src/applications/herald/action/HeraldAction.php b/src/applications/herald/action/HeraldAction.php index a2d589f9f2..04884a94d4 100644 --- a/src/applications/herald/action/HeraldAction.php +++ b/src/applications/herald/action/HeraldAction.php @@ -9,6 +9,7 @@ abstract class HeraldAction extends Phobject { const STANDARD_NONE = 'standard.none'; const STANDARD_PHID_LIST = 'standard.phid.list'; const STANDARD_TEXT = 'standard.text'; + const STANDARD_REMARKUP = 'standard.remarkup'; const DO_STANDARD_EMPTY = 'do.standard.empty'; const DO_STANDARD_NO_EFFECT = 'do.standard.no-effect'; @@ -60,6 +61,8 @@ public function getHeraldActionValueType() { return new HeraldEmptyFieldValue(); case self::STANDARD_TEXT: return new HeraldTextFieldValue(); + case self::STANDARD_REMARKUP: + return new HeraldRemarkupFieldValue(); case self::STANDARD_PHID_LIST: $tokenizer = id(new HeraldTokenizerFieldValue()) ->setKey($this->getHeraldActionName()) diff --git a/src/applications/herald/action/HeraldCommentAction.php b/src/applications/herald/action/HeraldCommentAction.php new file mode 100644 index 0000000000..fa52ba1f5f --- /dev/null +++ b/src/applications/herald/action/HeraldCommentAction.php @@ -0,0 +1,79 @@ +getApplicationTransactionTemplate(); + try { + $comment = $xaction->getApplicationTransactionCommentObject(); + if (!$comment) { + return false; + } + } catch (PhutilMethodNotImplementedException $ex) { + return false; + } + + return true; + } + + public function supportsRuleType($rule_type) { + return ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); + } + + public function applyEffect($object, HeraldEffect $effect) { + $adapter = $this->getAdapter(); + $comment_text = $effect->getTarget(); + + $xaction = $adapter->newTransaction() + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT); + + $comment = $xaction->getApplicationTransactionCommentObject() + ->setContent($comment_text); + + $xaction->attachComment($comment); + + $adapter->queueTransaction($xaction); + + $this->logEffect(self::DO_COMMENT, $comment_text); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_REMARKUP; + } + + protected function getActionEffectMap() { + return array( + self::DO_COMMENT => array( + 'icon' => 'fa-comment', + 'color' => 'blue', + 'name' => pht('Added Comment'), + ), + ); + } + + public function renderActionDescription($value) { + $summary = PhabricatorMarkupEngine::summarize($value); + return pht('Add comment: %s', $summary); + } + + protected function renderActionEffectDescription($type, $data) { + $summary = PhabricatorMarkupEngine::summarize($data); + return pht('Added a comment: %s', $summary); + } + +} diff --git a/src/applications/herald/value/HeraldFieldValue.php b/src/applications/herald/value/HeraldFieldValue.php index b3f35968e2..c88776f53a 100644 --- a/src/applications/herald/value/HeraldFieldValue.php +++ b/src/applications/herald/value/HeraldFieldValue.php @@ -8,6 +8,7 @@ abstract class HeraldFieldValue extends Phobject { const CONTROL_TEXT = 'herald.control.text'; const CONTROL_SELECT = 'herald.control.select'; const CONTROL_TOKENIZER = 'herald.control.tokenizer'; + const CONTROL_REMARKUP = 'herald.control.remarkup'; abstract public function getFieldValueKey(); abstract public function getControlType(); diff --git a/src/applications/herald/value/HeraldRemarkupFieldValue.php b/src/applications/herald/value/HeraldRemarkupFieldValue.php new file mode 100644 index 0000000000..4740ec7342 --- /dev/null +++ b/src/applications/herald/value/HeraldRemarkupFieldValue.php @@ -0,0 +1,22 @@ + Date: Wed, 13 Dec 2017 05:35:56 -0800 Subject: [PATCH 314/865] Move the Git LFS gate to dedicated (non-prototype) config Summary: See PHI131. Ref T7789. Although this probably isn't 100% complete, there don't seem to be any actual, known, practical blocking issues remaining (everything is either heresay or not reproducible). Test Plan: Tried to push LFS locally, got blocked with a helpful message. Enabled setting, tried to push LFS locally, got a successful push. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D18825 --- .../config/PhabricatorDiffusionConfigOptions.php | 15 ++++++++++++++- .../repository/storage/PhabricatorRepository.php | 3 +-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php index 50f761eabc..d15fb678d5 100644 --- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php +++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php @@ -101,7 +101,7 @@ public function getOptions() { ->setBoolOptions( array( pht('Allow HTTP Basic Auth'), - pht('Disable HTTP Basic Auth'), + pht('Disallow HTTP Basic Auth'), )) ->setSummary(pht('Enable HTTP Basic Auth for repositories.')) ->setDescription( @@ -115,6 +115,19 @@ public function getOptions() { "barrier to attackers than SSH does.\n\n". "Consider using SSH for authenticated access to repositories ". "instead of HTTP.")), + $this->newOption('diffusion.allow-git-lfs', 'bool', false) + ->setBoolOptions( + array( + pht('Allow Git LFS'), + pht('Disallow Git LFS'), + )) + ->setLocked(true) + ->setSummary(pht('Allow Git Large File Storage (LFS).')) + ->setDescription( + pht( + 'Phabricator supports Git LFS, a Git extension for storing large '. + 'files alongside a repository. Activate this setting to allow '. + 'the extension to store file data in Phabricator.')), $this->newOption('diffusion.ssh-user', 'string', null) ->setLocked(true) ->setSummary(pht('Login username for SSH connections to repositories.')) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 713ed92270..702bb42780 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1627,8 +1627,7 @@ public function canUseGitLFS() { return false; } - // TODO: Unprototype this feature. - if (!PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + if (!PhabricatorEnv::getEnvConfig('diffusion.allow-git-lfs')) { return false; } From 8e416474c06398857a990bb0bd6d4725ab17f100 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Dec 2017 05:52:39 -0800 Subject: [PATCH 315/865] Add a Herald pre-commit field for detecting LFS usage Summary: Depends on D18825. Ref T7789. See PHI131. Allows installs to selectively disable LFS by adding Herald rules to block commits that use LFS. Test Plan: - Wrote an LFS rule ("When commit uses git lfs, block commit"). - Pushed an LFS commit: rejected. - Pushed a non-lFS commit: success. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D18827 --- src/__phutil_library_map__.php | 2 + ...iffusionPreCommitUsesGitLFSHeraldField.php | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/applications/diffusion/herald/DiffusionPreCommitUsesGitLFSHeraldField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 924e81a5ff..f86b3a1d2d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -821,6 +821,7 @@ 'DiffusionPreCommitRefRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryHeraldField.php', 'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryProjectsHeraldField.php', 'DiffusionPreCommitRefTypeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php', + 'DiffusionPreCommitUsesGitLFSHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitUsesGitLFSHeraldField.php', 'DiffusionPullEventGarbageCollector' => 'applications/diffusion/garbagecollector/DiffusionPullEventGarbageCollector.php', 'DiffusionPushCapability' => 'applications/diffusion/capability/DiffusionPushCapability.php', 'DiffusionPushEventViewController' => 'applications/diffusion/controller/DiffusionPushEventViewController.php', @@ -5881,6 +5882,7 @@ 'DiffusionPreCommitRefRepositoryHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefTypeHeraldField' => 'DiffusionPreCommitRefHeraldField', + 'DiffusionPreCommitUsesGitLFSHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPullEventGarbageCollector' => 'PhabricatorGarbageCollector', 'DiffusionPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionPushEventViewController' => 'DiffusionPushLogController', diff --git a/src/applications/diffusion/herald/DiffusionPreCommitUsesGitLFSHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitUsesGitLFSHeraldField.php new file mode 100644 index 0000000000..3a34db777c --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionPreCommitUsesGitLFSHeraldField.php @@ -0,0 +1,41 @@ +getAdapter()->getDiffContent('+'); + + // At the time of writing, all current Git LFS files begin with this + // line, verbatim: + // + // version https://git-lfs.github.com/spec/v1 + // + // ...but we don't try to match the specific version here, in the hopes + // that this might also detect future versions. + $pattern = '(^version\s*https://git-lfs.github.com/spec/)i'; + + foreach ($map as $path => $content) { + if (preg_match($pattern, $content)) { + return true; + } + } + + return false; + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_BOOL; + } + +} From 5295840a4cf9c1c75e317ace5597a254e3e6b27b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Dec 2017 06:02:09 -0800 Subject: [PATCH 316/865] Restore the "Download from Git LFS" UI button to Diffusion Summary: Depends on D18827. Ref T7789. See PHI204. See PHI131. This button got accidentally removed in Diffusion refactoring (`$data` is no longer used). Test Plan: {F5321459} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D18828 --- .../diffusion/controller/DiffusionBrowseController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 4985ac2bb7..77ec8e46ca 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -916,7 +916,8 @@ private function renderGitLFSButton() { ->setTag('a') ->setText($text) ->setHref($href) - ->setIcon($icon); + ->setIcon($icon) + ->setColor(PHUIButtonView::GREY); } private function buildDisplayRows( @@ -1924,7 +1925,7 @@ private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) { try { $file = $this->loadGitLFSFile($ref); - $data = $this->renderGitLFSButton(); + $this->corpusButtons[] = $this->renderGitLFSButton(); } catch (Exception $ex) { $severity = PHUIInfoView::SEVERITY_ERROR; $messages[] = pht('The data for this file could not be loaded.'); From e1d6bad864d4faccba5a63261d2a2800a39fd377 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Dec 2017 06:28:11 -0800 Subject: [PATCH 317/865] Stop trying to assess the image dimensions of large files and file chunks Summary: Depends on D18828. Ref T7789. See . Currently, when you upload a large (>4MB) image, we may try to assess the dimensions for the image and for each individual chunk. At best, this is slow and not useful. At worst, it fatals or consumes a ton of memory and I/O we don't need to be using. Instead: - Don't try to assess dimensions for chunked files. - Don't try to assess dimensions for the chunks themselves. - Squelch errors for bad data, etc., that `gd` can't actually read, since we recover sensibly. Test Plan: - Created a 2048x2048 PNG in Photoshop using the "Random Noise" filter which weighs 8.5MB. - Uploaded it. - Before patch: got complaints in log about `imagecreatefromstring()` failing, although the actual upload went OK in my environment. - After patch: clean log, no attempt to detect the size of a big image. - Also uploaded a small image, got dimensions detected properly still. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T7789 Differential Revision: https://secure.phabricator.com/D18830 --- .../FileUploadChunkConduitAPIMethod.php | 1 + .../files/storage/PhabricatorFile.php | 31 +++++++++++++++++-- .../PhabricatorFileUploadSource.php | 1 + 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php index b4d1a23a3f..eab46c4992 100644 --- a/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php +++ b/src/applications/files/conduit/FileUploadChunkConduitAPIMethod.php @@ -64,6 +64,7 @@ protected function execute(ConduitAPIRequest $request) { $params = array( 'name' => $file->getMonogram().'.chunk-'.$chunk->getID(), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + 'chunk' => true, ); if ($mime_type !== null) { diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index c87623c68c..d3dc5208d2 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -40,6 +40,7 @@ final class PhabricatorFile extends PhabricatorFileDAO const METADATA_PROFILE = 'profile'; const METADATA_STORAGE = 'storage'; const METADATA_INTEGRITY = 'integrity'; + const METADATA_CHUNK = 'chunk'; const STATUS_ACTIVE = 'active'; const STATUS_DELETED = 'deleted'; @@ -410,7 +411,7 @@ private static function buildFromFileData($data, array $params = array()) { try { $file->updateDimensions(false); } catch (Exception $ex) { - // Do nothing + // Do nothing. } $file->saveAndIndex(); @@ -1057,9 +1058,20 @@ public function updateDimensions($save = true) { throw new Exception(pht('Cannot retrieve image information.')); } + if ($this->getIsChunk()) { + throw new Exception( + pht('Refusing to assess image dimensions of file chunk.')); + } + + $engine = $this->instantiateStorageEngine(); + if ($engine->isChunkEngine()) { + throw new Exception( + pht('Refusing to assess image dimensions of chunked file.')); + } + $data = $this->loadFileData(); - $img = imagecreatefromstring($data); + $img = @imagecreatefromstring($data); if ($img === false) { throw new Exception(pht('Error when decoding image.')); } @@ -1255,6 +1267,15 @@ public function setIsProfileImage($value) { return $this; } + public function getIsChunk() { + return idx($this->metadata, self::METADATA_CHUNK); + } + + public function setIsChunk($value) { + $this->metadata[self::METADATA_CHUNK] = $value; + return $this; + } + public function setIntegrityHash($integrity_hash) { $this->metadata[self::METADATA_INTEGRITY] = $integrity_hash; return $this; @@ -1339,6 +1360,7 @@ private function readPropertiesFromParameters(array $params) { 'mime-type' => 'optional string', 'builtin' => 'optional string', 'storageEngines' => 'optional list', + 'chunk' => 'optional bool', )); $file_name = idx($params, 'name'); @@ -1416,6 +1438,11 @@ private function readPropertiesFromParameters(array $params) { $this->setMimeType($mime_type); } + $is_chunk = idx($params, 'chunk'); + if ($is_chunk) { + $this->setIsChunk(true); + } + return $this; } diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index e3242af329..bbd93a455a 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -189,6 +189,7 @@ private function writeChunk( $params = array( 'name' => $file->getMonogram().'.chunk-'.$offset, 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + 'chunk' => true, ); // If this isn't the initial chunk, provide a dummy MIME type so we do not From a1ad184ddd8cd7d788320e871c36b4c1a144fc2c Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Dec 2017 07:36:06 -0800 Subject: [PATCH 318/865] Denormalize added and removed line counts for the current diff onto revisions Summary: See PHI230. Currently, we denormalize raw line counts onto diffs and revisions, but not added/removed line counts. I'd like to try a `[---+ ]` sort of size hint element (see D16322 for more) as a general approach to conveying size information at a glance and see how it feels, since I think the raw size number isn't very scannable/useful and it may be a significant improvement to hint about how much of a change is throwing stuff out vs adding new stuff. This just makes the data available without any subquerying and doesn't actually change the UI. Test Plan: Created a revision, saw detailed change information populate in the database. ``` mysql> select * from differential_revision where id = 292\G *************************** 1. row *************************** id: 292 title: WIP originalTitle: WIP phid: PHID-DREV-ux3cxptibn3l5pxsug3z status: draft summary: asdf testPlan: asdf authorPHID: PHID-USER-cvfydnwadpdj7vdon36z lastReviewerPHID: NULL lineCount: 41 dateCreated: 1513179418 dateModified: 1513179418 attached: [] mailKey: h4mn6perdio47o4beomyvu75zezwvredx3mbrlgz branchName: NULL viewPolicy: users editPolicy: users repositoryPHID: PHID-REPO-wif5lutk5gn3y6ursk4p properties: {"lines.added":40,"lines.removed":1} activeDiffPHID: PHID-DIFF-ixjphpunpkenqgukpmce 1 row in set (0.00 sec) ``` Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18832 --- .../editor/DifferentialTransactionEditor.php | 24 ++++++++++++++++++- .../storage/DifferentialRevision.php | 17 +++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index b2fc179002..e6dfed88a7 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -132,12 +132,14 @@ protected function applyCustomInternalTransaction( $diff = $this->requireDiff($xaction->getNewValue()); - $object->setLineCount($diff->getLineCount()); + $this->updateRevisionLineCounts($object, $diff); + if ($this->repositoryPHIDOverride !== false) { $object->setRepositoryPHID($this->repositoryPHIDOverride); } else { $object->setRepositoryPHID($diff->getRepositoryPHID()); } + $object->attachActiveDiff($diff); $object->setActiveDiffPHID($diff->getPHID()); return; @@ -1645,5 +1647,25 @@ private function hasActiveBuilds($object) { return true; } + private function updateRevisionLineCounts( + DifferentialRevision $revision, + DifferentialDiff $diff) { + + $revision->setLineCount($diff->getLineCount()); + + $conn = $revision->establishConnection('r'); + + $row = queryfx_one( + $conn, + 'SELECT SUM(addLines) A, SUM(delLines) D FROM %T + WHERE diffID = %d', + id(new DifferentialChangeset())->getTableName(), + $diff->getID()); + + if ($row) { + $revision->setAddedLineCount((int)$row['A']); + $revision->setRemovedLineCount((int)$row['D']); + } + } } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 73f22c91e4..2c82de164a 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -61,6 +61,8 @@ final class DifferentialRevision extends DifferentialDAO const PROPERTY_CLOSED_FROM_ACCEPTED = 'wasAcceptedBeforeClose'; const PROPERTY_DRAFT_HOLD = 'draft.hold'; const PROPERTY_HAS_BROADCAST = 'draft.broadcast'; + const PROPERTY_LINES_ADDED = 'lines.added'; + const PROPERTY_LINES_REMOVED = 'lines.removed'; public static function initializeNewRevision(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -728,6 +730,21 @@ public function setHasBroadcast($has_broadcast) { return $this->setProperty(self::PROPERTY_HAS_BROADCAST, $has_broadcast); } + public function setAddedLineCount($count) { + return $this->setProperty(self::PROPERTY_LINES_ADDED, $count); + } + + public function getAddedLineCount() { + return $this->getProperty(self::PROPERTY_LINES_ADDED); + } + + public function setRemovedLineCount($count) { + return $this->setProperty(self::PROPERTY_LINES_REMOVED, $count); + } + + public function getRemovedLineCount() { + return $this->getProperty(self::PROPERTY_LINES_REMOVED); + } public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); From 7cbbe2ccf793fe29d1cb7b6a7081f57da1f24795 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 13 Dec 2017 07:05:07 -0800 Subject: [PATCH 319/865] When users browse to a submodule path in Diffusion explicitly, don't fatal Summary: Ref T13030. See PHI254. This behavior could be cleaner than I've made it, but it fixes the "this is totally broken" issue, replacing a fatal/exception with an informative (just not terribly useful) page. Test Plan: - Added a submodule to a repository. - In Diffusion, clicked some other file next to the submodule, then edited the URI to the submodule path instead. - Before patch: fatal. - After patch: relatively useful message about this being a submodule. Note that it's normally hard to hit this URI directly. In the browse view, submodules are marked up as directories and linked to a separate submodule resolution flow. {F5321524} Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13030 Differential Revision: https://secure.phabricator.com/D18831 --- .../DiffusionBrowseQueryConduitAPIMethod.php | 30 +++++++++++++++++++ .../data/DiffusionBrowseResultSet.php | 1 + .../view/DiffusionEmptyResultView.php | 7 +++++ 3 files changed, 38 insertions(+) diff --git a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php index 7ff0f6725a..2e272d69a7 100644 --- a/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php @@ -52,6 +52,36 @@ protected function getGitResult(ConduitAPIRequest $request) { $commit, $path); } catch (CommandException $e) { + // The "cat-file" command may fail if the path legitimately does not + // exist, but it may also fail if the path is a submodule. This can + // produce either "Not a valid object name" or "could not get object + // info". + + // To detect if we have a submodule, use `git ls-tree`. If the path + // is a submodule, we'll get a "160000" mode mask with type "commit". + + list($sub_err, $sub_stdout) = $repository->execLocalCommand( + 'ls-tree %s -- %s', + $commit, + $path); + if (!$sub_err) { + // If the path failed "cat-file" but "ls-tree" worked, we assume it + // must be a submodule. If it is, the output will look something + // like this: + // + // 160000 commit + // + // We make sure it has the 160000 mode mask to confirm that it's + // definitely a submodule. + $mode = (int)$sub_stdout; + if ($mode & 160000) { + $submodule_reason = DiffusionBrowseResultSet::REASON_IS_SUBMODULE; + $result + ->setReasonForEmptyResultSet($submodule_reason); + return $result; + } + } + $stderr = $e->getStderr(); if (preg_match('/^fatal: Not a valid object name/', $stderr)) { // Grab two logs, since the first one is when the object was deleted. diff --git a/src/applications/diffusion/data/DiffusionBrowseResultSet.php b/src/applications/diffusion/data/DiffusionBrowseResultSet.php index 2208aca505..22d9e08251 100644 --- a/src/applications/diffusion/data/DiffusionBrowseResultSet.php +++ b/src/applications/diffusion/data/DiffusionBrowseResultSet.php @@ -3,6 +3,7 @@ final class DiffusionBrowseResultSet extends Phobject { const REASON_IS_FILE = 'is-file'; + const REASON_IS_SUBMODULE = 'is-submodule'; const REASON_IS_DELETED = 'is-deleted'; const REASON_IS_NONEXISTENT = 'nonexistent'; const REASON_BAD_COMMIT = 'bad-commit'; diff --git a/src/applications/diffusion/view/DiffusionEmptyResultView.php b/src/applications/diffusion/view/DiffusionEmptyResultView.php index 12def83e76..c818267268 100644 --- a/src/applications/diffusion/view/DiffusionEmptyResultView.php +++ b/src/applications/diffusion/view/DiffusionEmptyResultView.php @@ -40,6 +40,13 @@ public function render() { $body = pht('This path was an empty directory at %s.', $commit); $severity = PHUIInfoView::SEVERITY_NOTICE; break; + case DiffusionBrowseResultSet::REASON_IS_SUBMODULE: + $title = pht('Submodule'); + // TODO: We could improve this, but it is normally difficult to + // reach this page for a submodule. + $body = pht('This path was a submodule at %s.', $commit); + $severity = PHUIInfoView::SEVERITY_NOTICE; + break; case DiffusionBrowseResultSet::REASON_IS_DELETED: $deleted = $this->browseResultSet->getDeletedAtCommit(); $existed = $this->browseResultSet->getExistedAtCommit(); From 8dccf05c4c1af535ca1310e9e893bd2e4d316db3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 20 Dec 2017 09:47:40 -0800 Subject: [PATCH 320/865] Manually set "max_allowed_packet" to 1GB for "mysqldump" Summary: We have one production instance with failing database backups since they recently uploaded a 52MB hunk. The production configuration specifies a 64MB "max_allowed_packet" in `[mysqld]`, but this doesn't apply to `mysqldump` (we'd need to specify it in a separate `[mysqldump]` section) and `mysqldump` runs with an effective limit of the default (16MB). We could change our production config to specify a value in `[mysqldump]`, but just change it unconditionally at execution time since there's no reason for any user to ever want this command to fail because they have too much data. Test Plan: Dumped locally, will verify production backup goes through cleanly. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18834 --- .../PhabricatorStorageManagementDumpWorkflow.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index 88bd80a9a8..d5f1da9ecf 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -187,6 +187,22 @@ public function didExecute(PhutilArgumentParser $args) { $argv[] = '-h'; $argv[] = $host; + // MySQL's default "max_allowed_packet" setting is fairly conservative + // (16MB). If we try to dump a row which is larger than this limit, the + // dump will fail. + + // We encourage users to increase this limit during setup, but modifying + // the "[mysqld]" section of the configuration file (instead of + // "[mysqldump]" section) won't apply to "mysqldump" and we can not easily + // detect what the "mysqldump" setting is. + + // Since no user would ever reasonably want a dump to fail because a row + // was too large, just manually force this setting to the largest supported + // value. + + $argv[] = '--max-allowed-packet'; + $argv[] = '1G'; + if ($port) { $argv[] = '--port'; $argv[] = $port; From f56124dc93c3ed342dd9a687b8c07fe0d5f10410 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 20 Dec 2017 09:47:40 -0800 Subject: [PATCH 321/865] (stable) Manually set "max_allowed_packet" to 1GB for "mysqldump" Summary: We have one production instance with failing database backups since they recently uploaded a 52MB hunk. The production configuration specifies a 64MB "max_allowed_packet" in `[mysqld]`, but this doesn't apply to `mysqldump` (we'd need to specify it in a separate `[mysqldump]` section) and `mysqldump` runs with an effective limit of the default (16MB). We could change our production config to specify a value in `[mysqldump]`, but just change it unconditionally at execution time since there's no reason for any user to ever want this command to fail because they have too much data. Test Plan: Dumped locally, will verify production backup goes through cleanly. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18834 --- .../PhabricatorStorageManagementDumpWorkflow.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index 88bd80a9a8..d5f1da9ecf 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -187,6 +187,22 @@ public function didExecute(PhutilArgumentParser $args) { $argv[] = '-h'; $argv[] = $host; + // MySQL's default "max_allowed_packet" setting is fairly conservative + // (16MB). If we try to dump a row which is larger than this limit, the + // dump will fail. + + // We encourage users to increase this limit during setup, but modifying + // the "[mysqld]" section of the configuration file (instead of + // "[mysqldump]" section) won't apply to "mysqldump" and we can not easily + // detect what the "mysqldump" setting is. + + // Since no user would ever reasonably want a dump to fail because a row + // was too large, just manually force this setting to the largest supported + // value. + + $argv[] = '--max-allowed-packet'; + $argv[] = '1G'; + if ($port) { $argv[] = '--port'; $argv[] = $port; From e411d75964e51686ce95d9a9cc245193160f9f70 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Dec 2017 06:26:38 -0800 Subject: [PATCH 322/865] Fix an issue where blame could fatal for unrecognized authors Summary: See PHI255. See . Test Plan: - Viewed a file contributed to by users with no Phabricator user accounts, in Diffusion. - Enabled blame. - Before patch: blame failed, fatal in logs. - After patch: blame worked. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18833 --- .../controller/DiffusionBrowseController.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 77ec8e46ca..92181ff551 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1804,10 +1804,17 @@ private function renderAuthorLinks(array $authors, $handles) { // revision. We just render a blank for alignment. $style = null; $href = null; + $sigil = null; + $meta = null; } else { $src = $handles[$phid]->getImageURI(); $style = 'background-image: url('http://github.com/.$src.');'; $href = $handles[$phid]->getURI(); + $sigil = 'has-tooltip'; + $meta = array( + 'tip' => $handles[$phid]->getName(), + 'align' => 'E', + ); } $links[$phid] = javelin_tag( @@ -1816,11 +1823,8 @@ private function renderAuthorLinks(array $authors, $handles) { 'class' => 'diffusion-author-link', 'style' => $style, 'href' => $href, - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $handles[$phid]->getName(), - 'align' => 'E', - ), + 'sigil' => $sigil, + 'meta' => $meta, )); } From 563d42d40e15d2adf1ff5ae3958a853846f304a7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 18 Dec 2017 06:26:38 -0800 Subject: [PATCH 323/865] (stable) Fix an issue where blame could fatal for unrecognized authors Summary: See PHI255. See . Test Plan: - Viewed a file contributed to by users with no Phabricator user accounts, in Diffusion. - Enabled blame. - Before patch: blame failed, fatal in logs. - After patch: blame worked. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18833 --- .../controller/DiffusionBrowseController.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 77ec8e46ca..92181ff551 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1804,10 +1804,17 @@ private function renderAuthorLinks(array $authors, $handles) { // revision. We just render a blank for alignment. $style = null; $href = null; + $sigil = null; + $meta = null; } else { $src = $handles[$phid]->getImageURI(); $style = 'background-image: url('http://github.com/.$src.');'; $href = $handles[$phid]->getURI(); + $sigil = 'has-tooltip'; + $meta = array( + 'tip' => $handles[$phid]->getName(), + 'align' => 'E', + ); } $links[$phid] = javelin_tag( @@ -1816,11 +1823,8 @@ private function renderAuthorLinks(array $authors, $handles) { 'class' => 'diffusion-author-link', 'style' => $style, 'href' => $href, - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => $handles[$phid]->getName(), - 'align' => 'E', - ), + 'sigil' => $sigil, + 'meta' => $meta, )); } From 393824656fcc222b8e0e9a87614ca189794d8208 Mon Sep 17 00:00:00 2001 From: Dmitri Iouchtchenko Date: Thu, 21 Dec 2017 12:46:45 -0800 Subject: [PATCH 324/865] Don't notify without notifiable attendees Summary: Events with no attendees (e.g. fresh instances of recurring events) would trigger an exception when sending notifications, because `$attendee_map` would still get populated. Test Plan: Declined event, restarted daemons. Did not see exception. Accepted event, restarted daemons. Saw "[Calendar] [Reminder]" email. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D18835 --- .../notifications/PhabricatorCalendarNotificationEngine.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php index 4ae007fc0c..59d8476c87 100644 --- a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php +++ b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php @@ -100,10 +100,11 @@ private function sendNotifications() { } $notifiable_phids[] = $invitee->getInviteePHID(); } - if (!$notifiable_phids) { + if ($notifiable_phids) { + $attendee_map[$key] = array_fuse($notifiable_phids); + } else { unset($events[$key]); } - $attendee_map[$key] = array_fuse($notifiable_phids); } if (!$attendee_map) { // None of the events have any notifiable attendees, so there is no From 84cf4938798473a4e0c370702fae56d24116052f Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 23 Dec 2017 08:36:58 -0800 Subject: [PATCH 325/865] Allow "Manage" to be the default menu item for projects Summary: Fixes T13033. Currently (prior to D18836) all the default items for projects can be hidden. When this occurs, the main project page fatals. If we fix the fatal narrowly (don't try to call `null->getBuiltinKey()` when `$default` is `null`), it would 404, which is a little better but not by much. After D18836 you can't hide "Profile", which is pretty sensible, and effectively fixes this. This change doubles down: let "Manage" be a default, and send the user there if we can't find a different default. Ideally, the MenuEngine itself will do more rendering eventually (as it does for some of the newer Home stuff) and could handle this defaulting behavior with less special casing, but we'd still end up in a similar situation if a project had only one "Link" item to "coolcats.com" or something: redirecting the user to "coolcats.com" is probably better than fataling, but not by a huge margin, and not likely to be what they expect. Test Plan: Before D18836, disabled both "Profile" and "Workboard" items on a project. Visited project page. Before patch: fatal. After patch: manage page. After D18836, you can't do this and just get the profile, so this is sort of moot and mostly future-proofing/for-completeness. Reviewers: 20after4, amckinley Reviewed By: amckinley Maniphest Tasks: T13033 Differential Revision: https://secure.phabricator.com/D18843 --- .../controller/PhabricatorProjectViewController.php | 11 +++++++++++ .../PhabricatorProjectManageProfileMenuItem.php | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/applications/project/controller/PhabricatorProjectViewController.php b/src/applications/project/controller/PhabricatorProjectViewController.php index 7d5dc37e0b..2e53fe7276 100644 --- a/src/applications/project/controller/PhabricatorProjectViewController.php +++ b/src/applications/project/controller/PhabricatorProjectViewController.php @@ -20,6 +20,14 @@ public function handleRequest(AphrontRequest $request) { $engine = $this->getProfileMenuEngine(); $default = $engine->getDefaultItem(); + // If defaults are broken somehow, serve the manage page. See T13033 for + // discussion. + if ($default) { + $default_key = $default->getBuiltinKey(); + } else { + $default_key = PhabricatorProject::ITEM_MANAGE; + } + switch ($default->getBuiltinKey()) { case PhabricatorProject::ITEM_WORKBOARD: $controller_object = new PhabricatorProjectBoardViewController(); @@ -27,6 +35,9 @@ public function handleRequest(AphrontRequest $request) { case PhabricatorProject::ITEM_PROFILE: $controller_object = new PhabricatorProjectProfileController(); break; + case PhabricatorProject::ITEM_MANAGE: + $controller_object = new PhabricatorProjectManageController(); + break; default: return $engine->buildResponse(); } diff --git a/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php index ddb59ec095..1bd7e796dc 100644 --- a/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectManageProfileMenuItem.php @@ -18,6 +18,11 @@ public function canHideMenuItem( return false; } + public function canMakeDefault( + PhabricatorProfileMenuItemConfiguration $config) { + return true; + } + public function getDisplayName( PhabricatorProfileMenuItemConfiguration $config) { $name = $config->getMenuItemProperty('name'); From bd5aa0c90fd1816830948a9e118a9e514145f813 Mon Sep 17 00:00:00 2001 From: Mukunda Modell Date: Sat, 23 Dec 2017 11:38:05 -0800 Subject: [PATCH 326/865] Prevent hiding the PhabricatorProjectDetailsProfileMenuItem Summary: This probably isn't the best solution, however, it conveniently avoids the bug from T13033. It would probably be more user-friendly (but more difficult to implement) if we allowed either Project Details //or// Workboard to be hidden but not both. Test Plan: Tested locally, indeed this prevents hiding the menu item. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18836 --- .../menuitem/PhabricatorProjectDetailsProfileMenuItem.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php index 96619b8ec5..b779f4be90 100644 --- a/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php +++ b/src/applications/project/menuitem/PhabricatorProjectDetailsProfileMenuItem.php @@ -13,6 +13,11 @@ private function getDefaultName() { return pht('Project Details'); } + public function canHideMenuItem( + PhabricatorProfileMenuItemConfiguration $config) { + return false; + } + public function canMakeDefault( PhabricatorProfileMenuItemConfiguration $config) { return true; From 66b74073be68007ae918e4e4115df8b9b8c68fe8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 22 Dec 2017 09:59:43 -0800 Subject: [PATCH 327/865] Provide ANSI color information for Harbormaster build status via API Summary: Ref PHI261. This moves Harbormaster build status to work more similarly to other modern status types, like Differential revision status, where we try to specify as much behavior on the server as possible so that the client and server can vary independently. (I don't have any specific plans to make Harbormaster build status configurable on the server side, but it isn't out of the question.) Test Plan: Ran `harbormaster.querybuilds` (saw same data as before) and `harbormaster.build.search` (saw same data as before, plus new ANSI color data). Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18838 --- ...arbormasterQueryBuildsConduitAPIMethod.php | 10 ++ .../constants/HarbormasterBuildStatus.php | 128 +++++++++++------- .../storage/build/HarbormasterBuild.php | 2 + 3 files changed, 88 insertions(+), 52 deletions(-) diff --git a/src/applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php index a76f53a362..c4622a902c 100644 --- a/src/applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php @@ -65,6 +65,16 @@ protected function execute(ConduitAPIRequest $request) { $fields = idx($build_data, 'fields', array()); unset($build_data['fields']); unset($build_data['attachments']); + + // To retain backward compatibility, remove newer keys from the + // result array. + $fields['buildStatus'] = array_select_keys( + $fields['buildStatus'], + array( + 'value', + 'name', + )); + $data[] = array_mergev(array($build_data, $querybuilds, $fields)); } diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php index a2cdba5b63..b0e3105d78 100644 --- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php @@ -55,67 +55,28 @@ final class HarbormasterBuildStatus extends Phobject { * @return string Human-readable name. */ public static function getBuildStatusName($status) { - $map = self::getBuildStatusMap(); - return idx($map, $status, pht('Unknown ("%s")', $status)); + $spec = self::getBuildStatusSpec($status); + return idx($spec, 'name', pht('Unknown ("%s")', $status)); } public static function getBuildStatusMap() { - return array( - self::STATUS_INACTIVE => pht('Inactive'), - self::STATUS_PENDING => pht('Pending'), - self::STATUS_BUILDING => pht('Building'), - self::STATUS_PASSED => pht('Passed'), - self::STATUS_FAILED => pht('Failed'), - self::STATUS_ABORTED => pht('Aborted'), - self::STATUS_ERROR => pht('Unexpected Error'), - self::STATUS_PAUSED => pht('Paused'), - self::STATUS_DEADLOCKED => pht('Deadlocked'), - ); + $specs = self::getBuildStatusSpecMap(); + return ipull($specs, 'name'); } public static function getBuildStatusIcon($status) { - switch ($status) { - case self::STATUS_INACTIVE: - case self::STATUS_PENDING: - return PHUIStatusItemView::ICON_OPEN; - case self::STATUS_BUILDING: - return PHUIStatusItemView::ICON_RIGHT; - case self::STATUS_PASSED: - return PHUIStatusItemView::ICON_ACCEPT; - case self::STATUS_FAILED: - return PHUIStatusItemView::ICON_REJECT; - case self::STATUS_ABORTED: - return PHUIStatusItemView::ICON_MINUS; - case self::STATUS_ERROR: - return PHUIStatusItemView::ICON_MINUS; - case self::STATUS_PAUSED: - return PHUIStatusItemView::ICON_MINUS; - case self::STATUS_DEADLOCKED: - return PHUIStatusItemView::ICON_WARNING; - default: - return PHUIStatusItemView::ICON_QUESTION; - } + $spec = self::getBuildStatusSpec($status); + return idx($spec, 'icon', 'fa-question-circle'); } public static function getBuildStatusColor($status) { - switch ($status) { - case self::STATUS_INACTIVE: - return 'dark'; - case self::STATUS_PENDING: - case self::STATUS_BUILDING: - return 'blue'; - case self::STATUS_PASSED: - return 'green'; - case self::STATUS_FAILED: - case self::STATUS_ABORTED: - case self::STATUS_ERROR: - case self::STATUS_DEADLOCKED: - return 'red'; - case self::STATUS_PAUSED: - return 'dark'; - default: - return 'bluegrey'; - } + $spec = self::getBuildStatusSpec($status); + return idx($spec, 'color', 'bluegrey'); + } + + public static function getBuildStatusANSIColor($status) { + $spec = self::getBuildStatusSpec($status); + return idx($spec, 'color.ansi', 'magenta'); } public static function getWaitingStatusConstants() { @@ -142,4 +103,67 @@ public static function getCompletedStatusConstants() { ); } + private static function getBuildStatusSpecMap() { + return array( + self::STATUS_INACTIVE => array( + 'name' => pht('Inactive'), + 'icon' => 'fa-circle-o', + 'color' => 'dark', + 'color.ansi' => 'yellow', + ), + self::STATUS_PENDING => array( + 'name' => pht('Pending'), + 'icon' => 'fa-circle-o', + 'color' => 'blue', + 'color.ansi' => 'yellow', + ), + self::STATUS_BUILDING => array( + 'name' => pht('Building'), + 'icon' => 'fa-chevron-circle-right', + 'color' => 'blue', + 'color.ansi' => 'yellow', + ), + self::STATUS_PASSED => array( + 'name' => pht('Passed'), + 'icon' => 'fa-check-circle', + 'color' => 'green', + 'color.ansi' => 'green', + ), + self::STATUS_FAILED => array( + 'name' => pht('Failed'), + 'icon' => 'fa-times-circle', + 'color' => 'red', + 'color.ansi' => 'red', + ), + self::STATUS_ABORTED => array( + 'name' => pht('Aborted'), + 'icon' => 'fa-minus-circle', + 'color' => 'red', + 'color.ansi' => 'red', + ), + self::STATUS_ERROR => array( + 'name' => pht('Unexpected Error'), + 'icon' => 'fa-minus-circle', + 'color' => 'red', + 'color.ansi' => 'red', + ), + self::STATUS_PAUSED => array( + 'name' => pht('Paused'), + 'icon' => 'fa-minus-circle', + 'color' => 'dark', + 'color.ansi' => 'yellow', + ), + self::STATUS_DEADLOCKED => array( + 'name' => pht('Deadlocked'), + 'icon' => 'fa-exclamation-circle', + 'color' => 'red', + 'color.ansi' => 'red', + ), + ); + } + + private static function getBuildStatusSpec($status) { + return idx(self::getBuildStatusSpecMap(), $status, array()); + } + } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 958eaa1f2b..92d4293913 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -435,6 +435,8 @@ public function getFieldValuesForConduit() { 'buildStatus' => array( 'value' => $status, 'name' => HarbormasterBuildStatus::getBuildStatusName($status), + 'color.ansi' => + HarbormasterBuildStatus::getBuildStatusANSIColor($status), ), 'initiatorPHID' => nonempty($this->getInitiatorPHID(), null), 'name' => $this->getName(), From ad4db9b2f3f83ab2506de9897d159917454cad5b Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 22 Dec 2017 11:55:39 -0800 Subject: [PATCH 328/865] Separate "Set/Reset Password" from "Change Password" Summary: See PHI223. Ref T13024. There's a remaining registration/login order issue after the other changes in T13024: we lose track of the current URI when we go through the MFA flow, so we can lose "Set Password" at the end of the flow. Specifically, the flow goes like this today: - User clicks the welcome link in email. - They get redirected to the "set password" settings panel. - This gets pre-empted by Legalpad (although we'll potentially survive this with the URI intact). - This also gets pre-empted by the "Set MFA" workflow. If the user completes this flow, they get redirected to a `/auth/multifactor/?id=123` sort of URI to highlight the factor they added. This causes us to lose the `/settings/panel/password/blah/blah?key=xyz` URI. The ordering on this is also not ideal; it's preferable to start with a password, then do the other steps, so the user can return to the flow more easily if they are interrupted. Resolve this by separating the "change your password" and "set/reset your password" flows onto two different pages. This copy/pastes a bit of code, but both flows end up simpler so it feels reasonable to me overall. We don't require a full session for "set/reset password" (so you can do it if you don't have MFA/legalpad yet) and do it first. This works better and is broadly simpler for users. Test Plan: - Required MFA + legalpad, invited a user via email, registered. - Before: password set flow got lost when setting MFA. - After: prompted to set password, then sign documents, then set up MFA. - Reset password (with MFA confgiured, was required to MFA first). - Tried to reset password without a valid reset key, wasn't successful. - Changed password using existing flow. - Hit various (all?) error cases (short password, common password, mismatch, missing password, etc). Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13024 Differential Revision: https://secure.phabricator.com/D18840 --- src/__phutil_library_map__.php | 2 + .../PhabricatorAuthApplication.php | 1 + .../PhabricatorAuthOneTimeLoginController.php | 3 +- .../PhabricatorAuthSetPasswordController.php | 155 ++++++++++++++++++ .../people/storage/PhabricatorUser.php | 4 + .../PhabricatorPasswordSettingsPanel.php | 79 +++------ 6 files changed, 185 insertions(+), 59 deletions(-) create mode 100644 src/applications/auth/controller/PhabricatorAuthSetPasswordController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f86b3a1d2d..f031f7deca 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2112,6 +2112,7 @@ 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', 'PhabricatorAuthSessionInfo' => 'applications/auth/data/PhabricatorAuthSessionInfo.php', 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', + 'PhabricatorAuthSetPasswordController' => 'applications/auth/controller/PhabricatorAuthSetPasswordController.php', 'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'applications/auth/factor/PhabricatorAuthTOTPKeyTemporaryTokenType.php', @@ -7377,6 +7378,7 @@ 'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthSessionInfo' => 'Phobject', 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorAuthSetPasswordController' => 'PhabricatorAuthController', 'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 'PhabricatorAuthTOTPKeyTemporaryTokenType' => 'PhabricatorAuthTemporaryTokenType', diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php index dfc855f315..a6127394fd 100644 --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -84,6 +84,7 @@ public function getRoutes() { => 'PhabricatorAuthSSHKeyDeactivateController', 'view/(?P\d+)/' => 'PhabricatorAuthSSHKeyViewController', ), + 'password/' => 'PhabricatorAuthSetPasswordController', ), '/oauth/(?P\w+)/login/' diff --git a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php index 534bda3f35..9f74d50765 100644 --- a/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php +++ b/src/applications/auth/controller/PhabricatorAuthOneTimeLoginController.php @@ -139,8 +139,7 @@ public function handleRequest(AphrontRequest $request) { ->save(); unset($unguarded); - $username = $target_user->getUsername(); - $panel_uri = "/settings/user/{$username}/page/password/"; + $panel_uri = '/auth/password/'; $next = (string)id(new PhutilURI($panel_uri)) ->setQueryParams( diff --git a/src/applications/auth/controller/PhabricatorAuthSetPasswordController.php b/src/applications/auth/controller/PhabricatorAuthSetPasswordController.php new file mode 100644 index 0000000000..0db23e52a7 --- /dev/null +++ b/src/applications/auth/controller/PhabricatorAuthSetPasswordController.php @@ -0,0 +1,155 @@ +getViewer(); + + if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) { + return new Aphront404Response(); + } + + $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( + $viewer, + $request, + '/'); + + $key = $request->getStr('key'); + $password_type = PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; + if (!$key) { + return new Aphront404Response(); + } + + $auth_token = id(new PhabricatorAuthTemporaryTokenQuery()) + ->setViewer($viewer) + ->withTokenResources(array($viewer->getPHID())) + ->withTokenTypes(array($password_type)) + ->withTokenCodes(array(PhabricatorHash::weakDigest($key))) + ->withExpired(false) + ->executeOne(); + if (!$auth_token) { + return new Aphront404Response(); + } + + $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); + $min_len = (int)$min_len; + + $e_password = true; + $e_confirm = true; + $errors = array(); + if ($request->isFormPost()) { + $password = $request->getStr('password'); + $confirm = $request->getStr('confirm'); + + $e_password = null; + $e_confirm = null; + + if (!strlen($password)) { + $errors[] = pht('You must choose a password or skip this step.'); + $e_password = pht('Required'); + } else if (strlen($password) < $min_len) { + $errors[] = pht( + 'The selected password is too short. Passwords must be a minimum '. + 'of %s characters.', + new PhutilNumber($min_len)); + $e_password = pht('Too Short'); + } else if (!strlen($confirm)) { + $errors[] = pht('You must confirm the selecetd password.'); + $e_confirm = pht('Required'); + } else if ($password !== $confirm) { + $errors[] = pht('The password and confirmation do not match.'); + $e_password = pht('Invalid'); + $e_confirm = pht('Invalid'); + } else if (PhabricatorCommonPasswords::isCommonPassword($password)) { + $e_password = pht('Very Weak'); + $errors[] = pht( + 'The selected password is very weak: it is one of the most common '. + 'passwords in use. Choose a stronger password.'); + } + + if (!$errors) { + $envelope = new PhutilOpaqueEnvelope($password); + + // This write is unguarded because the CSRF token has already + // been checked in the call to $request->isFormPost() and + // the CSRF token depends on the password hash, so when it + // is changed here the CSRF token check will fail. + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + + id(new PhabricatorUserEditor()) + ->setActor($viewer) + ->changePassword($viewer, $envelope); + + unset($unguarded); + + // Destroy the token. + $auth_token->delete(); + + return id(new AphrontRedirectResponse())->setURI('/'); + } + } + + $len_caption = null; + if ($min_len) { + $len_caption = pht('Minimum password length: %d characters.', $min_len); + } + + if ($viewer->hasPassword()) { + $title = pht('Reset Password'); + $crumb = pht('Reset Password'); + $submit = pht('Reset Password'); + } else { + $title = pht('Set Password'); + $crumb = pht('Set Password'); + $submit = pht('Set Account Password'); + } + + $form = id(new AphrontFormView()) + ->setViewer($viewer) + ->addHiddenInput('key', $key) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setDisableAutocomplete(true) + ->setLabel(pht('New Password')) + ->setError($e_password) + ->setName('password')) + ->appendChild( + id(new AphrontFormPasswordControl()) + ->setDisableAutocomplete(true) + ->setLabel(pht('Confirm Password')) + ->setCaption($len_caption) + ->setError($e_confirm) + ->setName('confirm')) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton('/', pht('Skip This Step')) + ->setValue($submit)); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::WHITE_CONFIG) + ->setForm($form); + + $main_view = id(new PHUITwoColumnView()) + ->setFooter($form_box); + + $crumbs = $this->buildApplicationCrumbs() + ->addTextCrumb($crumb) + ->setBorder(true); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($main_view); + } +} diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 1745154826..30aa3d81ef 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -262,6 +262,10 @@ public function generatePHID() { PhabricatorPeopleUserPHIDType::TYPECONST); } + public function hasPassword() { + return (bool)strlen($this->passwordHash); + } + public function setPassword(PhutilOpaqueEnvelope $envelope) { if (!$this->getPHID()) { throw new Exception( diff --git a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php index c1250fca23..3807139fe3 100644 --- a/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorPasswordSettingsPanel.php @@ -35,23 +35,10 @@ public function processRequest(AphrontRequest $request) { $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); $min_len = (int)$min_len; - // NOTE: To change your password, you need to prove you own the account, - // either by providing the old password or by carrying a token to - // the workflow from a password reset email. - - $key = $request->getStr('key'); - $password_type = PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE; - - $token = null; - if ($key) { - $token = id(new PhabricatorAuthTemporaryTokenQuery()) - ->setViewer($user) - ->withTokenResources(array($user->getPHID())) - ->withTokenTypes(array($password_type)) - ->withTokenCodes(array(PhabricatorHash::weakDigest($key))) - ->withExpired(false) - ->executeOne(); - } + // NOTE: Users can also change passwords through the separate "set/reset" + // interface which is reached by logging in with a one-time token after + // registration or password reset. If this flow changes, that flow may + // also need to change. $e_old = true; $e_new = true; @@ -59,12 +46,10 @@ public function processRequest(AphrontRequest $request) { $errors = array(); if ($request->isFormPost()) { - if (!$token) { - $envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw')); - if (!$user->comparePassword($envelope)) { - $errors[] = pht('The old password you entered is incorrect.'); - $e_old = pht('Invalid'); - } + $envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw')); + if (!$user->comparePassword($envelope)) { + $errors[] = pht('The old password you entered is incorrect.'); + $e_old = pht('Invalid'); } $pass = $request->getStr('new_pw'); @@ -98,16 +83,7 @@ public function processRequest(AphrontRequest $request) { unset($unguarded); - if ($token) { - // Destroy the token. - $token->delete(); - - // If this is a password set/reset, kick the user to the home page - // after we update their account. - $next = '/'; - } else { - $next = $this->getPanelURI('?saved=true'); - } + $next = $this->getPanelURI('?saved=true'); id(new PhabricatorAuthSessionEngine())->terminateLoginSessions( $user, @@ -125,19 +101,15 @@ public function processRequest(AphrontRequest $request) { } catch (PhabricatorPasswordHasherUnavailableException $ex) { $can_upgrade = false; - // Only show this stuff if we aren't on the reset workflow. We can - // do resets regardless of the old hasher's availability. - if (!$token) { - $errors[] = pht( - 'Your password is currently hashed using an algorithm which is '. - 'no longer available on this install.'); - $errors[] = pht( - 'Because the algorithm implementation is missing, your password '. - 'can not be used or updated.'); - $errors[] = pht( - 'To set a new password, request a password reset link from the '. - 'login screen and then follow the instructions.'); - } + $errors[] = pht( + 'Your password is currently hashed using an algorithm which is '. + 'no longer available on this install.'); + $errors[] = pht( + 'Because the algorithm implementation is missing, your password '. + 'can not be used or updated.'); + $errors[] = pht( + 'To set a new password, request a password reset link from the '. + 'login screen and then follow the instructions.'); } if ($can_upgrade) { @@ -153,20 +125,13 @@ public function processRequest(AphrontRequest $request) { $len_caption = pht('Minimum password length: %d characters.', $min_len); } - $form = new AphrontFormView(); - $form - ->setUser($user) - ->addHiddenInput('key', $key); - - if (!$token) { - $form->appendChild( + $form = id(new AphrontFormView()) + ->setViewer($user) + ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel(pht('Old Password')) ->setError($e_old) - ->setName('old_pw')); - } - - $form + ->setName('old_pw')) ->appendChild( id(new AphrontFormPasswordControl()) ->setDisableAutocomplete(true) From ead5f4fd9c03f3e2a9939a190739d3e2e65f45e2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 22 Dec 2017 10:11:10 -0800 Subject: [PATCH 329/865] Add an "Accepting reviewers" Herald field for commits Summary: See PHI262. Fixes T12578. Although this is a bit niche and probably better accomplished through advisory/soft measures ("Add blocking reviewers") in most cases, it isn't difficult to implement and doesn't create any technical or product tension. If installs write a rule that blocks commits, that will probably also naturally lead them to an "add reviewers" rule anyway. Also, allow packages to be hit with the typeahead. They're valid reviewers but previously you couldn't write rules against them, for no actual reason. Test Plan: Used test console to run this against commits, got sensible results for the field value. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12578 Differential Revision: https://secure.phabricator.com/D18839 --- src/__phutil_library_map__.php | 4 ++ ...tRevisionAcceptingReviewersHeraldField.php | 43 +++++++++++++++++++ ...sionCommitRevisionReviewersHeraldField.php | 2 +- ...tRevisionAcceptingReviewersHeraldField.php | 43 +++++++++++++++++++ ...mitContentRevisionReviewersHeraldField.php | 2 +- 5 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php create mode 100644 src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f031f7deca..849d4d4702 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -696,6 +696,7 @@ 'DiffusionCommitRevertsCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php', 'DiffusionCommitReviewerHeraldField' => 'applications/diffusion/herald/DiffusionCommitReviewerHeraldField.php', 'DiffusionCommitRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php', + 'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php', 'DiffusionCommitRevisionHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionHeraldField.php', 'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php', 'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php', @@ -809,6 +810,7 @@ 'DiffusionPreCommitContentRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryHeraldField.php', 'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryProjectsHeraldField.php', 'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php', + 'DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php', 'DiffusionPreCommitContentRevisionHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionHeraldField.php', 'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php', 'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionSubscribersHeraldField.php', @@ -5755,6 +5757,7 @@ 'DiffusionCommitRevertsCommitEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitReviewerHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionAcceptedHeraldField' => 'DiffusionCommitHeraldField', + 'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField', @@ -5871,6 +5874,7 @@ 'DiffusionPreCommitContentRepositoryHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'DiffusionPreCommitContentHeraldField', + 'DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'DiffusionPreCommitContentHeraldField', diff --git a/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php new file mode 100644 index 0000000000..6d266f1357 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php @@ -0,0 +1,43 @@ +getAdapter()->loadDifferentialRevision(); + + if (!$revision) { + return array(); + } + + $diff_phid = $revision->getActiveDiffPHID(); + + $reviewer_phids = array(); + foreach ($revision->getReviewers() as $reviewer) { + if ($reviewer->isAccepted($diff_phid)) { + $reviewer_phids[] = $reviewer->getReviewerPHID(); + } + } + + return $reviewer_phids; + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new DifferentialReviewerDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php index 50900ede78..a9a3f3e8ca 100644 --- a/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php @@ -28,7 +28,7 @@ protected function getHeraldFieldStandardType() { } protected function getDatasource() { - return new PhabricatorProjectOrUserDatasource(); + return new DifferentialReviewerDatasource(); } } diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php new file mode 100644 index 0000000000..b6d50d4a2a --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php @@ -0,0 +1,43 @@ +getAdapter()->getRevision(); + + if (!$revision) { + return array(); + } + + $diff_phid = $revision->getActiveDiffPHID(); + + $reviewer_phids = array(); + foreach ($revision->getReviewers() as $reviewer) { + if ($reviewer->isAccepted($diff_phid)) { + $reviewer_phids[] = $reviewer->getReviewerPHID(); + } + } + + return $reviewer_phids; + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new DifferentialReviewerDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php index 936126ba89..aa2b2e9e09 100644 --- a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php @@ -28,7 +28,7 @@ protected function getHeraldFieldStandardType() { } protected function getDatasource() { - return new PhabricatorProjectOrUserDatasource(); + return new DifferentialReviewerDatasource(); } } From 153c054658754214f32b1d428f0ab14fbeca1b7c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 22 Dec 2017 10:11:10 -0800 Subject: [PATCH 330/865] (stable) Add an "Accepting reviewers" Herald field for commits Summary: See PHI262. Fixes T12578. Although this is a bit niche and probably better accomplished through advisory/soft measures ("Add blocking reviewers") in most cases, it isn't difficult to implement and doesn't create any technical or product tension. If installs write a rule that blocks commits, that will probably also naturally lead them to an "add reviewers" rule anyway. Also, allow packages to be hit with the typeahead. They're valid reviewers but previously you couldn't write rules against them, for no actual reason. Test Plan: Used test console to run this against commits, got sensible results for the field value. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12578 Differential Revision: https://secure.phabricator.com/D18839 --- src/__phutil_library_map__.php | 4 ++ ...tRevisionAcceptingReviewersHeraldField.php | 43 +++++++++++++++++++ ...sionCommitRevisionReviewersHeraldField.php | 2 +- ...tRevisionAcceptingReviewersHeraldField.php | 43 +++++++++++++++++++ ...mitContentRevisionReviewersHeraldField.php | 2 +- 5 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php create mode 100644 src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f031f7deca..849d4d4702 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -696,6 +696,7 @@ 'DiffusionCommitRevertsCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php', 'DiffusionCommitReviewerHeraldField' => 'applications/diffusion/herald/DiffusionCommitReviewerHeraldField.php', 'DiffusionCommitRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php', + 'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php', 'DiffusionCommitRevisionHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionHeraldField.php', 'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php', 'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php', @@ -809,6 +810,7 @@ 'DiffusionPreCommitContentRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryHeraldField.php', 'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryProjectsHeraldField.php', 'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php', + 'DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php', 'DiffusionPreCommitContentRevisionHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionHeraldField.php', 'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php', 'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionSubscribersHeraldField.php', @@ -5755,6 +5757,7 @@ 'DiffusionCommitRevertsCommitEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitReviewerHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionAcceptedHeraldField' => 'DiffusionCommitHeraldField', + 'DiffusionCommitRevisionAcceptingReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField', @@ -5871,6 +5874,7 @@ 'DiffusionPreCommitContentRepositoryHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'DiffusionPreCommitContentHeraldField', + 'DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'DiffusionPreCommitContentHeraldField', diff --git a/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php new file mode 100644 index 0000000000..6d266f1357 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionCommitRevisionAcceptingReviewersHeraldField.php @@ -0,0 +1,43 @@ +getAdapter()->loadDifferentialRevision(); + + if (!$revision) { + return array(); + } + + $diff_phid = $revision->getActiveDiffPHID(); + + $reviewer_phids = array(); + foreach ($revision->getReviewers() as $reviewer) { + if ($reviewer->isAccepted($diff_phid)) { + $reviewer_phids[] = $reviewer->getReviewerPHID(); + } + } + + return $reviewer_phids; + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new DifferentialReviewerDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php index 50900ede78..a9a3f3e8ca 100644 --- a/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php @@ -28,7 +28,7 @@ protected function getHeraldFieldStandardType() { } protected function getDatasource() { - return new PhabricatorProjectOrUserDatasource(); + return new DifferentialReviewerDatasource(); } } diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php new file mode 100644 index 0000000000..b6d50d4a2a --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptingReviewersHeraldField.php @@ -0,0 +1,43 @@ +getAdapter()->getRevision(); + + if (!$revision) { + return array(); + } + + $diff_phid = $revision->getActiveDiffPHID(); + + $reviewer_phids = array(); + foreach ($revision->getReviewers() as $reviewer) { + if ($reviewer->isAccepted($diff_phid)) { + $reviewer_phids[] = $reviewer->getReviewerPHID(); + } + } + + return $reviewer_phids; + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new DifferentialReviewerDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php index 936126ba89..aa2b2e9e09 100644 --- a/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php @@ -28,7 +28,7 @@ protected function getHeraldFieldStandardType() { } protected function getDatasource() { - return new PhabricatorProjectOrUserDatasource(); + return new DifferentialReviewerDatasource(); } } From de6c68b91e57208b7ccb454d53916553d79c667c Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 3 Jan 2018 13:40:44 -0800 Subject: [PATCH 331/865] Always show "X requested review" in mail to stop some undraft mail from being dropped Summary: Ref T13035. See that task for a description of the issue. Test Plan: - Enabled prototypes. - Disabled all Herald rules that trigger Harbormaster builds. - Created a new revision. - Before patch: initial review request email was dropped. - After patch: initial review request email is sent. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13035 Differential Revision: https://secure.phabricator.com/D18851 --- .../differential/storage/DifferentialTransaction.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php index ea0d7789cb..18edd3c625 100644 --- a/src/applications/differential/storage/DifferentialTransaction.php +++ b/src/applications/differential/storage/DifferentialTransaction.php @@ -111,6 +111,12 @@ public function shouldHideForMail(array $xactions) { // Don't hide the initial "X added reviewers: ..." transaction during // object creation from mail. See T12118 and PHI54. return false; + case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE: + // Don't hide the initial "X requested review: ..." transaction from + // mail even when it occurs during creation. We need this transaction + // to survive so we'll generate mail when revisions immediately leave + // the draft state. See T13035 for discussion. + return false; } return parent::shouldHideForMail($xactions); From 129e3a120849ada69ecda9f7a475cb9522080eb5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 3 Jan 2018 13:47:56 -0800 Subject: [PATCH 332/865] Fix a minor/harmless race with feed publishers in certain draft states Summary: Depends on D18851. Ref T13035. After D18819, revision creation transactions may be split into two groups (if prototypes are enabled). This split means we have two workers. The first worker doesn't publish feed stories or mail; the second one does. Currently, both workers call `shouldPublishFeedStory()` before they queue, and then again after the daemons pull them out of the queue. However, the answer to this question can change. Specifically, this happens: - `arc` creates a revision. - The first transaction group applies, creating the revision as a draft, and returns `false` from `shouldPublishFeedStory()`, and does not generate related PHIDs. It queues a daemon to send mail, expecting it not to publish a feed story. - The second transaction group applies, promoting the revision to "needs review". Since the revision has promoted, `shouldPublishFeedStory()` now returns true. This editor generates related PHIDs and queues a daemon task, expecting it to send mail / publish feed. - A few milliseconds pass. - The first job gets pulled out of the daemon queue and worked on. It does not have any feed metadata because the object wasn't publishable when the job was queued -- but `shouldPublishFeedStory()` now returns true, so it tries to publish a story without any metadata available. Slightly bad stuff happens (see below). - The second job gets pulled out of the daemon queue and worked on. This one has metadata and works fine. The "slightly bad stuff" is that we publish an empty feed story with no references to any objects, then try to push it to hooks and other listeners. Since it doesn't have any references, it fails to load during the "push to external listeners" phase. This is harmless but clutters the log and doesn't help anything. Instead, cache the state of "are we publishing a feed story for this object?" when we queue the worker so it can't race. Test Plan: - Enabled prototypes. - Disabled all Herald triggers for Harbormaster build plans. - Ran `bin/phd debug task` in one window. - Created a revision in a separate window. - Before patch: saw "unable to load feed story" errors in the daemon log. - After patch: no more "unable to load feed story" errors. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13035 Differential Revision: https://secure.phabricator.com/D18852 --- src/applications/feed/worker/FeedPushWorker.php | 4 +++- .../editor/PhabricatorApplicationTransactionEditor.php | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/applications/feed/worker/FeedPushWorker.php b/src/applications/feed/worker/FeedPushWorker.php index 90407f6a75..3eff676e99 100644 --- a/src/applications/feed/worker/FeedPushWorker.php +++ b/src/applications/feed/worker/FeedPushWorker.php @@ -13,7 +13,9 @@ protected function loadFeedStory() { if (!$story) { throw new PhabricatorWorkerPermanentFailureException( - pht('Feed story does not exist.')); + pht( + 'Feed story (with key "%s") does not exist or could not be loaded.', + $key)); } return $story; diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 8e6d816769..c08451624d 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -68,6 +68,7 @@ abstract class PhabricatorApplicationTransactionEditor private $mailCCPHIDs = array(); private $feedNotifyPHIDs = array(); private $feedRelatedPHIDs = array(); + private $feedShouldPublish = false; private $modularTypes; private $transactionQueue = array(); @@ -1159,6 +1160,7 @@ final public function applyTransactions( } if ($this->shouldPublishFeedStory($object, $xactions)) { + $this->feedShouldPublish = true; $this->feedRelatedPHIDs = $this->getFeedRelatedPHIDs($object, $xactions); $this->feedNotifyPHIDs = $this->getFeedNotifyPHIDs($object, $xactions); } @@ -1216,8 +1218,7 @@ public function publishTransactions( )); } - if ($this->shouldPublishFeedStory($object, $xactions)) { - + if ($this->feedShouldPublish) { $mailed = array(); foreach ($messages as $mail) { foreach ($mail->buildRecipientList() as $phid) { @@ -3512,6 +3513,7 @@ private function getAutomaticStateProperties() { 'mailCCPHIDs', 'feedNotifyPHIDs', 'feedRelatedPHIDs', + 'feedShouldPublish', ); } From 33f7f65e81d49c589241a945dda2651bad0ac6e3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 3 Jan 2018 13:40:44 -0800 Subject: [PATCH 333/865] (stable) Always show "X requested review" in mail to stop some undraft mail from being dropped Summary: Ref T13035. See that task for a description of the issue. Test Plan: - Enabled prototypes. - Disabled all Herald rules that trigger Harbormaster builds. - Created a new revision. - Before patch: initial review request email was dropped. - After patch: initial review request email is sent. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13035 Differential Revision: https://secure.phabricator.com/D18851 --- .../differential/storage/DifferentialTransaction.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php index ea0d7789cb..18edd3c625 100644 --- a/src/applications/differential/storage/DifferentialTransaction.php +++ b/src/applications/differential/storage/DifferentialTransaction.php @@ -111,6 +111,12 @@ public function shouldHideForMail(array $xactions) { // Don't hide the initial "X added reviewers: ..." transaction during // object creation from mail. See T12118 and PHI54. return false; + case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE: + // Don't hide the initial "X requested review: ..." transaction from + // mail even when it occurs during creation. We need this transaction + // to survive so we'll generate mail when revisions immediately leave + // the draft state. See T13035 for discussion. + return false; } return parent::shouldHideForMail($xactions); From c12a6cac76030803555b37a52985b2335456d147 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 3 Jan 2018 13:47:56 -0800 Subject: [PATCH 334/865] (stable) Fix a minor/harmless race with feed publishers in certain draft states Summary: Depends on D18851. Ref T13035. After D18819, revision creation transactions may be split into two groups (if prototypes are enabled). This split means we have two workers. The first worker doesn't publish feed stories or mail; the second one does. Currently, both workers call `shouldPublishFeedStory()` before they queue, and then again after the daemons pull them out of the queue. However, the answer to this question can change. Specifically, this happens: - `arc` creates a revision. - The first transaction group applies, creating the revision as a draft, and returns `false` from `shouldPublishFeedStory()`, and does not generate related PHIDs. It queues a daemon to send mail, expecting it not to publish a feed story. - The second transaction group applies, promoting the revision to "needs review". Since the revision has promoted, `shouldPublishFeedStory()` now returns true. This editor generates related PHIDs and queues a daemon task, expecting it to send mail / publish feed. - A few milliseconds pass. - The first job gets pulled out of the daemon queue and worked on. It does not have any feed metadata because the object wasn't publishable when the job was queued -- but `shouldPublishFeedStory()` now returns true, so it tries to publish a story without any metadata available. Slightly bad stuff happens (see below). - The second job gets pulled out of the daemon queue and worked on. This one has metadata and works fine. The "slightly bad stuff" is that we publish an empty feed story with no references to any objects, then try to push it to hooks and other listeners. Since it doesn't have any references, it fails to load during the "push to external listeners" phase. This is harmless but clutters the log and doesn't help anything. Instead, cache the state of "are we publishing a feed story for this object?" when we queue the worker so it can't race. Test Plan: - Enabled prototypes. - Disabled all Herald triggers for Harbormaster build plans. - Ran `bin/phd debug task` in one window. - Created a revision in a separate window. - Before patch: saw "unable to load feed story" errors in the daemon log. - After patch: no more "unable to load feed story" errors. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13035 Differential Revision: https://secure.phabricator.com/D18852 --- src/applications/feed/worker/FeedPushWorker.php | 4 +++- .../editor/PhabricatorApplicationTransactionEditor.php | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/applications/feed/worker/FeedPushWorker.php b/src/applications/feed/worker/FeedPushWorker.php index 90407f6a75..3eff676e99 100644 --- a/src/applications/feed/worker/FeedPushWorker.php +++ b/src/applications/feed/worker/FeedPushWorker.php @@ -13,7 +13,9 @@ protected function loadFeedStory() { if (!$story) { throw new PhabricatorWorkerPermanentFailureException( - pht('Feed story does not exist.')); + pht( + 'Feed story (with key "%s") does not exist or could not be loaded.', + $key)); } return $story; diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 8e6d816769..c08451624d 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -68,6 +68,7 @@ abstract class PhabricatorApplicationTransactionEditor private $mailCCPHIDs = array(); private $feedNotifyPHIDs = array(); private $feedRelatedPHIDs = array(); + private $feedShouldPublish = false; private $modularTypes; private $transactionQueue = array(); @@ -1159,6 +1160,7 @@ final public function applyTransactions( } if ($this->shouldPublishFeedStory($object, $xactions)) { + $this->feedShouldPublish = true; $this->feedRelatedPHIDs = $this->getFeedRelatedPHIDs($object, $xactions); $this->feedNotifyPHIDs = $this->getFeedNotifyPHIDs($object, $xactions); } @@ -1216,8 +1218,7 @@ public function publishTransactions( )); } - if ($this->shouldPublishFeedStory($object, $xactions)) { - + if ($this->feedShouldPublish) { $mailed = array(); foreach ($messages as $mail) { foreach ($mail->buildRecipientList() as $phid) { @@ -3512,6 +3513,7 @@ private function getAutomaticStateProperties() { 'mailCCPHIDs', 'feedNotifyPHIDs', 'feedRelatedPHIDs', + 'feedShouldPublish', ); } From cb957f8d62424ace76a06331d9859d92dfb9f5db Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 Jan 2018 09:34:40 -0800 Subject: [PATCH 335/865] Pile more atrocities onto the Maniphest burnup report Summary: See PHI273. Ref T13020. After D18777, tasks created directly into the default status (which is common) via the web UI no longer write a "status" transaction. This is consistent with other applications, and consistent with the API/email behavior for tasks since early 2016. It also improves the consistency of //reading// tasks via the API. However, it impacted the "Burnup Report" which relies on directly reading these rows to detect task creation. Until this is fixed properly (T1562), synthetically generate the "missing" transactions which this page expects by looking at task creation dates instead. Specifically, we: - Generate a fake `status: null -> "open"` transaction for every task by looking at the Task table. - Go through the transaction list and remove all the legacy `status: null -> "any open status"` transactions. These will only exist for older tasks. - Merge all our new fake transactions into the list of transactions. - Continue on as though nothing happened, letting the rendering code continue to operate on legacy-looking data. I think this will slightly miscount tasks which were created directly into a closed status, but this is very rare, and does not significantly impact the accuracy of this report relative to other known issues (notably, merging closed tasks). This will also get the wrong result if the default status has changed from an "open" status to a "closed" status at any point, but this is exceptionally bizarre/rare. Ultimately, T1562 will let us delete all this stuff and disavow its existence. Test Plan: - Created some tasks, loaded burnup before/after this patch. - My local chart looks more accurate afterwards, but the data is super weird (I used `bin/lipsum` to create a huge number of tasks a couple months ago). I'll vet this on `secure`, which has more reasonable data. Here's my local chart: {F5356499} That's what it //should// look like, it's just hard to be confident that nothing else is hiding there. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13020 Differential Revision: https://secure.phabricator.com/D18853 --- .../controller/ManiphestReportController.php | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 66aa154e8f..821c96a2aa 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -99,13 +99,66 @@ public function renderBurn() { ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE, )); + // See PHI273. After the move to EditEngine, we no longer create a + // "status" transaction if a task is created directly into the default + // status. This likely impacted API/email tasks after 2016 and all other + // tasks after late 2017. Until Facts can fix this properly, use the + // task creation dates to generate synthetic transactions which look like + // the older transactions that this page expects. + + $default_status = ManiphestTaskStatus::getDefaultStatus(); + $duplicate_status = ManiphestTaskStatus::getDuplicateStatus(); + + // Build synthetic transactions which take status from `null` to the + // default value. + $create_rows = queryfx_all( + $conn, + 'SELECT dateCreated FROM %T', + id(new ManiphestTask())->getTableName()); + foreach ($create_rows as $key => $create_row) { + $create_rows[$key] = array( + 'transactionType' => 'status', + 'oldValue' => null, + 'newValue' => $default_status, + 'dateCreated' => $create_row['dateCreated'], + ); + } + + // Remove any actual legacy status transactions which take status from + // `null` to any open status. + foreach ($data as $key => $row) { + if ($row['transactionType'] != 'status') { + continue; + } + + $oldv = trim($row['oldValue'], '"'); + $newv = trim($row['oldValue'], '"'); + + // If this is a status change, preserve it. + if ($oldv != 'null') { + continue; + } + + // If this task was created directly into a closed status, preserve + // the transaction. + if (!ManiphestTaskStatus::isOpenStatus($newv)) { + continue; + } + + // If this is a legacy "create" transaction, discard it in favor of the + // synthetic one. + unset($data[$key]); + } + + // Merge the synthetic rows into the real transactions. + $data = array_merge($create_rows, $data); + $data = array_values($data); + $stats = array(); $day_buckets = array(); $open_tasks = array(); - $default_status = ManiphestTaskStatus::getDefaultStatus(); - $duplicate_status = ManiphestTaskStatus::getDuplicateStatus(); foreach ($data as $key => $row) { switch ($row['transactionType']) { case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: From 53b25db9185606a93f46cfd5d8911af644d63c31 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 Dec 2017 10:38:45 -0800 Subject: [PATCH 336/865] Prevent enormous changes from being pushed to repositoires by default Summary: Fixes T13031. "Enormous" changes are basically changes which are too large to hold in memory, although the actual definition we use today is "more than 1GB of change text or `git diff` runs for more than 15 minutes". If an install configures a Herald content rule like "when content matches /XYZ/, do something" and then a user pushes a 30 GB source file, we can't put it into memory to `preg_match()` it. Currently, the way to handle this case is to write a separate Herald rule that rejects enormous changes. However, this isn't obvious and means the default behavior is unsafe. Make the default behavior safe by rejecting these changes with a message, similar to how we reject "dangerous" changes (which permanently delete or overwrite history) by default. Also, change a couple of UI strings from "Enormous" to "Very Large" to reduce ambiguity. See . Test Plan: Changed the definition of "enormous" from 1GB to 1 byte. Pushed a change; got rejected. Allowed enormous changes, pushed, got rejected by a Herald rule. Disabled the Herald rule, pushed, got a clean push. Prevented enormous changes again. Grepped for "enormous" elsewhere in the UI. Reviewers: amckinley Reviewed By: amckinley Subscribers: joshuaspence Maniphest Tasks: T13031 Differential Revision: https://secure.phabricator.com/D18850 --- src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionApplication.php | 1 + .../controller/DiffusionCommitController.php | 4 +- ...fusionRepositoryEditEnormousController.php | 90 +++++++++++++++++++ .../editor/DiffusionRepositoryEditEngine.php | 13 +++ .../engine/DiffusionCommitHookEngine.php | 63 ++++++++++++- .../herald/HeraldPreCommitContentAdapter.php | 2 +- ...ffusionRepositoryBasicsManagementPanel.php | 31 +++++++ .../config/PhabricatorFilesConfigOptions.php | 2 +- .../editor/PhabricatorRepositoryEditor.php | 8 ++ .../storage/PhabricatorRepository.php | 12 +++ .../storage/PhabricatorRepositoryPushLog.php | 2 + .../PhabricatorRepositoryTransaction.php | 11 +++ 13 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 849d4d4702..1a8de4e42b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -860,6 +860,7 @@ 'DiffusionRepositoryEditDangerousController' => 'applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php', 'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php', 'DiffusionRepositoryEditEngine' => 'applications/diffusion/editor/DiffusionRepositoryEditEngine.php', + 'DiffusionRepositoryEditEnormousController' => 'applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php', 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', 'DiffusionRepositoryFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryFunctionDatasource.php', 'DiffusionRepositoryHistoryManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryHistoryManagementPanel.php', @@ -5923,6 +5924,7 @@ 'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditEngine' => 'PhabricatorEditEngine', + 'DiffusionRepositoryEditEnormousController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryManageController', 'DiffusionRepositoryFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionRepositoryHistoryManagementPanel' => 'DiffusionRepositoryManagementPanel', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 5a426ec299..5797b8ba7c 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -89,6 +89,7 @@ public function getRoutes() { 'edit/' => array( 'activate/' => 'DiffusionRepositoryEditActivateController', 'dangerous/' => 'DiffusionRepositoryEditDangerousController', + 'enormous/' => 'DiffusionRepositoryEditEnormousController', 'delete/' => 'DiffusionRepositoryEditDeleteController', 'update/' => 'DiffusionRepositoryEditUpdateController', 'testautomation/' => 'DiffusionRepositoryTestAutomationController', diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 11463ef97d..b4d06989c2 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -277,9 +277,9 @@ public function handleRequest(AphrontRequest $request) { 'This commit is empty and does not affect any paths.')); } else if ($was_limited) { $info_panel = $this->renderStatusMessage( - pht('Enormous Commit'), + pht('Very Large Commit'), pht( - 'This commit is enormous, and affects more than %d files. '. + 'This commit is very large, and affects more than %d files. '. 'Changes are not shown.', $hard_limit)); } else if (!$this->getCommitExists()) { diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php new file mode 100644 index 0000000000..d4eeb118d7 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditEnormousController.php @@ -0,0 +1,90 @@ +loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + + $viewer = $this->getViewer(); + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + $panel_uri = id(new DiffusionRepositoryBasicsManagementPanel()) + ->setRepository($repository) + ->getPanelURI(); + + if (!$repository->canAllowEnormousChanges()) { + return $this->newDialog() + ->setTitle(pht('Unprotectable Repository')) + ->appendParagraph( + pht( + 'This repository can not be protected from enormous changes '. + 'because Phabricator does not control what users are allowed '. + 'to push to it.')) + ->addCancelButton($panel_uri); + } + + if ($request->isFormPost()) { + $xaction = id(new PhabricatorRepositoryTransaction()) + ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ENORMOUS) + ->setNewValue(!$repository->shouldAllowEnormousChanges()); + + $editor = id(new PhabricatorRepositoryEditor()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->setActor($viewer) + ->applyTransactions($repository, array($xaction)); + + return id(new AphrontReloadResponse())->setURI($panel_uri); + } + + if ($repository->shouldAllowEnormousChanges()) { + $title = pht('Prevent Enormous Changes'); + + $body = pht( + 'It will no longer be possible to push enormous changes to this '. + 'repository.'); + + $submit = pht('Prevent Enormous Changes'); + } else { + $title = pht('Allow Enormous Changes'); + + $body = array( + pht( + 'If you allow enormous changes, users can push commits which are '. + 'too large for Herald to process content rules for. This can allow '. + 'users to evade content rules implemented in Herald.'), + pht( + 'You can selectively configure Herald by adding rules to prevent a '. + 'subset of enormous changes (for example, based on who is trying '. + 'to push the change).'), + ); + + $submit = pht('Allow Enormous Changes'); + } + + $more_help = pht( + 'Enormous changes are commits which are too large to process with '. + 'content rules because: the diff text for the change is larger than '. + '%s bytes; or the diff text takes more than %s seconds to extract.', + new PhutilNumber(HeraldCommitAdapter::getEnormousByteLimit()), + new PhutilNumber(HeraldCommitAdapter::getEnormousTimeLimit())); + + $response = $this->newDialog(); + + foreach ((array)$body as $paragraph) { + $response->appendParagraph($paragraph); + } + + return $response + ->setTitle($title) + ->appendParagraph($more_help) + ->addSubmitButton($submit) + ->addCancelButton($panel_uri); + } + +} diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index b3baafbd06..4eebc09b52 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -309,6 +309,19 @@ protected function buildCustomEditFields($object) { ->setConduitDescription(pht('Allow or prevent dangerous changes.')) ->setConduitTypeDescription(pht('New protection setting.')) ->setValue($object->shouldAllowDangerousChanges()), + id(new PhabricatorBoolEditField()) + ->setKey('allowEnormousChanges') + ->setLabel(pht('Allow Enormous Changes')) + ->setIsCopyable(true) + ->setIsConduitOnly(true) + ->setOptions( + pht('Prevent Enormous Changes'), + pht('Allow Enormous Changes')) + ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ENORMOUS) + ->setDescription(pht('Permit enomrous changes to be made.')) + ->setConduitDescription(pht('Allow or prevent enormous changes.')) + ->setConduitTypeDescription(pht('New protection setting.')) + ->setValue($object->shouldAllowEnormousChanges()), id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index 7fe45834b3..99df0e54af 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -34,6 +34,7 @@ final class DiffusionCommitHookEngine extends Phobject { private $rejectCode = PhabricatorRepositoryPushLog::REJECT_BROKEN; private $rejectDetails; private $emailPHIDs = array(); + private $changesets = array(); /* -( Config )------------------------------------------------------------- */ @@ -131,6 +132,15 @@ public function execute() { $this->applyHeraldRefRules($ref_updates, $all_updates); $content_updates = $this->findContentUpdates($ref_updates); + + try { + $this->rejectEnormousChanges($content_updates); + } catch (DiffusionCommitHookRejectException $ex) { + // If we're rejecting enormous changes, flag everything. + $this->rejectCode = PhabricatorRepositoryPushLog::REJECT_ENORMOUS; + throw $ex; + } + $all_updates = array_merge($all_updates, $content_updates); $this->applyHeraldContentRules($content_updates, $all_updates); @@ -1079,7 +1089,37 @@ private function newPushEvent() { ->setEpoch(time()); } - public function loadChangesetsForCommit($identifier) { + private function rejectEnormousChanges(array $content_updates) { + $repository = $this->getRepository(); + if ($repository->shouldAllowEnormousChanges()) { + return; + } + + foreach ($content_updates as $update) { + $identifier = $update->getRefNew(); + try { + $changesets = $this->loadChangesetsForCommit($identifier); + $this->changesets[$identifier] = $changesets; + } catch (Exception $ex) { + $this->changesets[$identifier] = $ex; + + $message = pht( + 'ENORMOUS CHANGE'. + "\n". + 'Enormous change protection is enabled for this repository, but '. + 'you are pushing an enormous change ("%s"). Edit the repository '. + 'configuration before making enormous changes.'. + "\n\n". + "Content Exception: %s", + $identifier, + $ex->getMessage()); + + throw new DiffusionCommitHookRejectException($message); + } + } + } + + private function loadChangesetsForCommit($identifier) { $byte_limit = HeraldCommitAdapter::getEnormousByteLimit(); $time_limit = HeraldCommitAdapter::getEnormousTimeLimit(); @@ -1126,9 +1166,10 @@ public function loadChangesetsForCommit($identifier) { if (strlen($raw_diff) >= $byte_limit) { throw new Exception( pht( - 'The raw text of this change is enormous (larger than %d '. - 'bytes). Herald can not process it.', - $byte_limit)); + 'The raw text of this change ("%s") is enormous (larger than %s '. + 'bytes).', + $identifier, + new PhutilNumber($byte_limit))); } if (!strlen($raw_diff)) { @@ -1143,6 +1184,20 @@ public function loadChangesetsForCommit($identifier) { return $diff->getChangesets(); } + public function getChangesetsForCommit($identifier) { + if (isset($this->changesets[$identifier])) { + $cached = $this->changesets[$identifier]; + + if ($cached instanceof Exception) { + throw $cached; + } + + return $cached; + } + + return $this->loadChangesetsForCommit($identifier); + } + public function loadCommitRefForCommit($identifier) { $repository = $this->getRepository(); $vcs = $repository->getVersionControlSystem(); diff --git a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php index f4d7e794a2..c93ba32345 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php @@ -37,7 +37,7 @@ public function isDiffEnormous() { public function getDiffContent($type) { if ($this->changesets === null) { try { - $this->changesets = $this->getHookEngine()->loadChangesetsForCommit( + $this->changesets = $this->getHookEngine()->getChangesetsForCommit( $this->getObject()->getRefNew()); } catch (Exception $ex) { $this->changesets = $ex; diff --git a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php index 6e527e81b5..af5e9b2881 100644 --- a/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryBasicsManagementPanel.php @@ -43,6 +43,7 @@ private function buildActionMenu() { $delete_uri = $repository->getPathURI('edit/delete/'); $encoding_uri = $this->getEditPageURI('encoding'); $dangerous_uri = $repository->getPathURI('edit/dangerous/'); + $enormous_uri = $repository->getPathURI('edit/enormous/'); if ($repository->isTracked()) { $activate_label = pht('Deactivate Repository'); @@ -59,6 +60,15 @@ private function buildActionMenu() { $can_dangerous = ($can_edit && $repository->canAllowDangerousChanges()); } + $should_enormous = $repository->shouldAllowEnormousChanges(); + if ($should_enormous) { + $enormous_name = pht('Prevent Enormous Changes'); + $can_enormous = $can_edit; + } else { + $enormous_name = pht('Allow Enormous Changes'); + $can_enormous = ($can_edit && $repository->canAllowEnormousChanges()); + } + $action_list->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Basic Information')) @@ -80,6 +90,13 @@ private function buildActionMenu() { ->setDisabled(!$can_dangerous) ->setWorkflow(true)); + $action_list->addAction( + id(new PhabricatorActionView()) + ->setName($enormous_name) + ->setHref($enormous_uri) + ->setDisabled(!$can_enormous) + ->setWorkflow(true)); + $action_list->addAction( id(new PhabricatorActionView()) ->setHref($activate_uri) @@ -198,6 +215,20 @@ private function buildBasics() { $view->addProperty(pht('Dangerous Changes'), $dangerous); + $can_enormous = $repository->canAllowEnormousChanges(); + if (!$can_enormous) { + $enormous = phutil_tag('em', array(), pht('Not Preventable')); + } else { + $should_enormous = $repository->shouldAllowEnormousChanges(); + if ($should_enormous) { + $enormous = pht('Allowed'); + } else { + $enormous = pht('Not Allowed'); + } + } + + $view->addProperty(pht('Enormous Changes'), $enormous); + return $view; } diff --git a/src/applications/files/config/PhabricatorFilesConfigOptions.php b/src/applications/files/config/PhabricatorFilesConfigOptions.php index 0f13a2fb4f..1493c54f59 100644 --- a/src/applications/files/config/PhabricatorFilesConfigOptions.php +++ b/src/applications/files/config/PhabricatorFilesConfigOptions.php @@ -134,7 +134,7 @@ public function getOptions() { "Configure which uploaded file types may be viewed directly ". "in the browser. Other file types will be downloaded instead ". "of displayed. This is mainly a usability consideration, since ". - "browsers tend to freak out when viewing enormous binary files.". + "browsers tend to freak out when viewing very large binary files.". "\n\n". "The keys in this map are viewable MIME types; the values are ". "the MIME types they are delivered as when they are viewed in ". diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 401fca3668..768121de5a 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -28,6 +28,7 @@ public function getTransactionTypes() { $types[] = PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE; $types[] = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY; $types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS; + $types[] = PhabricatorRepositoryTransaction::TYPE_ENORMOUS; $types[] = PhabricatorRepositoryTransaction::TYPE_SLUG; $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; @@ -76,6 +77,8 @@ protected function getCustomTransactionOldValue( return $object->getPushPolicy(); case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: return $object->shouldAllowDangerousChanges(); + case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: + return $object->shouldAllowEnormousChanges(); case PhabricatorRepositoryTransaction::TYPE_SLUG: return $object->getRepositorySlug(); case PhabricatorRepositoryTransaction::TYPE_SERVICE: @@ -110,6 +113,7 @@ protected function getCustomTransactionNewValue( case PhabricatorRepositoryTransaction::TYPE_VCS: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: + case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: @@ -184,6 +188,9 @@ protected function applyCustomInternalTransaction( case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: $object->setDetail('allow-dangerous-changes', $xaction->getNewValue()); return; + case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: + $object->setDetail('allow-enormous-changes', $xaction->getNewValue()); + return; case PhabricatorRepositoryTransaction::TYPE_SLUG: $object->setRepositorySlug($xaction->getNewValue()); return; @@ -248,6 +255,7 @@ protected function requireCapabilities( case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: + case PhabricatorRepositoryTransaction::TYPE_ENORMOUS: case PhabricatorRepositoryTransaction::TYPE_SLUG: case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 702bb42780..435e5749e6 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1672,6 +1672,18 @@ public function shouldAllowDangerousChanges() { return (bool)$this->getDetail('allow-dangerous-changes'); } + public function canAllowEnormousChanges() { + if (!$this->isHosted()) { + return false; + } + + return true; + } + + public function shouldAllowEnormousChanges() { + return (bool)$this->getDetail('allow-enormous-changes'); + } + public function writeStatusMessage( $status_type, $status_code, diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php index 4c449b4a84..4e099209c6 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushLog.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushLog.php @@ -23,12 +23,14 @@ final class PhabricatorRepositoryPushLog const CHANGEFLAG_APPEND = 4; const CHANGEFLAG_REWRITE = 8; const CHANGEFLAG_DANGEROUS = 16; + const CHANGEFLAG_ENORMOUS = 32; const REJECT_ACCEPT = 0; const REJECT_DANGEROUS = 1; const REJECT_HERALD = 2; const REJECT_EXTERNAL = 3; const REJECT_BROKEN = 4; + const REJECT_ENORMOUS = 5; protected $repositoryPHID; protected $epoch; diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index e02b670f75..866800161e 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -16,6 +16,7 @@ final class PhabricatorRepositoryTransaction const TYPE_AUTOCLOSE = 'repo:autoclose'; const TYPE_PUSH_POLICY = 'repo:push-policy'; const TYPE_DANGEROUS = 'repo:dangerous'; + const TYPE_ENORMOUS = 'repo:enormous'; const TYPE_SLUG = 'repo:slug'; const TYPE_SERVICE = 'repo:service'; const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source'; @@ -376,6 +377,16 @@ public function getTitle() { '%s enabled protection against dangerous changes.', $this->renderHandleLink($author_phid)); } + case self::TYPE_ENORMOUS: + if ($new) { + return pht( + '%s disabled protection against enormous changes.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s enabled protection against enormous changes.', + $this->renderHandleLink($author_phid)); + } case self::TYPE_SLUG: if (strlen($old) && !strlen($new)) { return pht( From 83c528c46435e25f929c1a359bf3b3929783db00 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 15 Dec 2017 08:21:20 -0800 Subject: [PATCH 337/865] Modularize transactions for Drydock Blueprints Summary: Ref PHI243. This is a followup to D18822, which added an edit-only `drydock.blueprint.edit`. By modularizing transactions (here) and then adding a "type" transaction (next change) I intend to remove the "edit-only" limitation and make this API method fully functional. Test Plan: Created and edited blueprints via the web UI. Edited blueprints via the API. Disabled/enabled blueprints (currently web UI only). Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18845 --- src/__phutil_library_map__.php | 8 +- .../DrydockBlueprintDisableController.php | 3 +- .../editor/DrydockBlueprintEditEngine.php | 2 +- .../drydock/editor/DrydockBlueprintEditor.php | 100 ++---------------- .../storage/DrydockBlueprintTransaction.php | 35 +----- .../DrydockBlueprintDisableTransaction.php | 48 +++++++++ .../DrydockBlueprintNameTransaction.php | 60 +++++++++++ .../DrydockBlueprintTransactionType.php | 4 + 8 files changed, 133 insertions(+), 127 deletions(-) create mode 100644 src/applications/drydock/xaction/DrydockBlueprintDisableTransaction.php create mode 100644 src/applications/drydock/xaction/DrydockBlueprintNameTransaction.php create mode 100644 src/applications/drydock/xaction/DrydockBlueprintTransactionType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1a8de4e42b..0a005eef4a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1008,6 +1008,7 @@ 'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php', 'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php', 'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php', + 'DrydockBlueprintDisableTransaction' => 'applications/drydock/xaction/DrydockBlueprintDisableTransaction.php', 'DrydockBlueprintEditConduitAPIMethod' => 'applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php', 'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php', 'DrydockBlueprintEditEngine' => 'applications/drydock/editor/DrydockBlueprintEditEngine.php', @@ -1016,12 +1017,14 @@ 'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php', 'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php', 'DrydockBlueprintNameNgrams' => 'applications/drydock/storage/DrydockBlueprintNameNgrams.php', + 'DrydockBlueprintNameTransaction' => 'applications/drydock/xaction/DrydockBlueprintNameTransaction.php', 'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php', 'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php', 'DrydockBlueprintSearchConduitAPIMethod' => 'applications/drydock/conduit/DrydockBlueprintSearchConduitAPIMethod.php', 'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php', 'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php', 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', + 'DrydockBlueprintTransactionType' => 'applications/drydock/xaction/DrydockBlueprintTransactionType.php', 'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php', 'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php', 'DrydockCommandError' => 'applications/drydock/exception/DrydockCommandError.php', @@ -6102,6 +6105,7 @@ 'DrydockBlueprintCustomField' => 'PhabricatorCustomField', 'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockBlueprintDisableController' => 'DrydockBlueprintController', + 'DrydockBlueprintDisableTransaction' => 'DrydockBlueprintTransactionType', 'DrydockBlueprintEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DrydockBlueprintEditController' => 'DrydockBlueprintController', 'DrydockBlueprintEditEngine' => 'PhabricatorEditEngine', @@ -6110,12 +6114,14 @@ 'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase', 'DrydockBlueprintListController' => 'DrydockBlueprintController', 'DrydockBlueprintNameNgrams' => 'PhabricatorSearchNgrams', + 'DrydockBlueprintNameTransaction' => 'DrydockBlueprintTransactionType', 'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType', 'DrydockBlueprintQuery' => 'DrydockQuery', 'DrydockBlueprintSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction', + 'DrydockBlueprintTransaction' => 'PhabricatorModularTransaction', 'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'DrydockBlueprintTransactionType' => 'PhabricatorModularTransactionType', 'DrydockBlueprintViewController' => 'DrydockBlueprintController', 'DrydockCommand' => array( 'DrydockDAO', diff --git a/src/applications/drydock/controller/DrydockBlueprintDisableController.php b/src/applications/drydock/controller/DrydockBlueprintDisableController.php index 525e55228b..60fc8b0d74 100644 --- a/src/applications/drydock/controller/DrydockBlueprintDisableController.php +++ b/src/applications/drydock/controller/DrydockBlueprintDisableController.php @@ -28,7 +28,8 @@ public function handleRequest(AphrontRequest $request) { $xactions = array(); $xactions[] = id(new DrydockBlueprintTransaction()) - ->setTransactionType(DrydockBlueprintTransaction::TYPE_DISABLED) + ->setTransactionType( + DrydockBlueprintDisableTransaction::TRANSACTIONTYPE) ->setNewValue($is_disable ? 1 : 0); $editor = id(new DrydockBlueprintEditor()) diff --git a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php index 020fe7514c..bd91aa3350 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php @@ -121,7 +121,7 @@ protected function buildCustomEditFields($object) { ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the blueprint.')) - ->setTransactionType(DrydockBlueprintTransaction::TYPE_NAME) + ->setTransactionType(DrydockBlueprintNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getBlueprintName()), ); diff --git a/src/applications/drydock/editor/DrydockBlueprintEditor.php b/src/applications/drydock/editor/DrydockBlueprintEditor.php index 125c36811a..aac3e7a020 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditor.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditor.php @@ -11,6 +11,14 @@ public function getEditorObjectsDescription() { return pht('Drydock Blueprints'); } + public function getCreateObjectTitle($author, $object) { + return pht('%s created this blueprint.', $author); + } + + public function getCreateObjectTitleForFeed($author, $object) { + return pht('%s created %s.', $author, $object); + } + protected function supportsSearch() { return true; } @@ -21,99 +29,7 @@ public function getTransactionTypes() { $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; - $types[] = DrydockBlueprintTransaction::TYPE_NAME; - $types[] = DrydockBlueprintTransaction::TYPE_DISABLED; - return $types; } - protected function getCustomTransactionOldValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case DrydockBlueprintTransaction::TYPE_NAME: - return $object->getBlueprintName(); - case DrydockBlueprintTransaction::TYPE_DISABLED: - return (int)$object->getIsDisabled(); - } - - return parent::getCustomTransactionOldValue($object, $xaction); - } - - protected function getCustomTransactionNewValue( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case DrydockBlueprintTransaction::TYPE_NAME: - return $xaction->getNewValue(); - case DrydockBlueprintTransaction::TYPE_DISABLED: - return (int)$xaction->getNewValue(); - } - - return parent::getCustomTransactionNewValue($object, $xaction); - } - - protected function applyCustomInternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case DrydockBlueprintTransaction::TYPE_NAME: - $object->setBlueprintName($xaction->getNewValue()); - return; - case DrydockBlueprintTransaction::TYPE_DISABLED: - $object->setIsDisabled((int)$xaction->getNewValue()); - return; - } - - return parent::applyCustomInternalTransaction($object, $xaction); - } - - protected function applyCustomExternalTransaction( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - switch ($xaction->getTransactionType()) { - case DrydockBlueprintTransaction::TYPE_NAME: - case DrydockBlueprintTransaction::TYPE_DISABLED: - return; - } - - return parent::applyCustomExternalTransaction($object, $xaction); - } - - - protected function validateTransaction( - PhabricatorLiskDAO $object, - $type, - array $xactions) { - - $errors = parent::validateTransaction($object, $type, $xactions); - - switch ($type) { - case DrydockBlueprintTransaction::TYPE_NAME: - $missing = $this->validateIsEmptyTextField( - $object->getBlueprintName(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('You must choose a name for this blueprint.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - continue; - } - - break; - } - - return $errors; - } - } diff --git a/src/applications/drydock/storage/DrydockBlueprintTransaction.php b/src/applications/drydock/storage/DrydockBlueprintTransaction.php index 0856f01fdb..68eebe8c1d 100644 --- a/src/applications/drydock/storage/DrydockBlueprintTransaction.php +++ b/src/applications/drydock/storage/DrydockBlueprintTransaction.php @@ -1,7 +1,7 @@ getOldValue(); - $new = $this->getNewValue(); - $author_handle = $this->renderHandleLink($this->getAuthorPHID()); - - switch ($this->getTransactionType()) { - case self::TYPE_NAME: - if (!strlen($old)) { - return pht( - '%s created this blueprint.', - $author_handle); - } else { - return pht( - '%s renamed this blueprint from "%s" to "%s".', - $author_handle, - $old, - $new); - } - case self::TYPE_DISABLED: - if ($new) { - return pht( - '%s disabled this blueprint.', - $author_handle); - } else { - return pht( - '%s enabled this blueprint.', - $author_handle); - } - } - - return parent::getTitle(); + public function getBaseTransactionClass() { + return 'DrydockBlueprintTransactionType'; } } diff --git a/src/applications/drydock/xaction/DrydockBlueprintDisableTransaction.php b/src/applications/drydock/xaction/DrydockBlueprintDisableTransaction.php new file mode 100644 index 0000000000..216cea35f4 --- /dev/null +++ b/src/applications/drydock/xaction/DrydockBlueprintDisableTransaction.php @@ -0,0 +1,48 @@ +getIsDisabled(); + } + + public function generateNewValue($object, $value) { + return (bool)$value; + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled((int)$value); + } + + public function getTitle() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s disabled this blueprint.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled this blueprint.', + $this->renderAuthor()); + } + } + + public function getTitleForFeed() { + $new = $this->getNewValue(); + if ($new) { + return pht( + '%s disabled %s.', + $this->renderAuthor(), + $this->renderObject()); + } else { + return pht( + '%s enabled %s.', + $this->renderAuthor(), + $this->renderObject()); + } + } + +} diff --git a/src/applications/drydock/xaction/DrydockBlueprintNameTransaction.php b/src/applications/drydock/xaction/DrydockBlueprintNameTransaction.php new file mode 100644 index 0000000000..c3c17d0413 --- /dev/null +++ b/src/applications/drydock/xaction/DrydockBlueprintNameTransaction.php @@ -0,0 +1,60 @@ +getBlueprintName(); + } + + public function applyInternalEffects($object, $value) { + $object->setBlueprintName($value); + } + + public function getTitle() { + return pht( + '%s renamed this blueprint from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return pht( + '%s renamed %s from %s to %s.', + $this->renderAuthor(), + $this->renderObject(), + $this->renderOldValue(), + $this->renderNewValue()); + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $name = $object->getBlueprintName(); + if ($this->isEmptyTextTransaction($name, $xactions)) { + $errors[] = $this->newRequiredError( + pht('Blueprints must have a name.')); + } + + $max_length = $object->getColumnMaximumByteLength('blueprintName'); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + $new_length = strlen($new_value); + if ($new_length > $max_length) { + $errors[] = $this->newInvalidError( + pht('Blueprint names can be no longer than %s characters.', + new PhutilNumber($max_length))); + } + } + + return $errors; + } + +} diff --git a/src/applications/drydock/xaction/DrydockBlueprintTransactionType.php b/src/applications/drydock/xaction/DrydockBlueprintTransactionType.php new file mode 100644 index 0000000000..ecf021b3ad --- /dev/null +++ b/src/applications/drydock/xaction/DrydockBlueprintTransactionType.php @@ -0,0 +1,4 @@ + Date: Wed, 13 Dec 2017 07:47:18 -0800 Subject: [PATCH 338/865] Give EditEngine a Conduit-specific initialization pathway for objects Summary: Depends on D18845. See PHI243 for context and more details. Briefly, some objects need a "type" transaction (or something similar) very early on, and we can't generate their fields until we know the object type. Drydock blueprints are an example: a blueprint's fields depend on the blueprint's type. In web interfaces, the workflow just forces the user to select a type first. For Conduit workflows, I think the cleanest approach is to proactively extract and apply type information before processing the request. This will make the implementation a little messier, but the API cleaner. An alternative is to add more fields to the API, like a "type" field. This makes the implementation cleaner, but the API messier. I think we're better off favoring a cleaner API here. This change just makes it possible for `DrydockBlueprintEditEngine` to look at the incoming transactions and extract a "type"; it doesn't actually change any behavior. Test Plan: Performed edits via API, but this change doesn't alter any behavior. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18847 --- .../editengine/PhabricatorEditEngine.php | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 8ba49f4d77..1e9d936929 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -630,6 +630,17 @@ final public function getIsCreate() { return $this->isCreate; } + /** + * Initialize a new object for object creation via Conduit. + * + * @return object Newly initialized object. + * @param list Raw transactions. + * @task load + */ + protected function newEditableObjectFromConduit(array $raw_xactions) { + return $this->newEditableObject(); + } + /** * Initialize a new object for documentation creation. * @@ -2031,6 +2042,8 @@ final public function buildConduitResponse(ConduitAPIRequest $request) { get_class($this))); } + $raw_xactions = $this->getRawConduitTransactions($request); + $identifier = $request->getValue('objectIdentifier'); if ($identifier) { $this->setIsCreate(false); @@ -2039,7 +2052,7 @@ final public function buildConduitResponse(ConduitAPIRequest $request) { $this->requireCreateCapability(); $this->setIsCreate(true); - $object = $this->newEditableObject(); + $object = $this->newEditableObjectFromConduit($raw_xactions); } $this->validateObject($object); @@ -2049,7 +2062,11 @@ final public function buildConduitResponse(ConduitAPIRequest $request) { $types = $this->getConduitEditTypesFromFields($fields); $template = $object->getApplicationTransactionTemplate(); - $xactions = $this->getConduitTransactions($request, $types, $template); + $xactions = $this->getConduitTransactions( + $request, + $raw_xactions, + $types, + $template); $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) @@ -2078,23 +2095,7 @@ final public function buildConduitResponse(ConduitAPIRequest $request) { ); } - - /** - * Generate transactions which can be applied from edit actions in a Conduit - * request. - * - * @param ConduitAPIRequest The request. - * @param list Supported edit types. - * @param PhabricatorApplicationTransaction Template transaction. - * @return list Generated transactions. - * @task conduit - */ - private function getConduitTransactions( - ConduitAPIRequest $request, - array $types, - PhabricatorApplicationTransaction $template) { - - $viewer = $request->getUser(); + private function getRawConduitTransactions(ConduitAPIRequest $request) { $transactions_key = 'transactions'; $xactions = $request->getValue($transactions_key); @@ -2124,7 +2125,33 @@ private function getConduitTransactions( $transactions_key, $key)); } + } + + return $xactions; + } + + + /** + * Generate transactions which can be applied from edit actions in a Conduit + * request. + * + * @param ConduitAPIRequest The request. + * @param list Raw conduit transactions. + * @param list Supported edit types. + * @param PhabricatorApplicationTransaction Template transaction. + * @return list Generated transactions. + * @task conduit + */ + private function getConduitTransactions( + ConduitAPIRequest $request, + array $xactions, + array $types, + PhabricatorApplicationTransaction $template) { + $viewer = $request->getUser(); + $results = array(); + + foreach ($xactions as $key => $xaction) { $type = $xaction['type']; if (empty($types[$type])) { throw new Exception( @@ -2137,8 +2164,6 @@ private function getConduitTransactions( } } - $results = array(); - if ($this->getIsCreate()) { $results[] = id(clone $template) ->setTransactionType(PhabricatorTransactions::TYPE_CREATE); From f3f1f9dc577ac9675accae0f5870e55a08a3f258 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 Dec 2017 10:17:27 -0800 Subject: [PATCH 339/865] Allow "drydock.blueprint.edit" to create blueprints Summary: Depends on D18848. Ref PHI243. This puts a bit of logic up front to figure out the blueprint type before we actually start editing it. This implementation is a little messy but it keeps the API clean. Eventually, the implementation could probably go in the TransactionTypes so more code is shared, but I'd like to wait for a couple more of these first. This capability probably isn't too useful, but just pays down a bit of technical debt from the caveat introduced in D18822. Test Plan: - Created a new blueprint with the API. - Tried to create a blueprint without a "type" (got a helpful error). - Created and edited blueprints via the web UI. - Tried to change the "type" of an existing blueprint (got a helpful error). Reviewers: amckinley Reviewed By: amckinley Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Differential Revision: https://secure.phabricator.com/D18849 --- src/__phutil_library_map__.php | 2 + .../DrydockBlueprintEditConduitAPIMethod.php | 3 +- .../editor/DrydockBlueprintEditEngine.php | 45 ++++++++++++++- .../DrydockBlueprintTypeTransaction.php | 57 +++++++++++++++++++ 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/applications/drydock/xaction/DrydockBlueprintTypeTransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0a005eef4a..f4a410ad2e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1025,6 +1025,7 @@ 'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php', 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', 'DrydockBlueprintTransactionType' => 'applications/drydock/xaction/DrydockBlueprintTransactionType.php', + 'DrydockBlueprintTypeTransaction' => 'applications/drydock/xaction/DrydockBlueprintTypeTransaction.php', 'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php', 'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php', 'DrydockCommandError' => 'applications/drydock/exception/DrydockCommandError.php', @@ -6122,6 +6123,7 @@ 'DrydockBlueprintTransaction' => 'PhabricatorModularTransaction', 'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DrydockBlueprintTransactionType' => 'PhabricatorModularTransactionType', + 'DrydockBlueprintTypeTransaction' => 'DrydockBlueprintTransactionType', 'DrydockBlueprintViewController' => 'DrydockBlueprintController', 'DrydockCommand' => array( 'DrydockDAO', diff --git a/src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php b/src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php index 764c077d87..7a120f7c5b 100644 --- a/src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php +++ b/src/applications/drydock/conduit/DrydockBlueprintEditConduitAPIMethod.php @@ -13,8 +13,7 @@ public function newEditEngine() { public function getMethodSummary() { return pht( - 'WARNING: Apply transactions to edit an existing blueprint. This method '. - 'can not create new blueprints.'); + 'Apply transactions to create or edit a blueprint.'); } } diff --git a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php index bd91aa3350..6bd1366ef0 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php @@ -51,6 +51,38 @@ protected function newEditableObject() { return $blueprint; } + protected function newEditableObjectFromConduit(array $raw_xactions) { + $type = null; + foreach ($raw_xactions as $raw_xaction) { + if ($raw_xaction['type'] !== 'type') { + continue; + } + + $type = $raw_xaction['value']; + } + + if ($type === null) { + throw new Exception( + pht( + 'When creating a new Drydock blueprint via the Conduit API, you '. + 'must provide a "type" transaction to select a type.')); + } + + $map = DrydockBlueprintImplementation::getAllBlueprintImplementations(); + if (!isset($map[$type])) { + throw new Exception( + pht( + 'Blueprint type "%s" is unrecognized. Valid types are: %s.', + $type, + implode(', ', array_keys($map)))); + } + + $impl = clone $map[$type]; + $this->setBlueprintImplementation($impl); + + return $this->newEditableObject(); + } + protected function newEditableObjectForDocumentation() { // In order to generate the proper list of fields/transactions for a // blueprint, a blueprint's type needs to be known upfront, and there's @@ -112,11 +144,22 @@ protected function buildCustomEditFields($object) { $impl = $object->getImplementation(); return array( + // This field appears in the web UI id(new PhabricatorStaticEditField()) - ->setKey('type') + ->setKey('displayType') ->setLabel(pht('Blueprint Type')) ->setDescription(pht('Type of blueprint.')) ->setValue($impl->getBlueprintName()), + id(new PhabricatorTextEditField()) + ->setKey('type') + ->setLabel(pht('Type')) + ->setIsConduitOnly(true) + ->setTransactionType( + DrydockBlueprintTypeTransaction::TRANSACTIONTYPE) + ->setDescription(pht('When creating a blueprint, set the type.')) + ->setConduitDescription(pht('Set the blueprint type.')) + ->setConduitTypeDescription(pht('Blueprint type.')) + ->setValue($object->getClassName()), id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) diff --git a/src/applications/drydock/xaction/DrydockBlueprintTypeTransaction.php b/src/applications/drydock/xaction/DrydockBlueprintTypeTransaction.php new file mode 100644 index 0000000000..024e6092fa --- /dev/null +++ b/src/applications/drydock/xaction/DrydockBlueprintTypeTransaction.php @@ -0,0 +1,57 @@ +getClassName(); + } + + public function applyInternalEffects($object, $value) { + $object->setClassName($value); + } + + public function getTitle() { + // These transactions can only be applied during object creation and never + // generate a timeline event. + return null; + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $name = $object->getClassName(); + if ($this->isEmptyTextTransaction($name, $xactions)) { + $errors[] = $this->newRequiredError( + pht('You must select a blueprint type when creating a blueprint.')); + } + + $map = DrydockBlueprintImplementation::getAllBlueprintImplementations(); + + foreach ($xactions as $xaction) { + if (!$this->isNewObject()) { + $errors[] = $this->newInvalidError( + pht( + 'The type of a blueprint can not be changed once it has '. + 'been created.'), + $xaction); + continue; + } + + $new = $xaction->getNewValue(); + if (!isset($map[$new])) { + $errors[] = $this->newInvalidError( + pht( + 'Blueprint type "%s" is not valid. Valid types are: %s.', + $new, + implode(', ', array_keys($map)))); + continue; + } + } + + return $errors; + } + +} From 0f02d79ffa9d5f20d3687953f7276076cf0a82cf Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 Jan 2018 12:22:27 -0800 Subject: [PATCH 340/865] Remove nonfunctional Mercurial "bundle2" capability filtering from SSH pathway Summary: Ref T13036. This code attempts to filter the "capabilities" message to remove "bundle2", but I think this has never worked. Specifically, the //write// pathway is hooked, and "write" here means "client is writing a message to the server". However, the "capabilities" frame is part of the response, not part of the request. Thus, this code never fires, at least on recent versions of Mercurial. Since I plan to support bundle2 and don't want to decode response frames, just get rid of this, assuming we'll achieve those goals. I think this was just overlooked in D14241, which probably focused on the HTTP version. This code does (at least, potentially) do something for HTTP. I'm leaving the actual "strip stuff" code in place for now since I think it's still used on the HTTP pathway. Test Plan: - Added debug logging, saw this code never hit even though `hg push --debug` shows the client believing bundle2 is supported. - Logged both halves of the wire protocol and saw this come from the server, not the client. - Ran the failing `hg push` of a 4MB file under hg 4.4.1, got the same error as before. Reviewers: amckinley Reviewed By: amckinley Subscribers: cspeckmim Maniphest Tasks: T13036 Differential Revision: https://secure.phabricator.com/D18855 --- .../diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php index 4508ae53da..da877b7314 100644 --- a/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php @@ -109,14 +109,7 @@ public function willWriteMessageCallback( $this->didSeeWrite = true; } - $raw_message = $message['raw']; - if ($command == 'capabilities') { - $raw_message = DiffusionMercurialWireProtocol::filterBundle2Capability( - $raw_message); - } - - // If we're good, return the raw message data. - return $raw_message; + return $message['raw']; } protected function raiseWrongVCSException( From 3a4e14431fce5b80c57c1f62ed6003ee93575ac6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 Jan 2018 12:46:38 -0800 Subject: [PATCH 341/865] Remove an obsolete comment about Mercurial SSH error behavior Summary: Depends on D18855. Ref T13036. This comment no longer seems to be accurate: anything we send over `stderr` is faithfully shown to the user with recent clients. From [[ https://www.mercurial-scm.org/repo/hg/file/default/mercurial/help/internals/wireprotocol.txt | this document ]], the missing sauce may have been: ``` A generic error response type is also supported. It consists of a an error message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is written to ``stdout``. ``` That is, writing "\n" to stdout in addition to writing the error to stderr. However, this no longer appears to be necessary. I think the modern client behavior is generally sensible (and consistent with the behavior of Git and Subversion) so this //probably// isn't a bug or me making a mistake. Test Plan: With a modern client, threw some arbitrary exception during execution. Observed a helpful message on the client with no additional steps. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13036 Differential Revision: https://secure.phabricator.com/D18856 --- .../ssh/DiffusionMercurialServeSSHWorkflow.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php index da877b7314..5701a4bac1 100644 --- a/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php @@ -66,17 +66,6 @@ protected function executeRepositoryOperations() { ->setWillWriteCallback(array($this, 'willWriteMessageCallback')) ->execute(); - // TODO: It's apparently technically possible to communicate errors to - // Mercurial over SSH by writing a special "\n\n-\n" string. However, - // my attempt to implement that resulted in Mercurial closing the socket and - // then hanging, without showing the error. This might be an issue on our - // side (we need to close our half of the socket?), or maybe the code - // for this in Mercurial doesn't actually work, or maybe something else - // is afoot. At some point, we should look into doing this more cleanly. - // For now, when we, e.g., reject writes for policy reasons, the user will - // see "abort: unexpected response: empty string" after the diagnostically - // useful, e.g., "remote: This repository is read-only over SSH." message. - if (!$err && $this->didSeeWrite) { $repository->writeStatusMessage( PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, From 13c8963dab27b5ee24a87c5f4ebbf18be585dc19 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 Jan 2018 13:41:20 -0800 Subject: [PATCH 342/865] Fix a Mercurial wire protocol parser issue when we receive a length frame before any data Summary: Depends on D18856. Ref T13036. See PHI275. When we receive a length frame but the buffer doesn't have any data yet, we currently emit a pointless 0-length data frame on the channel. For normal chatter this is harmless/valid, but it causes problems when a channel has transitioned into bundle2 mode (probably it indicates "end of stream")? In any case, it's never helpful, so if we're about to read a data block and don't have any data, just bail out until we see some more data. Note that we can't end up here //expecting// a 0-length data block: both the `data-length` and `data-bytes` states already handle that properly. Test Plan: Pushed 4MB changes to a Mercurial repository with Mercurial 4.1.1, was no longer able to hit channel errors. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13036 Differential Revision: https://secure.phabricator.com/D18857 --- .../ssh/DiffusionMercurialWireClientSSHProtocolChannel.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php b/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php index 5150ef9352..4c16fb9f0f 100644 --- a/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php +++ b/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php @@ -192,6 +192,13 @@ protected function decodeStream($data) { $this->state = 'data-bytes'; } } else if ($this->state == 'data-bytes') { + // If we don't have any more bytes on the buffer yet, just bail: + // otherwise, we'll emit a pointless and possibly harmful 0-byte data + // frame. See T13036 for discussion. + if (!strlen($this->buffer)) { + break; + } + $bytes = substr($this->buffer, 0, $this->expectBytes); $this->buffer = substr($this->buffer, strlen($bytes)); $this->expectBytes -= strlen($bytes); From 94db95a165e710e30366c52d99ef1f5f289fbac3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 Jan 2018 10:18:45 -0800 Subject: [PATCH 343/865] Sort burnup data chronologically after merging synthetic and "real" data Summary: Ref T13020. See PHI273. See D18853. On `secure`, the chart looks less promising than it did locally, and is full of discontinuities: {F5356544} I think this is a sorting issue. But if I can't fake my way through this soon I'll maybe get the Fact engine running and use it to provide the data here, as a sort of half-step toward T1562? Test Plan: Chart looks the same locally, will push and see if `secure` improves? Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13020 Differential Revision: https://secure.phabricator.com/D18854 --- .../maniphest/controller/ManiphestReportController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 821c96a2aa..5633091450 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -153,6 +153,7 @@ public function renderBurn() { // Merge the synthetic rows into the real transactions. $data = array_merge($create_rows, $data); $data = array_values($data); + $data = isort($data, 'dateCreated'); $stats = array(); $day_buckets = array(); From 554359203467986d2873f274364f80e2bc640239 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 Jan 2018 14:11:54 -0800 Subject: [PATCH 344/865] Add a couple of clarifying comments to the Mercurial protocol parser Summary: See D18857. Ref T13036. See PHI275. Explain what's going on here a little better since it isn't entirely obvious and debugging these stream parsers is a gigantic pain. Test Plan: Read text. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13036 Differential Revision: https://secure.phabricator.com/D18859 --- ...nMercurialWireClientSSHProtocolChannel.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php b/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php index 4c16fb9f0f..8eb026d8fe 100644 --- a/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php +++ b/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php @@ -180,6 +180,15 @@ protected function decodeStream($data) { $this->state = 'arguments'; } } else if ($this->state == 'data-length') { + + // We're reading the length of a chunk of raw data. It looks like + // this: + // + // \n + // + // The length is human-readable text (for example, "4096"), and + // may be 0. + $line = $this->readProtocolLine(); if ($line === null) { break; @@ -192,6 +201,9 @@ protected function decodeStream($data) { $this->state = 'data-bytes'; } } else if ($this->state == 'data-bytes') { + + // We're reading some known, nonzero number of raw bytes of data. + // If we don't have any more bytes on the buffer yet, just bail: // otherwise, we'll emit a pointless and possibly harmful 0-byte data // frame. See T13036 for discussion. @@ -203,6 +215,13 @@ protected function decodeStream($data) { $this->buffer = substr($this->buffer, strlen($bytes)); $this->expectBytes -= strlen($bytes); + // NOTE: We emit a data frame as soon as we read some data. This can + // cause us to repackage frames: for example, if we receive one large + // frame slowly, we may emit it as several smaller frames. In theory + // this is good; in practice, Mercurial never seems to select a frame + // size larger than 4096 bytes naturally and this may be more + // complexity and trouble than it is worth. See T13036. + $messages[] = $this->newDataMessage($bytes); if (!$this->expectBytes) { From adbd7d4fd8a2c80b7f27c10c1432b6eef0196ae4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 4 Jan 2018 14:44:01 -0800 Subject: [PATCH 345/865] Make the new synthetic burnup chart data respect the "Project" filter Summary: See PHI273. Third time's the charm? This page has a "Project" filter which lets you view data for only one project, but the synthetic data currently ignores it. Test Plan: Filtered burnup chart by various projects, saw sensible-looking data. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18860 --- .../controller/ManiphestReportController.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 5633091450..3ba597fb92 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -75,6 +75,7 @@ public function renderBurn() { $conn = $table->establishConnection('r'); $joins = ''; + $create_joins = ''; if ($project_phid) { $joins = qsprintf( $conn, @@ -84,6 +85,12 @@ public function renderBurn() { PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, $project_phid); + $create_joins = qsprintf( + $conn, + 'JOIN %T p ON p.src = t.phid AND p.type = %d AND p.dst = %s', + PhabricatorEdgeConfig::TABLE_NAME_EDGE, + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + $project_phid); } $data = queryfx_all( @@ -113,8 +120,9 @@ public function renderBurn() { // default value. $create_rows = queryfx_all( $conn, - 'SELECT dateCreated FROM %T', - id(new ManiphestTask())->getTableName()); + 'SELECT t.dateCreated FROM %T t %Q', + id(new ManiphestTask())->getTableName(), + $create_joins); foreach ($create_rows as $key => $create_row) { $create_rows[$key] = array( 'transactionType' => 'status', From 2140741e25288b05671e60c8954ffc0c2be7b624 Mon Sep 17 00:00:00 2001 From: Alex Vandiver Date: Sat, 6 Jan 2018 07:25:26 -0800 Subject: [PATCH 346/865] Fix typo in new setting description Summary: Noticed by @amckinley in https://secure.phabricator.com/D18850#inline-57246 but not fixed before landing. Test Plan: ispell Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, amckinley, epriestley Differential Revision: https://secure.phabricator.com/D18861 --- .../diffusion/editor/DiffusionRepositoryEditEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php index 4eebc09b52..dcb79a6c86 100644 --- a/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php +++ b/src/applications/diffusion/editor/DiffusionRepositoryEditEngine.php @@ -318,7 +318,7 @@ protected function buildCustomEditFields($object) { pht('Prevent Enormous Changes'), pht('Allow Enormous Changes')) ->setTransactionType(PhabricatorRepositoryTransaction::TYPE_ENORMOUS) - ->setDescription(pht('Permit enomrous changes to be made.')) + ->setDescription(pht('Permit enormous changes to be made.')) ->setConduitDescription(pht('Allow or prevent enormous changes.')) ->setConduitTypeDescription(pht('New protection setting.')) ->setValue($object->shouldAllowEnormousChanges()), From 82bfb9817920bbd6f75d27cbbf4555e310b826b0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 10 Jan 2018 13:32:44 -0800 Subject: [PATCH 347/865] Fix a copy/paste error on the burnup chart Summary: See PHI286, maybe. See PHI273. Strongly considering just deleting this. Test Plan: ~.~ Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18865 --- .../maniphest/controller/ManiphestReportController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 3ba597fb92..4f546d04c5 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -140,7 +140,7 @@ public function renderBurn() { } $oldv = trim($row['oldValue'], '"'); - $newv = trim($row['oldValue'], '"'); + $newv = trim($row['newValue'], '"'); // If this is a status change, preserve it. if ($oldv != 'null') { From 53b4882b8073439f00502587e9979f93a56e232d Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 10 Jan 2018 13:32:44 -0800 Subject: [PATCH 348/865] (stable) Fix a copy/paste error on the burnup chart Summary: See PHI286, maybe. See PHI273. Strongly considering just deleting this. Test Plan: ~.~ Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18865 --- .../maniphest/controller/ManiphestReportController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 3ba597fb92..4f546d04c5 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -140,7 +140,7 @@ public function renderBurn() { } $oldv = trim($row['oldValue'], '"'); - $newv = trim($row['oldValue'], '"'); + $newv = trim($row['newValue'], '"'); // If this is a status change, preserve it. if ($oldv != 'null') { From 3e983b583dc0b5c46aa0702b346fbd552766a318 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 16 Jan 2018 11:01:29 -0800 Subject: [PATCH 349/865] Fix a transposed feed story in Badges Summary: See . These are swapped. Test Plan: Changed a badge quality, viewed feed story, saw it position the strings correctly. Reviewers: amckinley Reviewed By: amckinley Differential Revision: https://secure.phabricator.com/D18870 --- .../xaction/PhabricatorBadgesBadgeQualityTransaction.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/applications/badges/xaction/PhabricatorBadgesBadgeQualityTransaction.php b/src/applications/badges/xaction/PhabricatorBadgesBadgeQualityTransaction.php index de060c17e7..fc1bf014c0 100644 --- a/src/applications/badges/xaction/PhabricatorBadgesBadgeQualityTransaction.php +++ b/src/applications/badges/xaction/PhabricatorBadgesBadgeQualityTransaction.php @@ -37,11 +37,11 @@ public function getTitleForFeed() { $new = $this->getQualityLabel($this->getNewValue()); return pht( - '%s updated %s quality from %s to %s.', + '%s updated the quality of %s from %s to %s.', $this->renderAuthor(), $this->renderObject(), - $new, - $old); + $old, + $new); } public function validateTransactions($object, array $xactions) { From ad659627b3e931256e295efda941775c4020b713 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 05:14:05 -0800 Subject: [PATCH 350/865] Make bulk editor working set editable and more homogenous Summary: Ref T13025. See PHI50. Fixes T11286. Ref T10005. Begin modernizing the bulk editor. For T10005 ("move the bulk editor to modern infrastructure"), rewrite the rendering of the editable set so that it is application-agnostic and can work with any kind of object. For T11286 ("let users de-select items in the working set"), make the working set editable. Test Plan: {F5302158} - Deselected some objects, applied an edit, saw the edit apply to only selected objects. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13025, T11286, T10005 Differential Revision: https://secure.phabricator.com/D18805 --- resources/celerity/map.php | 17 ++-- .../ManiphestBatchEditController.php | 77 +++++++++++++------ src/view/phui/PHUIObjectItemView.php | 43 +++++++++++ .../phui/object-item/phui-oi-list-view.css | 19 +++++ webroot/rsrc/css/phui/phui-box.css | 4 + .../js/phui/behavior-phui-selectable-list.js | 44 +++++++++++ 6 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 webroot/rsrc/js/phui/behavior-phui-selectable-list.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d937b91e4f..0fdf44344f 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => 'fdb27ef9', + 'core.pkg.css' => '5be8063f', 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -135,14 +135,14 @@ 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'bf094950', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '73c5f5c4', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'f7f61a34', 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', 'rsrc/css/phui/phui-badge.css' => '22c0cf4f', 'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3', 'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c', - 'rsrc/css/phui/phui-box.css' => '9f3745fb', + 'rsrc/css/phui/phui-box.css' => '4bd6cdb9', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', @@ -523,6 +523,7 @@ 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => 'b95d6f7d', 'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb', + 'rsrc/js/phui/behavior-phui-selectable-list.js' => '464259a2', 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', @@ -673,6 +674,7 @@ 'javelin-behavior-phui-dropdown-menu' => 'b95d6f7d', 'javelin-behavior-phui-file-upload' => 'b003d4fb', 'javelin-behavior-phui-hovercards' => 'bcaccd64', + 'javelin-behavior-phui-selectable-list' => '464259a2', 'javelin-behavior-phui-submenu' => 'a6f7a73b', 'javelin-behavior-phui-tab-group' => '0a0b10e9', 'javelin-behavior-phuix-example' => '68af71ca', @@ -820,7 +822,7 @@ 'phui-badge-view-css' => '22c0cf4f', 'phui-basic-nav-view-css' => '98c11ab3', 'phui-big-info-view-css' => 'acc3492c', - 'phui-box-css' => '9f3745fb', + 'phui-box-css' => '4bd6cdb9', 'phui-button-bar-css' => 'f1ff5494', 'phui-button-css' => '1863cc6e', 'phui-button-simple-css' => '8e1baf68', @@ -860,7 +862,7 @@ 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => 'bf094950', + 'phui-oi-list-view-css' => '73c5f5c4', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => 'edcbc226', 'phui-pinboard-view-css' => '2495140e', @@ -1226,6 +1228,11 @@ 'javelin-behavior', 'javelin-dom', ), + '464259a2' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php index f2ec4b433f..c3438a6cee 100644 --- a/src/applications/maniphest/controller/ManiphestBatchEditController.php +++ b/src/applications/maniphest/controller/ManiphestBatchEditController.php @@ -89,12 +89,7 @@ public function handleRequest(AphrontRequest $request) { ->setURI($job->getMonitorURI()); } - $handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); - - $list = new ManiphestTaskListView(); - $list->setTasks($tasks); - $list->setUser($viewer); - $list->setHandles($handles); + $list = $this->newBulkObjectList($tasks); $template = new AphrontTokenizerTemplateView(); $template = $template->render(); @@ -142,21 +137,8 @@ public function handleRequest(AphrontRequest $request) { 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), )); - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('board', $board_id) - ->setID('maniphest-batch-edit-form'); - - foreach ($tasks as $task) { - $form->appendChild( - phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'batch[]', - 'value' => $task->getID(), - ))); - } + $form = id(new PHUIFormLayoutView()) + ->setUser($viewer); $form->appendChild( phutil_tag( @@ -166,6 +148,7 @@ public function handleRequest(AphrontRequest $request) { 'name' => 'actions', 'id' => 'batch-form-actions', ))); + $form->appendChild( id(new PHUIFormInsetView()) ->setTitle(pht('Actions')) @@ -210,17 +193,63 @@ public function handleRequest(AphrontRequest $request) { ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setForm($form); - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter(array( + + $complete_form = phabricator_form( + $viewer, + array( + 'action' => $request->getRequestURI(), + 'method' => 'POST', + 'id' => 'maniphest-batch-edit-form', + ), + array( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'board', + 'value' => $board_id, + )), $task_box, $form_box, )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($complete_form); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($view); } + private function newBulkObjectList(array $objects) { + $viewer = $this->getViewer(); + $objects = mpull($objects, null, 'getPHID'); + + $handles = $viewer->loadHandles(array_keys($objects)); + + $status_closed = PhabricatorObjectHandle::STATUS_CLOSED; + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true); + + foreach ($objects as $phid => $object) { + $handle = $handles[$phid]; + + $is_closed = ($handle->getStatus() === $status_closed); + + $item = id(new PHUIObjectItemView()) + ->setHeader($handle->getFullName()) + ->setHref($handle->getURI()) + ->setDisabled($is_closed) + ->setSelectable('batch[]', $object->getID(), true); + + $list->addItem($item); + } + + return $list; + } + } diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 070d436cf6..4753561124 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -29,6 +29,10 @@ final class PHUIObjectItemView extends AphrontTagView { private $coverImage; private $description; + private $selectableName; + private $selectableValue; + private $isSelected; + public function setDisabled($disabled) { $this->disabled = $disabled; return $this; @@ -160,6 +164,13 @@ public function setDescription($description) { return $this; } + public function setSelectable($name, $value, $is_selected) { + $this->selectableName = $name; + $this->selectableValue = $value; + $this->isSelected = $is_selected; + return $this; + } + public function setEpoch($epoch) { $date = phabricator_datetime($epoch, $this->getUser()); $this->addIcon('none', $date); @@ -239,6 +250,8 @@ protected function getTagName() { } protected function getTagAttributes() { + $sigils = array(); + $item_classes = array(); $item_classes[] = 'phui-oi'; @@ -286,6 +299,17 @@ protected function getTagAttributes() { throw new Exception(pht('Invalid effect!')); } + if ($this->isSelected) { + $item_classes[] = 'phui-oi-selected'; + } + + if ($this->selectableName !== null) { + $item_classes[] = 'phui-oi-selectable'; + $sigils[] = 'phui-oi-selectable'; + + Javelin::initBehavior('phui-selectable-list'); + } + if ($this->getGrippable()) { $item_classes[] = 'phui-oi-grippable'; } @@ -300,6 +324,7 @@ protected function getTagAttributes() { return array( 'class' => $item_classes, + 'sigil' => $sigils, ); } @@ -628,6 +653,24 @@ protected function getTagContent() { $countdown); } + if ($this->selectableName !== null) { + $checkbox = phutil_tag( + 'input', + array( + 'type' => 'checkbox', + 'name' => $this->selectableName, + 'value' => $this->selectableValue, + 'checked' => ($this->isSelected ? 'checked' : null), + )); + + $column0 = phutil_tag( + 'div', + array( + 'class' => 'phui-oi-col0 phui-oi-checkbox', + ), + $checkbox); + } + $column1 = phutil_tag( 'div', array( diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index e356396451..4ee5d41b67 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -664,3 +664,22 @@ ul.phui-oi-list-view .phui-oi-selected padding: 0 8px 8px; text-align: left; } + +.phui-oi-col0.phui-oi-checkbox { + width: 28px; + text-align: center; +} + +.phui-oi-selectable { + cursor: pointer; + user-select: none; + -webkit-user-select: none; +} + +/* When the list selection state can be toggled on the client (as in the bulk + editor), keep the border color consistent to make the interaction feel more + robust. */ +ul.phui-oi-list-view .phui-oi-selectable + .phui-oi-frame { + border-color: {$blueborder}; +} diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 278f1365e8..c5fd1d8d3b 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -103,6 +103,10 @@ body.device .phui-box-blue-property.phui-object-box.phui-object-box-collapsed padding: 2px 8px; } +.phui-box-blue-property .phui-oi-list-view.phui-oi-list-flush { + padding: 0; +} + body .phui-box-blue-property.phui-object-box.phui-object-box-collapsed { padding: 0; } diff --git a/webroot/rsrc/js/phui/behavior-phui-selectable-list.js b/webroot/rsrc/js/phui/behavior-phui-selectable-list.js new file mode 100644 index 0000000000..eaa33565d0 --- /dev/null +++ b/webroot/rsrc/js/phui/behavior-phui-selectable-list.js @@ -0,0 +1,44 @@ +/** + * @provides javelin-behavior-phui-selectable-list + * @requires javelin-behavior + * javelin-stratcom + * javelin-dom + */ + +JX.behavior('phui-selectable-list', function() { + + JX.Stratcom.listen('click', 'phui-oi-selectable', function(e) { + if (!e.isNormalClick()) { + return; + } + + // If the user clicked a link, ignore it. + if (e.getNode('tag:a')) { + return; + } + + var root = e.getNode('phui-oi-selectable'); + + // If the user did not click the checkbox, pretend they did. This makes + // the entire element a click target to make changing the selection set a + // bit easier. + if (!e.getNode('tag:input')) { + var checkbox = getCheckbox(root); + checkbox.checked = !checkbox.checked; + + e.kill(); + } + + setTimeout(JX.bind(null, redraw, root), 0); + }); + + function getCheckbox(root) { + return JX.DOM.find(root, 'input'); + } + + function redraw(root) { + var checkbox = getCheckbox(root); + JX.DOM.alterClass(root, 'phui-oi-selected', !!checkbox.checked); + } + +}); From 7f91c8c4acbb65ce7bd8a9fad217fd8a9db30112 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 30 Nov 2017 05:57:39 -0800 Subject: [PATCH 351/865] Rebuild the bulk editor on SearchEngine Summary: Depends on D18805. Ref T13025. Fixes T10268. Instead of using a list of IDs for the bulk editor, power it with SearchEngine queries. This gives us the full power of SearchEngine and lets us use a query key instead of a list of 20,000 IDs to avoid issues with URL lengths. Also, split it into a base `BulkEngine` and per-application subclasses. This moves us toward T10005 and universal support for bulk operations. Also: - Renames most of "batch" to "bulk": we're curently inconsitent about this, I like "bulk" better since I think it's more clear if you don't regularly interact with `.bat` files, and newer stuff mostly uses "bulk". - When objects in the result set can't be edited because you don't have permission, show the status more clearly. This probably breaks some stuff a bit since I refactored so heavily, but it seems mostly OK from poking around. I'll clean up anything I missed in followups to deal with remaining items on T13025. Test Plan: {F5302300} - Bulk edited from Maniphest. - Bulk edited from a workboard (no more giant `?ids=....` in the URL). - Hit most of the error conditions, I think? - Clicked the "Cancel" button. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13025, T10268 Differential Revision: https://secure.phabricator.com/D18806 --- resources/celerity/map.php | 24 +- src/__phutil_library_map__.php | 8 +- .../base/PhabricatorApplication.php | 4 + .../PhabricatorManiphestApplication.php | 2 +- .../bulk/ManiphestTaskBulkEngine.php | 50 ++ .../ManiphestBatchEditController.php | 255 ---------- .../ManiphestBulkEditController.php | 32 ++ .../view/ManiphestTaskResultListView.php | 2 +- .../PhabricatorProjectBoardViewController.php | 23 +- .../bulk/PhabricatorBulkEngine.php | 454 ++++++++++++++++++ src/view/phui/PHUIObjectItemView.php | 36 +- .../phui/object-item/phui-oi-list-view.css | 4 + .../maniphest/behavior-batch-selector.js | 11 +- 13 files changed, 612 insertions(+), 293 deletions(-) create mode 100644 src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php delete mode 100644 src/applications/maniphest/controller/ManiphestBatchEditController.php create mode 100644 src/applications/maniphest/controller/ManiphestBulkEditController.php create mode 100644 src/applications/transactions/bulk/PhabricatorBulkEngine.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0fdf44344f..650d3f7efb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', - 'core.pkg.css' => '5be8063f', + 'core.pkg.css' => '075f9867', 'core.pkg.js' => '4c79d74f', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '45951e9e', @@ -18,7 +18,7 @@ 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', 'maniphest.pkg.css' => '4845691a', - 'maniphest.pkg.js' => '5ab2753f', + 'maniphest.pkg.js' => '4d7e79c8', 'rsrc/audio/basic/alert.mp3' => '98461568', 'rsrc/audio/basic/bing.mp3' => 'ab8603a5', 'rsrc/audio/basic/pock.mp3' => '0cc772f5', @@ -135,7 +135,7 @@ 'rsrc/css/phui/object-item/phui-oi-color.css' => 'cd2b9b77', 'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => '08f4ccc3', 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '9d9685d6', - 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '73c5f5c4', + 'rsrc/css/phui/object-item/phui-oi-list-view.css' => '6ae18df0', 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => 'a8beebea', 'rsrc/css/phui/phui-action-list.css' => 'f7f61a34', 'rsrc/css/phui/phui-action-panel.css' => 'b4798122', @@ -420,7 +420,7 @@ 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', - 'rsrc/js/application/maniphest/behavior-batch-selector.js' => '0825c27a', + 'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e', 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', 'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763', @@ -643,7 +643,7 @@ 'javelin-behavior-line-chart' => 'e4232876', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', - 'javelin-behavior-maniphest-batch-selector' => '0825c27a', + 'javelin-behavior-maniphest-batch-selector' => 'ad54037e', 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 'javelin-behavior-maniphest-subpriority-editor' => '71237763', 'javelin-behavior-owners-path-editor' => '7a68dda3', @@ -862,7 +862,7 @@ 'phui-oi-color-css' => 'cd2b9b77', 'phui-oi-drag-ui-css' => '08f4ccc3', 'phui-oi-flush-ui-css' => '9d9685d6', - 'phui-oi-list-view-css' => '73c5f5c4', + 'phui-oi-list-view-css' => '6ae18df0', 'phui-oi-simple-ui-css' => 'a8beebea', 'phui-pager-css' => 'edcbc226', 'phui-pinboard-view-css' => '2495140e', @@ -960,12 +960,6 @@ 'javelin-stratcom', 'javelin-workflow', ), - '0825c27a' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), '08f4ccc3' => array( 'phui-oi-list-view-css', ), @@ -1815,6 +1809,12 @@ 'phuix-autocomplete', 'javelin-mask', ), + 'ad54037e' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), 'b003d4fb' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f4a410ad2e..3ff9ce4fd3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1487,8 +1487,8 @@ 'MacroQueryConduitAPIMethod' => 'applications/macro/conduit/MacroQueryConduitAPIMethod.php', 'ManiphestAssignEmailCommand' => 'applications/maniphest/command/ManiphestAssignEmailCommand.php', 'ManiphestAssigneeDatasource' => 'applications/maniphest/typeahead/ManiphestAssigneeDatasource.php', - 'ManiphestBatchEditController' => 'applications/maniphest/controller/ManiphestBatchEditController.php', 'ManiphestBulkEditCapability' => 'applications/maniphest/capability/ManiphestBulkEditCapability.php', + 'ManiphestBulkEditController' => 'applications/maniphest/controller/ManiphestBulkEditController.php', 'ManiphestClaimEmailCommand' => 'applications/maniphest/command/ManiphestClaimEmailCommand.php', 'ManiphestCloseEmailCommand' => 'applications/maniphest/command/ManiphestCloseEmailCommand.php', 'ManiphestConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestConduitAPIMethod.php', @@ -1547,6 +1547,7 @@ 'ManiphestTaskAttachTransaction' => 'applications/maniphest/xaction/ManiphestTaskAttachTransaction.php', 'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php', 'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php', + 'ManiphestTaskBulkEngine' => 'applications/maniphest/bulk/ManiphestTaskBulkEngine.php', 'ManiphestTaskCloseAsDuplicateRelationship' => 'applications/maniphest/relationship/ManiphestTaskCloseAsDuplicateRelationship.php', 'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php', 'ManiphestTaskCoverImageTransaction' => 'applications/maniphest/xaction/ManiphestTaskCoverImageTransaction.php', @@ -2198,6 +2199,7 @@ 'PhabricatorBuiltinFileCachePurger' => 'applications/cache/purger/PhabricatorBuiltinFileCachePurger.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php', + 'PhabricatorBulkEngine' => 'applications/transactions/bulk/PhabricatorBulkEngine.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', 'PhabricatorCacheEngine' => 'applications/system/engine/PhabricatorCacheEngine.php', 'PhabricatorCacheEngineExtension' => 'applications/system/engine/PhabricatorCacheEngineExtension.php', @@ -6678,8 +6680,8 @@ 'MacroQueryConduitAPIMethod' => 'MacroConduitAPIMethod', 'ManiphestAssignEmailCommand' => 'ManiphestEmailCommand', 'ManiphestAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', - 'ManiphestBatchEditController' => 'ManiphestController', 'ManiphestBulkEditCapability' => 'PhabricatorPolicyCapability', + 'ManiphestBulkEditController' => 'ManiphestController', 'ManiphestClaimEmailCommand' => 'ManiphestEmailCommand', 'ManiphestCloseEmailCommand' => 'ManiphestEmailCommand', 'ManiphestConduitAPIMethod' => 'ConduitAPIMethod', @@ -6761,6 +6763,7 @@ 'ManiphestTaskAttachTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule', + 'ManiphestTaskBulkEngine' => 'PhabricatorBulkEngine', 'ManiphestTaskCloseAsDuplicateRelationship' => 'ManiphestTaskRelationship', 'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskCoverImageTransaction' => 'ManiphestTaskTransactionType', @@ -7487,6 +7490,7 @@ 'PhabricatorBuiltinFileCachePurger' => 'PhabricatorCachePurger', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 'PhabricatorBulkContentSource' => 'PhabricatorContentSource', + 'PhabricatorBulkEngine' => 'Phobject', 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', 'PhabricatorCacheEngine' => 'Phobject', 'PhabricatorCacheEngineExtension' => 'Phobject', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index c25612408c..0ae8e75354 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -618,6 +618,10 @@ protected function getEditRoutePattern($base = null) { ')?'; } + protected function getBulkRoutePattern($base = null) { + return $base.'(?:query/(?P[^/]+)/)?'; + } + protected function getQueryRoutePattern($base = null) { return $base.'(?:query/(?P[^/]+)/)?'; } diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index 3076354599..6e4ac0a8f6 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -52,7 +52,7 @@ public function getRoutes() { '/maniphest/' => array( '(?:query/(?P[^/]+)/)?' => 'ManiphestTaskListController', 'report/(?:(?P\w+)/)?' => 'ManiphestReportController', - 'batch/' => 'ManiphestBatchEditController', + $this->getBulkRoutePattern('bulk/') => 'ManiphestBulkEditController', 'task/' => array( $this->getEditRoutePattern('edit/') => 'ManiphestTaskEditController', diff --git a/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php b/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php new file mode 100644 index 0000000000..8b62f627d8 --- /dev/null +++ b/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php @@ -0,0 +1,50 @@ +workboard = $workboard; + return $this; + } + + public function getWorkboard() { + return $this->workboard; + } + + public function newSearchEngine() { + return new ManiphestTaskSearchEngine(); + } + + public function getDoneURI() { + $board_uri = $this->getBoardURI(); + if ($board_uri) { + return $board_uri; + } + + return parent::getDoneURI(); + } + + public function getCancelURI() { + $board_uri = $this->getBoardURI(); + if ($board_uri) { + return $board_uri; + } + + return parent::getCancelURI(); + } + + private function getBoardURI() { + $workboard = $this->getWorkboard(); + + if ($workboard) { + $project_id = $workboard->getID(); + return "/project/board/{$project_id}/"; + } + + return null; + } + +} diff --git a/src/applications/maniphest/controller/ManiphestBatchEditController.php b/src/applications/maniphest/controller/ManiphestBatchEditController.php deleted file mode 100644 index c3438a6cee..0000000000 --- a/src/applications/maniphest/controller/ManiphestBatchEditController.php +++ /dev/null @@ -1,255 +0,0 @@ -getViewer(); - - $this->requireApplicationCapability( - ManiphestBulkEditCapability::CAPABILITY); - - $project = null; - $board_id = $request->getInt('board'); - if ($board_id) { - $project = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withIDs(array($board_id)) - ->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - } - - $task_ids = $request->getArr('batch'); - if (!$task_ids) { - $task_ids = $request->getStrList('batch'); - } - - if (!$task_ids) { - throw new Exception( - pht( - 'No tasks are selected.')); - } - - $tasks = id(new ManiphestTaskQuery()) - ->setViewer($viewer) - ->withIDs($task_ids) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->needSubscriberPHIDs(true) - ->needProjectPHIDs(true) - ->execute(); - - if (!$tasks) { - throw new Exception( - pht("You don't have permission to edit any of the selected tasks.")); - } - - if ($project) { - $cancel_uri = '/project/board/'.$project->getID().'/'; - $redirect_uri = $cancel_uri; - } else { - $cancel_uri = '/maniphest/'; - $redirect_uri = '/maniphest/?ids='.implode(',', mpull($tasks, 'getID')); - } - - $actions = $request->getStr('actions'); - if ($actions) { - $actions = phutil_json_decode($actions); - } - - if ($request->isFormPost() && $actions) { - $job = PhabricatorWorkerBulkJob::initializeNewJob( - $viewer, - new ManiphestTaskEditBulkJobType(), - array( - 'taskPHIDs' => mpull($tasks, 'getPHID'), - 'actions' => $actions, - 'cancelURI' => $cancel_uri, - 'doneURI' => $redirect_uri, - )); - - $type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS; - - $xactions = array(); - $xactions[] = id(new PhabricatorWorkerBulkJobTransaction()) - ->setTransactionType($type_status) - ->setNewValue(PhabricatorWorkerBulkJob::STATUS_CONFIRM); - - $editor = id(new PhabricatorWorkerBulkJobEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnMissingFields(true) - ->applyTransactions($job, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI($job->getMonitorURI()); - } - - $list = $this->newBulkObjectList($tasks); - - $template = new AphrontTokenizerTemplateView(); - $template = $template->render(); - - $projects_source = new PhabricatorProjectDatasource(); - $mailable_source = new PhabricatorMetaMTAMailableDatasource(); - $mailable_source->setViewer($viewer); - $owner_source = new ManiphestAssigneeDatasource(); - $owner_source->setViewer($viewer); - $spaces_source = id(new PhabricatorSpacesNamespaceDatasource()) - ->setViewer($viewer); - - require_celerity_resource('maniphest-batch-editor'); - Javelin::initBehavior( - 'maniphest-batch-editor', - array( - 'root' => 'maniphest-batch-edit-form', - 'tokenizerTemplate' => $template, - 'sources' => array( - 'project' => array( - 'src' => $projects_source->getDatasourceURI(), - 'placeholder' => $projects_source->getPlaceholderText(), - 'browseURI' => $projects_source->getBrowseURI(), - ), - 'owner' => array( - 'src' => $owner_source->getDatasourceURI(), - 'placeholder' => $owner_source->getPlaceholderText(), - 'browseURI' => $owner_source->getBrowseURI(), - 'limit' => 1, - ), - 'cc' => array( - 'src' => $mailable_source->getDatasourceURI(), - 'placeholder' => $mailable_source->getPlaceholderText(), - 'browseURI' => $mailable_source->getBrowseURI(), - ), - 'spaces' => array( - 'src' => $spaces_source->getDatasourceURI(), - 'placeholder' => $spaces_source->getPlaceholderText(), - 'browseURI' => $spaces_source->getBrowseURI(), - 'limit' => 1, - ), - ), - 'input' => 'batch-form-actions', - 'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(), - 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), - )); - - $form = id(new PHUIFormLayoutView()) - ->setUser($viewer); - - $form->appendChild( - phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'actions', - 'id' => 'batch-form-actions', - ))); - - $form->appendChild( - id(new PHUIFormInsetView()) - ->setTitle(pht('Actions')) - ->setRightButton(javelin_tag( - 'a', - array( - 'href' => '#', - 'class' => 'button button-green', - 'sigil' => 'add-action', - 'mustcapture' => true, - ), - pht('Add Another Action'))) - ->setContent(javelin_tag( - 'table', - array( - 'sigil' => 'maniphest-batch-actions', - 'class' => 'maniphest-batch-actions-table', - ), - ''))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Update Tasks')) - ->addCancelButton($cancel_uri)); - - $title = pht('Batch Editor'); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - $crumbs->setBorder(true); - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Batch Editor')) - ->setHeaderIcon('fa-pencil-square-o'); - - $task_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Selected Tasks')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setObjectList($list); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Actions')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setForm($form); - - - $complete_form = phabricator_form( - $viewer, - array( - 'action' => $request->getRequestURI(), - 'method' => 'POST', - 'id' => 'maniphest-batch-edit-form', - ), - array( - phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'board', - 'value' => $board_id, - )), - $task_box, - $form_box, - )); - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - ->setFooter($complete_form); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild($view); - } - - private function newBulkObjectList(array $objects) { - $viewer = $this->getViewer(); - $objects = mpull($objects, null, 'getPHID'); - - $handles = $viewer->loadHandles(array_keys($objects)); - - $status_closed = PhabricatorObjectHandle::STATUS_CLOSED; - - $list = id(new PHUIObjectItemListView()) - ->setViewer($viewer) - ->setFlush(true); - - foreach ($objects as $phid => $object) { - $handle = $handles[$phid]; - - $is_closed = ($handle->getStatus() === $status_closed); - - $item = id(new PHUIObjectItemView()) - ->setHeader($handle->getFullName()) - ->setHref($handle->getURI()) - ->setDisabled($is_closed) - ->setSelectable('batch[]', $object->getID(), true); - - $list->addItem($item); - } - - return $list; - } - -} diff --git a/src/applications/maniphest/controller/ManiphestBulkEditController.php b/src/applications/maniphest/controller/ManiphestBulkEditController.php new file mode 100644 index 0000000000..b698e54848 --- /dev/null +++ b/src/applications/maniphest/controller/ManiphestBulkEditController.php @@ -0,0 +1,32 @@ +getViewer(); + + $this->requireApplicationCapability( + ManiphestBulkEditCapability::CAPABILITY); + + $bulk_engine = id(new ManiphestTaskBulkEngine()) + ->setViewer($viewer) + ->setController($this) + ->addContextParameter('board'); + + $board_id = $request->getInt('board'); + if ($board_id) { + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withIDs(array($board_id)) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + + $bulk_engine->setWorkboard($project); + } + + return $bulk_engine->buildResponse(); + } + +} diff --git a/src/applications/maniphest/view/ManiphestTaskResultListView.php b/src/applications/maniphest/view/ManiphestTaskResultListView.php index 0144df0e33..2203db8bfc 100644 --- a/src/applications/maniphest/view/ManiphestTaskResultListView.php +++ b/src/applications/maniphest/view/ManiphestTaskResultListView.php @@ -255,7 +255,7 @@ private function renderBatchEditor(PhabricatorSavedQuery $saved_query) { $user, array( 'method' => 'POST', - 'action' => '/maniphest/batch/', + 'action' => '/maniphest/bulk/', 'id' => 'batch-select-form', ), $editor); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 793fe5603d..9396d1873e 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -230,14 +230,23 @@ public function handleRequest(AphrontRequest $request) { ->addCancelButton($board_uri); } - $batch_ids = mpull($batch_tasks, 'getID'); - $batch_ids = implode(',', $batch_ids); + // Create a saved query to hold the working set. This allows us to get + // around URI length limitations with a long "?ids=..." query string. + // For details, see T10268. + $search_engine = id(new ManiphestTaskSearchEngine()) + ->setViewer($viewer); + + $saved_query = $search_engine->newSavedQuery(); + $saved_query->setParameter('ids', mpull($batch_tasks, 'getID')); + $search_engine->saveQuery($saved_query); + + $query_key = $saved_query->getQueryKey(); + + $bulk_uri = new PhutilURI("/maniphest/bulk/query/{$query_key}/"); + $bulk_uri->setQueryParam('board', $this->id); - $batch_uri = new PhutilURI('/maniphest/batch/'); - $batch_uri->setQueryParam('board', $this->id); - $batch_uri->setQueryParam('batch', $batch_ids); return id(new AphrontRedirectResponse()) - ->setURI($batch_uri); + ->setURI($bulk_uri); } $move_id = $request->getStr('move'); @@ -1048,7 +1057,7 @@ private function buildColumnMenu( $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-list-ul') - ->setName(pht('Batch Edit Tasks...')) + ->setName(pht('Bulk Edit Tasks...')) ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); diff --git a/src/applications/transactions/bulk/PhabricatorBulkEngine.php b/src/applications/transactions/bulk/PhabricatorBulkEngine.php new file mode 100644 index 0000000000..82dc7f2510 --- /dev/null +++ b/src/applications/transactions/bulk/PhabricatorBulkEngine.php @@ -0,0 +1,454 @@ +savedQuery; + if ($saved_query) { + $path = '/query/'.$saved_query->getQueryKey().'/'; + } else { + $path = '/'; + } + + return $this->getQueryURI($path); + } + + public function getDoneURI() { + if ($this->objectList !== null) { + $ids = mpull($this->objectList, 'getID'); + $path = '/?ids='.implode(',', $ids); + } else { + $path = '/'; + } + + return $this->getQueryURI($path); + } + + protected function getQueryURI($path = '/') { + $viewer = $this->getViewer(); + + $engine = id($this->newSearchEngine()) + ->setViewer($viewer); + + return $engine->getQueryBaseURI().ltrim($path, '/'); + } + + protected function getBulkURI() { + $saved_query = $this->savedQuery; + if ($saved_query) { + $path = '/query/'.$saved_query->getQueryKey().'/'; + } else { + $path = '/'; + } + + return $this->getBulkBaseURI($path); + } + + protected function getBulkBaseURI($path) { + return $this->getQueryURI('bulk/'.ltrim($path, '/')); + } + + final public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + final public function setController(PhabricatorController $controller) { + $this->controller = $controller; + return $this; + } + + final public function getController() { + return $this->controller; + } + + final public function addContextParameter($key) { + $this->context[$key] = true; + return $this; + } + + final public function buildResponse() { + $viewer = $this->getViewer(); + $controller = $this->getController(); + $request = $controller->getRequest(); + + $response = $this->loadObjectList(); + if ($response) { + return $response; + } + + if ($request->isFormPost() && $request->getBool('bulkEngine')) { + return $this->buildEditResponse(); + } + + $list_view = $this->newBulkObjectList(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Bulk Editor')) + ->setHeaderIcon('fa-pencil-square-o'); + + $list_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Working Set')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setObjectList($list_view); + + $form_view = $this->newBulkActionForm(); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Actions')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form_view); + + $complete_form = phabricator_form( + $viewer, + array( + 'action' => $this->getBulkURI(), + 'method' => 'POST', + 'id' => 'maniphest-batch-edit-form', + ), + array( + $this->newContextInputs(), + $list_box, + $form_box, + )); + + $column_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($complete_form); + + // TODO: This is a bit hacky and inflexible. + $crumbs = $controller->buildApplicationCrumbsForEditEngine(); + $crumbs->addTextCrumb(pht('Query'), $this->getCancelURI()); + $crumbs->addTextCrumb(pht('Bulk Editor')); + + return $controller->newPage() + ->setTitle(pht('Bulk Edit')) + ->setCrumbs($crumbs) + ->appendChild($column_view); + } + + private function loadObjectList() { + $viewer = $this->getViewer(); + $controller = $this->getController(); + $request = $controller->getRequest(); + + $search_engine = id($this->newSearchEngine()) + ->setViewer($viewer); + + $query_key = $request->getURIData('queryKey'); + if (strlen($query_key)) { + if ($search_engine->isBuiltinQuery($query_key)) { + $saved = $search_engine->buildSavedQueryFromBuiltin($query_key); + } else { + $saved = id(new PhabricatorSavedQueryQuery()) + ->setViewer($viewer) + ->withQueryKeys(array($query_key)) + ->executeOne(); + if (!$saved) { + return new Aphront404Response(); + } + } + } else { + // TODO: For now, since we don't deal gracefully with queries which + // match a huge result set, just bail if we don't have any query + // parameters instead of querying for a trillion tasks and timing out. + $request_data = $request->getPassthroughRequestData(); + if (!$request_data) { + throw new Exception( + pht( + 'Expected a query key or a set of query constraints.')); + } + + $saved = $search_engine->buildSavedQueryFromRequest($request); + $search_engine->saveQuery($saved); + } + + $object_query = $search_engine->buildQueryFromSavedQuery($saved) + ->setViewer($viewer); + $object_list = $object_query->execute(); + $object_list = mpull($object_list, null, 'getPHID'); + + // If the user has submitted the bulk edit form, select only the objects + // they checked. + if ($request->getBool('bulkEngine')) { + $target_phids = $request->getArr('bulkTargetPHIDs'); + + // NOTE: It's possible that the underlying query result set has changed + // between the time we ran the query initially and now: for example, the + // query was for "Open Tasks" and some tasks were closed while the user + // was making action selections. + + // This could result in some objects getting dropped from the working set + // here: we'll have target PHIDs for them, but they will no longer be + // part of the object list. For now, just go with this since it doesn't + // seem like a big problem and may even be desirable. + + $this->targetList = array_select_keys($object_list, $target_phids); + } else { + $this->targetList = $object_list; + } + + $this->objectList = $object_list; + $this->savedQuery = $saved; + + // Filter just the editable objects. We show all the objects which the + // query matches whether they're editable or not, but indicate which ones + // can not be edited to the user. + + $editable_list = id(new PhabricatorPolicyFilter()) + ->setViewer($viewer) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->apply($object_list); + $this->editableList = mpull($editable_list, null, 'getPHID'); + + return null; + } + + private function newBulkObjectList() { + $viewer = $this->getViewer(); + + $objects = $this->objectList; + $objects = mpull($objects, null, 'getPHID'); + + $handles = $viewer->loadHandles(array_keys($objects)); + + $status_closed = PhabricatorObjectHandle::STATUS_CLOSED; + + $list = id(new PHUIObjectItemListView()) + ->setViewer($viewer) + ->setFlush(true); + + foreach ($objects as $phid => $object) { + $handle = $handles[$phid]; + + $is_closed = ($handle->getStatus() === $status_closed); + $can_edit = isset($this->editableList[$phid]); + $is_disabled = ($is_closed || !$can_edit); + $is_selected = isset($this->targetList[$phid]); + + $item = id(new PHUIObjectItemView()) + ->setHeader($handle->getFullName()) + ->setHref($handle->getURI()) + ->setDisabled($is_disabled) + ->setSelectable('bulkTargetPHIDs[]', $phid, $is_selected, !$can_edit); + + if (!$can_edit) { + $item->addIcon('fa-pencil red', pht('Not Editable')); + } + + $list->addItem($item); + } + + return $list; + } + + private function newContextInputs() { + $viewer = $this->getViewer(); + $controller = $this->getController(); + $request = $controller->getRequest(); + + $parameters = array(); + foreach ($this->context as $key => $value) { + $parameters[$key] = $request->getStr($key); + } + + $parameters = array( + 'bulkEngine' => 1, + ) + $parameters; + + $result = array(); + foreach ($parameters as $key => $value) { + $result[] = phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $key, + 'value' => $value, + )); + } + + return $result; + } + + private function newBulkActionForm() { + $viewer = $this->getViewer(); + + $cancel_uri = $this->getCancelURI(); + + $template = new AphrontTokenizerTemplateView(); + $template = $template->render(); + + $projects_source = new PhabricatorProjectDatasource(); + $mailable_source = new PhabricatorMetaMTAMailableDatasource(); + $mailable_source->setViewer($viewer); + $owner_source = new ManiphestAssigneeDatasource(); + $owner_source->setViewer($viewer); + $spaces_source = id(new PhabricatorSpacesNamespaceDatasource()) + ->setViewer($viewer); + + require_celerity_resource('maniphest-batch-editor'); + + Javelin::initBehavior( + 'maniphest-batch-editor', + array( + 'root' => 'maniphest-batch-edit-form', + 'tokenizerTemplate' => $template, + 'sources' => array( + 'project' => array( + 'src' => $projects_source->getDatasourceURI(), + 'placeholder' => $projects_source->getPlaceholderText(), + 'browseURI' => $projects_source->getBrowseURI(), + ), + 'owner' => array( + 'src' => $owner_source->getDatasourceURI(), + 'placeholder' => $owner_source->getPlaceholderText(), + 'browseURI' => $owner_source->getBrowseURI(), + 'limit' => 1, + ), + 'cc' => array( + 'src' => $mailable_source->getDatasourceURI(), + 'placeholder' => $mailable_source->getPlaceholderText(), + 'browseURI' => $mailable_source->getBrowseURI(), + ), + 'spaces' => array( + 'src' => $spaces_source->getDatasourceURI(), + 'placeholder' => $spaces_source->getPlaceholderText(), + 'browseURI' => $spaces_source->getBrowseURI(), + 'limit' => 1, + ), + ), + 'input' => 'batch-form-actions', + 'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(), + 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), + )); + + $form = id(new PHUIFormLayoutView()) + ->setUser($viewer); + + $form->appendChild( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'actions', + 'id' => 'batch-form-actions', + ))); + + $form->appendChild( + id(new PHUIFormInsetView()) + ->setTitle(pht('Bulk Edit Actions')) + ->setRightButton( + javelin_tag( + 'a', + array( + 'href' => '#', + 'class' => 'button button-green', + 'sigil' => 'add-action', + 'mustcapture' => true, + ), + pht('Add Another Action'))) + ->setContent( + javelin_tag( + 'table', + array( + 'sigil' => 'maniphest-batch-actions', + 'class' => 'maniphest-batch-actions-table', + ), + ''))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Apply Bulk Edit')) + ->addCancelButton($cancel_uri)); + + return $form; + } + + private function buildEditResponse() { + $viewer = $this->getViewer(); + $controller = $this->getController(); + $request = $controller->getRequest(); + + if (!$this->objectList) { + throw new Exception(pht('Query does not match any objects.')); + } + + if (!$this->editableList) { + throw new Exception( + pht( + 'Query does not match any objects you have permission to edit.')); + } + + // Restrict the selection set to objects the user can actually edit. + $objects = array_intersect_key($this->editableList, $this->targetList); + + if (!$objects) { + throw new Exception( + pht( + 'You have not selected any objects to edit.')); + } + + $raw_actions = $request->getStr('actions'); + if ($raw_actions) { + $actions = phutil_json_decode($raw_actions); + } else { + $actions = array(); + } + + if (!$actions) { + throw new Exception( + pht( + 'You have not chosen any edits to apply.')); + } + + $cancel_uri = $this->getCancelURI(); + $done_uri = $this->getDoneURI(); + + $job = PhabricatorWorkerBulkJob::initializeNewJob( + $viewer, + // TODO: This is a Maniphest-specific job type for now, but will become + // a generic one so it gets to live here for now instead of in the task + // specific BulkEngine subclass. + new ManiphestTaskEditBulkJobType(), + array( + 'taskPHIDs' => mpull($objects, 'getPHID'), + 'actions' => $actions, + 'cancelURI' => $cancel_uri, + 'doneURI' => $done_uri, + )); + + $type_status = PhabricatorWorkerBulkJobTransaction::TYPE_STATUS; + + $xactions = array(); + $xactions[] = id(new PhabricatorWorkerBulkJobTransaction()) + ->setTransactionType($type_status) + ->setNewValue(PhabricatorWorkerBulkJob::STATUS_CONFIRM); + + $editor = id(new PhabricatorWorkerBulkJobEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->applyTransactions($job, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($job->getMonitorURI()); + } + +} diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index 4753561124..c1e57632c4 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -32,6 +32,7 @@ final class PHUIObjectItemView extends AphrontTagView { private $selectableName; private $selectableValue; private $isSelected; + private $isForbidden; public function setDisabled($disabled) { $this->disabled = $disabled; @@ -164,10 +165,17 @@ public function setDescription($description) { return $this; } - public function setSelectable($name, $value, $is_selected) { + public function setSelectable( + $name, + $value, + $is_selected, + $is_forbidden = false) { + $this->selectableName = $name; $this->selectableValue = $value; $this->isSelected = $is_selected; + $this->isForbidden = $is_forbidden; + return $this; } @@ -299,11 +307,13 @@ protected function getTagAttributes() { throw new Exception(pht('Invalid effect!')); } - if ($this->isSelected) { + if ($this->isForbidden) { + $item_classes[] = 'phui-oi-forbidden'; + } else if ($this->isSelected) { $item_classes[] = 'phui-oi-selected'; } - if ($this->selectableName !== null) { + if ($this->selectableName !== null && !$this->isForbidden) { $item_classes[] = 'phui-oi-selectable'; $sigils[] = 'phui-oi-selectable'; @@ -654,14 +664,18 @@ protected function getTagContent() { } if ($this->selectableName !== null) { - $checkbox = phutil_tag( - 'input', - array( - 'type' => 'checkbox', - 'name' => $this->selectableName, - 'value' => $this->selectableValue, - 'checked' => ($this->isSelected ? 'checked' : null), - )); + if (!$this->isForbidden) { + $checkbox = phutil_tag( + 'input', + array( + 'type' => 'checkbox', + 'name' => $this->selectableName, + 'value' => $this->selectableValue, + 'checked' => ($this->isSelected ? 'checked' : null), + )); + } else { + $checkbox = null; + } $column0 = phutil_tag( 'div', diff --git a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css index 4ee5d41b67..ff79a8d70b 100644 --- a/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css +++ b/webroot/rsrc/css/phui/object-item/phui-oi-list-view.css @@ -455,6 +455,10 @@ ul.phui-oi-list-view .phui-oi-selected border-color: {$sh-blueborder}; } +.phui-oi-forbidden { + background: {$sh-redbackground}; +} + /* - Handle Icons -------------------------------------------------------------- diff --git a/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js b/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js index b7728abb57..b62f40a589 100644 --- a/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js +++ b/webroot/rsrc/js/application/maniphest/behavior-batch-selector.js @@ -157,12 +157,15 @@ JX.behavior('maniphest-batch-selector', function(config) { 'submit', null, function() { - var inputs = []; + var ids = []; for (var k in selected) { - inputs.push( - JX.$N('input', {type: 'hidden', name: 'batch[]', value: k})); + ids.push(k); } - JX.DOM.setContent(JX.$(config.idContainer), inputs); + ids = ids.join(','); + + var input = JX.$N('input', {type: 'hidden', name: 'ids', value: ids}); + + JX.DOM.setContent(JX.$(config.idContainer), input); }); update(); From 6ef45d82459336daeeaf2e08fd2c6b3c1faade44 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 5 Jan 2018 11:08:16 -0800 Subject: [PATCH 352/865] Provide a generic transaction-oriented bulk job worker Summary: Depends on D18806. Ref T13025. See PHI173. Currently, Maniphest bulk edits are processed by a Maniphest-specific worker. I want to replace this with a generic worker which can apply transactional edits to any object. This implements a generic worker, although it has no callers yet. Future changes give it callers, and later remove the Maniphest-specific worker. Test Plan: See next changes. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13025 Differential Revision: https://secure.phabricator.com/D18862 --- src/__phutil_library_map__.php | 2 + .../bulk/PhabricatorEditEngineBulkJobType.php | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/applications/transactions/bulk/PhabricatorEditEngineBulkJobType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3ff9ce4fd3..b39d89a087 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2729,6 +2729,7 @@ 'PhabricatorEdgesDestructionEngineExtension' => 'infrastructure/edges/engineextension/PhabricatorEdgesDestructionEngineExtension.php', 'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php', 'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php', + 'PhabricatorEditEngineBulkJobType' => 'applications/transactions/bulk/PhabricatorEditEngineBulkJobType.php', 'PhabricatorEditEngineCheckboxesCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCheckboxesCommentAction.php', 'PhabricatorEditEngineColumnsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php', 'PhabricatorEditEngineCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php', @@ -8110,6 +8111,7 @@ 'PhabricatorPolicyInterface', ), 'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod', + 'PhabricatorEditEngineBulkJobType' => 'PhabricatorWorkerBulkJobType', 'PhabricatorEditEngineCheckboxesCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineColumnsCommentAction' => 'PhabricatorEditEngineCommentAction', 'PhabricatorEditEngineCommentAction' => 'Phobject', diff --git a/src/applications/transactions/bulk/PhabricatorEditEngineBulkJobType.php b/src/applications/transactions/bulk/PhabricatorEditEngineBulkJobType.php new file mode 100644 index 0000000000..1a529f753d --- /dev/null +++ b/src/applications/transactions/bulk/PhabricatorEditEngineBulkJobType.php @@ -0,0 +1,82 @@ +getSize())); + } + + public function getJobSize(PhabricatorWorkerBulkJob $job) { + return count($job->getParameter('objectPHIDs', array())); + } + + public function getDoneURI(PhabricatorWorkerBulkJob $job) { + return $job->getParameter('doneURI'); + } + + public function createTasks(PhabricatorWorkerBulkJob $job) { + $tasks = array(); + + foreach ($job->getParameter('objectPHIDs', array()) as $phid) { + $tasks[] = PhabricatorWorkerBulkTask::initializeNewTask($job, $phid); + } + + return $tasks; + } + + public function runTask( + PhabricatorUser $actor, + PhabricatorWorkerBulkJob $job, + PhabricatorWorkerBulkTask $task) { + + $object = id(new PhabricatorObjectQuery()) + ->setViewer($actor) + ->withPHIDs(array($task->getObjectPHID())) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$object) { + return; + } + + $raw_xactions = $job->getParameter('xactions'); + $xactions = $this->buildTransactions($object, $raw_xactions); + + $editor = $object->getApplicationTransactionEditor() + ->setActor($actor) + ->setContentSource($job->newContentSource()) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($object, $xactions); + } + + private function buildTransactions($object, array $raw_xactions) { + $xactions = array(); + + foreach ($raw_xactions as $raw_xaction) { + $xaction = $object->getApplicationTransactionTemplate() + ->setTransactionType($raw_xaction['type']) + ->setNewValue($raw_xaction['value']); + + $xactions[] = $xaction; + } + + return $xactions; + } + +} From 09e71a408235008e39cb5e82876511452a5a0d13 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 10 Jan 2018 11:23:55 -0800 Subject: [PATCH 353/865] Define bulk edits in terms of EditEngine, not hard-coded ad-hoc definitions Summary: Depends on D18862. See PHI173. Ref T13025. Fixes T10005. This redefines bulk edits in terms of EditEngine fields, rather than hard-coding the whole thing. Only text fields -- and, specifically, only the "Title" field -- are supported after this change. Followup changes will add more bulk edit parameter types and broader field support. However, the title field now works without any Maniphest-specific code, outside of the small amount of binding code in the `ManiphestBulkEditor` subclass. Test Plan: Used the bulk edit workflow to change the titles of tasks. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13025, T10005 Differential Revision: https://secure.phabricator.com/D18863 --- resources/celerity/map.php | 37 ++-- src/__phutil_library_map__.php | 4 + .../bulk/ManiphestTaskBulkEngine.php | 4 + .../maniphest/editor/ManiphestEditEngine.php | 1 + .../bulk/PhabricatorBulkEngine.php | 154 ++++++++--------- .../bulk/type/BulkParameterType.php | 24 +++ .../bulk/type/BulkStringParameterType.php | 10 ++ .../editengine/PhabricatorEditEngine.php | 65 +++++++ .../editfield/PhabricatorEditField.php | 61 ++++++- .../PhabricatorPHIDListEditField.php | 4 + .../editfield/PhabricatorTextEditField.php | 4 + .../edittype/PhabricatorEditType.php | 40 +++++ .../application/maniphest/batch-editor.css | 17 -- webroot/rsrc/css/phui/phui-bulk-editor.css | 22 +++ .../maniphest/behavior-batch-editor.js | 158 ------------------ webroot/rsrc/js/core/behavior-bulk-editor.js | 113 +++++++++++++ webroot/rsrc/js/phuix/PHUIXFormControl.js | 13 ++ 17 files changed, 449 insertions(+), 282 deletions(-) create mode 100644 src/applications/transactions/bulk/type/BulkParameterType.php create mode 100644 src/applications/transactions/bulk/type/BulkStringParameterType.php delete mode 100644 webroot/rsrc/css/application/maniphest/batch-editor.css create mode 100644 webroot/rsrc/css/phui/phui-bulk-editor.css delete mode 100644 webroot/rsrc/js/application/maniphest/behavior-batch-editor.js create mode 100644 webroot/rsrc/js/core/behavior-bulk-editor.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 650d3f7efb..a8eee7a3cb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -81,7 +81,6 @@ 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald.css' => 'cd8d0134', - 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', @@ -143,6 +142,7 @@ 'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3', 'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c', 'rsrc/css/phui/phui-box.css' => '4bd6cdb9', + 'rsrc/css/phui/phui-bulk-editor.css' => '1fe728a8', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', @@ -419,7 +419,6 @@ 'rsrc/js/application/herald/HeraldRuleEditor.js' => '2dff5579', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', - 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e', 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', @@ -477,6 +476,7 @@ 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', + 'rsrc/js/core/behavior-bulk-editor.js' => '5e178556', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', @@ -532,7 +532,7 @@ 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', - 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', + 'rsrc/js/phuix/PHUIXFormControl.js' => '68bb05aa', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), 'symbols' => array( @@ -595,6 +595,7 @@ 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-badge-view' => '8ff5e24c', + 'javelin-behavior-bulk-editor' => '5e178556', 'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-calendar-month-view' => 'fe33e256', 'javelin-behavior-choose-control' => '327a00d1', @@ -642,7 +643,6 @@ 'javelin-behavior-lightbox-attachments' => '560f41da', 'javelin-behavior-line-chart' => 'e4232876', 'javelin-behavior-load-blame' => '42126667', - 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', 'javelin-behavior-maniphest-batch-selector' => 'ad54037e', 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 'javelin-behavior-maniphest-subpriority-editor' => '71237763', @@ -756,7 +756,6 @@ 'javelin-workboard-column' => '758b4758', 'javelin-workboard-controller' => '26167537', 'javelin-workflow' => '1e911d0f', - 'maniphest-batch-editor' => 'b0f0b6d5', 'maniphest-report-css' => '9b9580b7', 'maniphest-task-edit-css' => 'fda62a9b', 'maniphest-task-summary-css' => '11cc5344', @@ -823,6 +822,7 @@ 'phui-basic-nav-view-css' => '98c11ab3', 'phui-big-info-view-css' => 'acc3492c', 'phui-box-css' => '4bd6cdb9', + 'phui-bulk-editor-css' => '1fe728a8', 'phui-button-bar-css' => 'f1ff5494', 'phui-button-css' => '1863cc6e', 'phui-button-simple-css' => '8e1baf68', @@ -884,7 +884,7 @@ 'phuix-autocomplete' => 'e0731603', 'phuix-button-view' => '8a91e1ac', 'phuix-dropdown-menu' => '04b2ae03', - 'phuix-form-control-view' => '83e03671', + 'phuix-form-control-view' => '68bb05aa', 'phuix-icon-view' => 'bff6884b', 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', @@ -1387,6 +1387,15 @@ 'javelin-stratcom', 'javelin-dom', ), + '5e178556' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'phabricator-prefab', + 'multirow-row-manager', + 'javelin-json', + 'phuix-form-control-view', + ), '5e2634b9' => array( 'javelin-behavior', 'javelin-aphlict', @@ -1436,6 +1445,10 @@ 'javelin-dom', 'phuix-button-view', ), + '68bb05aa' => array( + 'javelin-install', + 'javelin-dom', + ), '69adf288' => array( 'javelin-install', ), @@ -1524,14 +1537,6 @@ 'javelin-request', 'javelin-util', ), - '782ab6e7' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'phabricator-prefab', - 'multirow-row-manager', - 'javelin-json', - ), '7927a7d3' => array( 'javelin-behavior', 'javelin-quicksand', @@ -1570,10 +1575,6 @@ 'javelin-behavior', 'javelin-scrollbar', ), - '83e03671' => array( - 'javelin-install', - 'javelin-dom', - ), '8499b6ab' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b39d89a087..e9bb60fe22 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -222,6 +222,8 @@ 'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php', 'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php', 'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php', + 'BulkParameterType' => 'applications/transactions/bulk/type/BulkParameterType.php', + 'BulkStringParameterType' => 'applications/transactions/bulk/type/BulkStringParameterType.php', 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', @@ -5242,6 +5244,8 @@ 'AuditConduitAPIMethod' => 'ConduitAPIMethod', 'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod', 'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability', + 'BulkParameterType' => 'Phobject', + 'BulkStringParameterType' => 'BulkParameterType', 'CalendarTimeUtil' => 'Phobject', 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 'CelerityAPI' => 'Phobject', diff --git a/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php b/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php index 8b62f627d8..44f575eefe 100644 --- a/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php +++ b/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php @@ -18,6 +18,10 @@ public function newSearchEngine() { return new ManiphestTaskSearchEngine(); } + public function newEditEngine() { + return new ManiphestEditEngine(); + } + public function getDoneURI() { $board_uri = $this->getBoardURI(); if ($board_uri) { diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index b4ca86a442..f74fae076f 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -178,6 +178,7 @@ protected function buildCustomEditFields($object) { id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) + ->setBulkEditLabel(pht('Set title to')) ->setDescription(pht('Name of the task.')) ->setConduitDescription(pht('Rename the task.')) ->setConduitTypeDescription(pht('New task name.')) diff --git a/src/applications/transactions/bulk/PhabricatorBulkEngine.php b/src/applications/transactions/bulk/PhabricatorBulkEngine.php index 82dc7f2510..f3cbeac9e1 100644 --- a/src/applications/transactions/bulk/PhabricatorBulkEngine.php +++ b/src/applications/transactions/bulk/PhabricatorBulkEngine.php @@ -10,7 +10,10 @@ abstract class PhabricatorBulkEngine extends Phobject { private $editableList; private $targetList; + private $rootFormID; + abstract public function newSearchEngine(); + abstract public function newEditEngine(); public function getCancelURI() { $saved_query = $this->savedQuery; @@ -118,7 +121,7 @@ final public function buildResponse() { array( 'action' => $this->getBulkURI(), 'method' => 'POST', - 'id' => 'maniphest-batch-edit-form', + 'id' => $this->getRootFormID(), ), array( $this->newContextInputs(), @@ -290,95 +293,60 @@ private function newContextInputs() { private function newBulkActionForm() { $viewer = $this->getViewer(); + $input_id = celerity_generate_unique_node_id(); - $cancel_uri = $this->getCancelURI(); - - $template = new AphrontTokenizerTemplateView(); - $template = $template->render(); - - $projects_source = new PhabricatorProjectDatasource(); - $mailable_source = new PhabricatorMetaMTAMailableDatasource(); - $mailable_source->setViewer($viewer); - $owner_source = new ManiphestAssigneeDatasource(); - $owner_source->setViewer($viewer); - $spaces_source = id(new PhabricatorSpacesNamespaceDatasource()) + $edit_engine = id($this->newEditEngine()) ->setViewer($viewer); - require_celerity_resource('maniphest-batch-editor'); + $edit_map = $edit_engine->newBulkEditMap(); + + require_celerity_resource('phui-bulk-editor-css'); Javelin::initBehavior( - 'maniphest-batch-editor', + 'bulk-editor', array( - 'root' => 'maniphest-batch-edit-form', - 'tokenizerTemplate' => $template, - 'sources' => array( - 'project' => array( - 'src' => $projects_source->getDatasourceURI(), - 'placeholder' => $projects_source->getPlaceholderText(), - 'browseURI' => $projects_source->getBrowseURI(), - ), - 'owner' => array( - 'src' => $owner_source->getDatasourceURI(), - 'placeholder' => $owner_source->getPlaceholderText(), - 'browseURI' => $owner_source->getBrowseURI(), - 'limit' => 1, - ), - 'cc' => array( - 'src' => $mailable_source->getDatasourceURI(), - 'placeholder' => $mailable_source->getPlaceholderText(), - 'browseURI' => $mailable_source->getBrowseURI(), - ), - 'spaces' => array( - 'src' => $spaces_source->getDatasourceURI(), - 'placeholder' => $spaces_source->getPlaceholderText(), - 'browseURI' => $spaces_source->getBrowseURI(), - 'limit' => 1, - ), - ), - 'input' => 'batch-form-actions', - 'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(), - 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), + 'rootNodeID' => $this->getRootFormID(), + 'inputNodeID' => $input_id, + 'edits' => $edit_map, )); - $form = id(new PHUIFormLayoutView()) - ->setUser($viewer); + $cancel_uri = $this->getCancelURI(); - $form->appendChild( - phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'actions', - 'id' => 'batch-form-actions', - ))); - - $form->appendChild( - id(new PHUIFormInsetView()) - ->setTitle(pht('Bulk Edit Actions')) - ->setRightButton( - javelin_tag( - 'a', - array( - 'href' => '#', - 'class' => 'button button-green', - 'sigil' => 'add-action', - 'mustcapture' => true, - ), - pht('Add Another Action'))) - ->setContent( - javelin_tag( - 'table', - array( - 'sigil' => 'maniphest-batch-actions', - 'class' => 'maniphest-batch-actions-table', - ), - ''))) + return id(new PHUIFormLayoutView()) + ->setViewer($viewer) + ->appendChild( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'xactions', + 'id' => $input_id, + ))) + ->appendChild( + id(new PHUIFormInsetView()) + ->setTitle(pht('Bulk Edit Actions')) + ->setRightButton( + javelin_tag( + 'a', + array( + 'href' => '#', + 'class' => 'button button-green', + 'sigil' => 'add-action', + 'mustcapture' => true, + ), + pht('Add Another Action'))) + ->setContent( + javelin_tag( + 'table', + array( + 'sigil' => 'bulk-actions', + 'class' => 'bulk-edit-table', + ), + ''))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Apply Bulk Edit')) ->addCancelButton($cancel_uri)); - - return $form; } private function buildEditResponse() { @@ -405,31 +373,33 @@ private function buildEditResponse() { 'You have not selected any objects to edit.')); } - $raw_actions = $request->getStr('actions'); - if ($raw_actions) { - $actions = phutil_json_decode($raw_actions); + $raw_xactions = $request->getStr('xactions'); + if ($raw_xactions) { + $raw_xactions = phutil_json_decode($raw_xactions); } else { - $actions = array(); + $raw_xactions = array(); } - if (!$actions) { + if (!$raw_xactions) { throw new Exception( pht( 'You have not chosen any edits to apply.')); } + $edit_engine = id($this->newEditEngine()) + ->setViewer($viewer); + + $xactions = $edit_engine->newRawBulkTransactions($raw_xactions); + $cancel_uri = $this->getCancelURI(); $done_uri = $this->getDoneURI(); $job = PhabricatorWorkerBulkJob::initializeNewJob( $viewer, - // TODO: This is a Maniphest-specific job type for now, but will become - // a generic one so it gets to live here for now instead of in the task - // specific BulkEngine subclass. - new ManiphestTaskEditBulkJobType(), + new PhabricatorEditEngineBulkJobType(), array( - 'taskPHIDs' => mpull($objects, 'getPHID'), - 'actions' => $actions, + 'objectPHIDs' => mpull($objects, 'getPHID'), + 'xactions' => $xactions, 'cancelURI' => $cancel_uri, 'doneURI' => $done_uri, )); @@ -451,4 +421,12 @@ private function buildEditResponse() { ->setURI($job->getMonitorURI()); } + private function getRootFormID() { + if (!$this->rootFormID) { + $this->rootFormID = celerity_generate_unique_node_id(); + } + + return $this->rootFormID; + } + } diff --git a/src/applications/transactions/bulk/type/BulkParameterType.php b/src/applications/transactions/bulk/type/BulkParameterType.php new file mode 100644 index 0000000000..e5d1ad9312 --- /dev/null +++ b/src/applications/transactions/bulk/type/BulkParameterType.php @@ -0,0 +1,24 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + abstract public function getPHUIXControlType(); + + public function getPHUIXControlSpecification() { + return array( + 'value' => null, + ); + } + +} diff --git a/src/applications/transactions/bulk/type/BulkStringParameterType.php b/src/applications/transactions/bulk/type/BulkStringParameterType.php new file mode 100644 index 0000000000..906ff9e2de --- /dev/null +++ b/src/applications/transactions/bulk/type/BulkStringParameterType.php @@ -0,0 +1,10 @@ +loadDefaultConfiguration(); + if (!$config) { + throw new Exception( + pht('No default edit engine configuration for bulk edit.')); + } + + $object = $this->newEditableObject(); + $fields = $this->buildEditFields($object); + + $edit_types = $this->getBulkEditTypesFromFields($fields); + + $map = array(); + foreach ($edit_types as $key => $type) { + $bulk_type = $type->getBulkParameterType(); + if ($bulk_type === null) { + continue; + } + + $bulk_label = $type->getBulkEditLabel(); + if ($bulk_label === null) { + continue; + } + + $map[] = array( + 'label' => $bulk_label, + 'xaction' => $type->getTransactionType(), + 'control' => array( + 'type' => $bulk_type->getPHUIXControlType(), + 'spec' => (object)$bulk_type->getPHUIXControlSpecification(), + ), + ); + } + + return $map; + } + + + final public function newRawBulkTransactions(array $xactions) { + return $xactions; + } + + private function getBulkEditTypesFromFields(array $fields) { + $types = array(); + + foreach ($fields as $field) { + $field_types = $field->getBulkEditTypes(); + + if ($field_types === null) { + continue; + } + + foreach ($field_types as $field_type) { + $field_type->setField($field); + $types[$field_type->getEditType()] = $field_type; + } + } + + return $types; + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index 3cc6fe2fff..643bb2fca4 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -17,6 +17,7 @@ abstract class PhabricatorEditField extends Phobject { private $previewPanel; private $controlID; private $controlInstructions; + private $bulkEditLabel; private $description; private $conduitDescription; @@ -45,6 +46,7 @@ abstract class PhabricatorEditField extends Phobject { private $isConduitOnly = false; private $conduitEditTypes; + private $bulkEditTypes; public function setKey($key) { $this->key = $key; @@ -64,6 +66,15 @@ public function getLabel() { return $this->label; } + public function setBulkEditLabel($bulk_edit_label) { + $this->bulkEditLabel = $bulk_edit_label; + return $this; + } + + public function getBulkEditLabel() { + return $this->bulkEditLabel; + } + public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; @@ -625,6 +636,22 @@ protected function newHTTPParameterType() { return new AphrontStringHTTPParameterType(); } + protected function getBulkParameterType() { + $type = $this->newBulkParameterType(); + + if (!$type) { + return null; + } + + $type->setViewer($this->getViewer()); + + return $type; + } + + protected function newBulkParameterType() { + return null; + } + public function getConduitParameterType() { $type = $this->newConduitParameterType(); @@ -657,8 +684,15 @@ protected function newEditType() { return null; } - return id(new PhabricatorSimpleEditType()) + $edit_type = id(new PhabricatorSimpleEditType()) ->setConduitParameterType($parameter_type); + + $bulk_type = $this->getBulkParameterType(); + if ($bulk_type) { + $edit_type->setBulkParameterType($bulk_type); + } + + return $edit_type; } protected function getEditType() { @@ -718,6 +752,31 @@ protected function newConduitEditTypes() { return array($edit_type); } + final public function getBulkEditTypes() { + if ($this->bulkEditTypes === null) { + $edit_types = $this->newBulkEditTypes(); + $edit_types = mpull($edit_types, null, 'getEditType'); + + foreach ($edit_types as $edit_type) { + $edit_type->setEditField($this); + } + + $this->bulkEditTypes = $edit_types; + } + + return $this->bulkEditTypes; + } + + protected function newBulkEditTypes() { + $edit_type = $this->getEditType(); + + if (!$edit_type) { + return array(); + } + + return array($edit_type); + } + public function getCommentAction() { $label = $this->getCommentActionLabel(); if ($label === null) { diff --git a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php index 7195741c43..e29206c8ba 100644 --- a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php +++ b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php @@ -104,6 +104,10 @@ protected function newEditType() { return $type; } + protected function newBulkEditTypes() { + return $this->newConduitEditTypes(); + } + protected function newConduitEditTypes() { if (!$this->getUseEdgeTransactions()) { return parent::newConduitEditTypes(); diff --git a/src/applications/transactions/editfield/PhabricatorTextEditField.php b/src/applications/transactions/editfield/PhabricatorTextEditField.php index fa51ff6142..68854cf2e2 100644 --- a/src/applications/transactions/editfield/PhabricatorTextEditField.php +++ b/src/applications/transactions/editfield/PhabricatorTextEditField.php @@ -29,4 +29,8 @@ protected function newConduitParameterType() { return new ConduitStringParameterType(); } + protected function newBulkParameterType() { + return new BulkStringParameterType(); + } + } diff --git a/src/applications/transactions/edittype/PhabricatorEditType.php b/src/applications/transactions/edittype/PhabricatorEditType.php index 1451ec2c04..0cba9db867 100644 --- a/src/applications/transactions/edittype/PhabricatorEditType.php +++ b/src/applications/transactions/edittype/PhabricatorEditType.php @@ -14,6 +14,9 @@ abstract class PhabricatorEditType extends Phobject { private $conduitTypeDescription; private $conduitParameterType; + private $bulkParameterType; + private $bulkEditLabel; + public function setLabel($label) { $this->label = $label; return $this; @@ -23,6 +26,19 @@ public function getLabel() { return $this->label; } + public function setBulkEditLabel($bulk_edit_label) { + $this->bulkEditLabel = $bulk_edit_label; + return $this; + } + + public function getBulkEditLabel() { + if ($this->bulkEditLabel !== null) { + return $this->bulkEditLabel; + } + + return $this->getField()->getBulkEditLabel(); + } + public function setField(PhabricatorEditField $field) { $this->field = $field; return $this; @@ -85,6 +101,30 @@ public function getEditField() { return $this->editField; } + +/* -( Bulk )--------------------------------------------------------------- */ + + + protected function newBulkParameterType() { + if ($this->bulkParameterType) { + return clone $this->bulkParameterType; + } + + return null; + } + + + public function setBulkParameterType(BulkParameterType $type) { + $this->bulkParameterType = $type; + return $this; + } + + + public function getBulkParameterType() { + return $this->newBulkParameterType(); + } + + /* -( Conduit )------------------------------------------------------------ */ diff --git a/webroot/rsrc/css/application/maniphest/batch-editor.css b/webroot/rsrc/css/application/maniphest/batch-editor.css deleted file mode 100644 index ea98fd013c..0000000000 --- a/webroot/rsrc/css/application/maniphest/batch-editor.css +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @provides maniphest-batch-editor - */ -.maniphest-batch-actions-table { - width: 100%; - margin: 12px 0; -} - -.maniphest-batch-actions-table td { - padding: 4px 8px; - vertical-align: middle; -} - -.batch-editor-input { - width: 100%; - text-align: left; -} diff --git a/webroot/rsrc/css/phui/phui-bulk-editor.css b/webroot/rsrc/css/phui/phui-bulk-editor.css new file mode 100644 index 0000000000..d7364b1d75 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-bulk-editor.css @@ -0,0 +1,22 @@ +/** + * @provides phui-bulk-editor-css + */ + +.bulk-edit-table { + width: 100%; + margin: 12px 0; +} + +.bulk-edit-table td { + padding: 4px 8px; + vertical-align: middle; +} + +.bulk-edit-input { + width: 100%; + text-align: left; +} + +.bulk-edit-input input { + width: 100%; +} diff --git a/webroot/rsrc/js/application/maniphest/behavior-batch-editor.js b/webroot/rsrc/js/application/maniphest/behavior-batch-editor.js deleted file mode 100644 index ad2cf32d9e..0000000000 --- a/webroot/rsrc/js/application/maniphest/behavior-batch-editor.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @provides javelin-behavior-maniphest-batch-editor - * @requires javelin-behavior - * javelin-dom - * javelin-util - * phabricator-prefab - * multirow-row-manager - * javelin-json - */ - -JX.behavior('maniphest-batch-editor', function(config) { - var root = JX.$(config.root); - var editor_table = JX.DOM.find(root, 'table', 'maniphest-batch-actions'); - var manager = new JX.MultirowRowManager(editor_table); - var action_rows = []; - - function renderRow() { - var action_select = JX.Prefab.renderSelect( - { - 'add_project': 'Add Projects', - 'remove_project' : 'Remove Projects', - 'priority': 'Change Priority', - 'status': 'Change Status', - 'add_comment': 'Comment', - 'assign': 'Assign', - 'add_ccs' : 'Add CCs', - 'remove_ccs' : 'Remove CCs', - 'space': 'Shift to Space' - }); - - var proj_tokenizer = build_tokenizer(config.sources.project); - var owner_tokenizer = build_tokenizer(config.sources.owner); - var cc_tokenizer = build_tokenizer(config.sources.cc); - var space_tokenizer = build_tokenizer(config.sources.spaces); - - var priority_select = JX.Prefab.renderSelect(config.priorityMap); - var status_select = JX.Prefab.renderSelect(config.statusMap); - var comment_input = JX.$N('input', {style: {width: '100%'}}); - - var cell = JX.$N('td', {className: 'batch-editor-input'}); - var vfunc = null; - - function update() { - switch (action_select.value) { - case 'add_project': - case 'remove_project': - JX.DOM.setContent(cell, proj_tokenizer.template); - vfunc = function() { - return JX.keys(proj_tokenizer.object.getTokens()); - }; - break; - case 'add_ccs': - case 'remove_ccs': - JX.DOM.setContent(cell, cc_tokenizer.template); - vfunc = function() { - return JX.keys(cc_tokenizer.object.getTokens()); - }; - break; - case 'assign': - JX.DOM.setContent(cell, owner_tokenizer.template); - vfunc = function() { - return JX.keys(owner_tokenizer.object.getTokens()); - }; - break; - case 'space': - JX.DOM.setContent(cell, space_tokenizer.template); - vfunc = function() { - return JX.keys(space_tokenizer.object.getTokens()); - }; - break; - case 'add_comment': - JX.DOM.setContent(cell, comment_input); - vfunc = function() { - return comment_input.value; - }; - break; - case 'priority': - JX.DOM.setContent(cell, priority_select); - vfunc = function() { return priority_select.value; }; - break; - case 'status': - JX.DOM.setContent(cell, status_select); - vfunc = function() { return status_select.value; }; - break; - } - } - - JX.DOM.listen(action_select, 'change', null, update); - update(); - - return { - nodes : [JX.$N('td', {}, action_select), cell], - dataCallback : function() { - return { - action: action_select.value, - value: vfunc() - }; - } - }; - } - - function onaddaction(e) { - e.kill(); - addRow({}); - } - - function addRow(info) { - var data = renderRow(info); - var row = manager.addRow(data.nodes); - var id = manager.getRowID(row); - - action_rows[id] = data.dataCallback; - } - - function onsubmit() { - var input = JX.$(config.input); - - var actions = []; - for (var k in action_rows) { - actions.push(action_rows[k]()); - } - - input.value = JX.JSON.stringify(actions); - } - - addRow({}); - - JX.DOM.listen( - root, - 'click', - 'add-action', - onaddaction); - - JX.DOM.listen( - root, - 'submit', - null, - onsubmit); - - manager.listen( - 'row-removed', - function(row_id) { - delete action_rows[row_id]; - }); - - function build_tokenizer(tconfig) { - var built = JX.Prefab.newTokenizerFromTemplate( - config.tokenizerTemplate, - JX.copy({}, tconfig)); - built.tokenizer.start(); - - return { - object: built.tokenizer, - template: built.node - }; - } - -}); diff --git a/webroot/rsrc/js/core/behavior-bulk-editor.js b/webroot/rsrc/js/core/behavior-bulk-editor.js new file mode 100644 index 0000000000..1e6e60e94e --- /dev/null +++ b/webroot/rsrc/js/core/behavior-bulk-editor.js @@ -0,0 +1,113 @@ +/** + * @provides javelin-behavior-bulk-editor + * @requires javelin-behavior + * javelin-dom + * javelin-util + * phabricator-prefab + * multirow-row-manager + * javelin-json + * phuix-form-control-view + */ + +JX.behavior('bulk-editor', function(config) { + + var root = JX.$(config.rootNodeID); + var editor_table = JX.DOM.find(root, 'table', 'bulk-actions'); + + var manager = new JX.MultirowRowManager(editor_table); + var action_rows = []; + + var option_map = {}; + var option_order = []; + var spec_map = {}; + + for (var ii = 0; ii < config.edits.length; ii++) { + var edit = config.edits[ii]; + + option_map[edit.xaction] = edit.label; + option_order.push(edit.xaction); + + spec_map[edit.xaction] = edit; + } + + function renderRow() { + var action_select = JX.Prefab.renderSelect( + option_map, + null, + null, + option_order); + + var cell = JX.$N('td', {className: 'bulk-edit-input'}); + var vfunc = null; + + function update() { + var spec = spec_map[action_select.value]; + var control = spec.control; + + var phuix = new JX.PHUIXFormControl() + .setControl(control.type, control.spec); + + JX.DOM.setContent(cell, phuix.getRawInputNode()); + + vfunc = JX.bind(phuix, phuix.getValue); + } + + JX.DOM.listen(action_select, 'change', null, update); + update(); + + return { + nodes : [JX.$N('td', {}, action_select), cell], + dataCallback : function() { + return { + type: action_select.value, + value: vfunc() + }; + } + }; + } + + function onaddaction(e) { + e.kill(); + addRow({}); + } + + function addRow(info) { + var data = renderRow(info); + var row = manager.addRow(data.nodes); + var id = manager.getRowID(row); + + action_rows[id] = data.dataCallback; + } + + function onsubmit() { + var input = JX.$(config.inputNodeID); + + var actions = []; + for (var k in action_rows) { + actions.push(action_rows[k]()); + } + + input.value = JX.JSON.stringify(actions); + } + + addRow({}); + + JX.DOM.listen( + root, + 'click', + 'add-action', + onaddaction); + + JX.DOM.listen( + root, + 'submit', + null, + onsubmit); + + manager.listen( + 'row-removed', + function(row_id) { + delete action_rows[row_id]; + }); + +}); diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js index 5640b95ae8..0a9b8d45d7 100644 --- a/webroot/rsrc/js/phuix/PHUIXFormControl.js +++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js @@ -14,6 +14,7 @@ JX.install('PHUIXFormControl', { _className: null, _valueSetCallback: null, _valueGetCallback: null, + _rawInputNode: null, setLabel: function(label) { JX.DOM.setContent(this._getLabelNode(), label); @@ -53,6 +54,9 @@ JX.install('PHUIXFormControl', { case 'checkboxes': input = this._newCheckboxes(spec); break; + case 'text': + input = this._newText(spec); + break; default: // TODO: Default or better error? JX.$E('Bad Input Type'); @@ -62,6 +66,7 @@ JX.install('PHUIXFormControl', { JX.DOM.setContent(node, input.node); this._valueGetCallback = input.get; this._valueSetCallback = input.set; + this._rawInputNode = input.node; return this; }, @@ -75,6 +80,10 @@ JX.install('PHUIXFormControl', { return this._valueGetCallback(); }, + getRawInputNode: function() { + return this._rawInputNode; + }, + getNode: function() { if (!this._node) { @@ -281,6 +290,10 @@ JX.install('PHUIXFormControl', { }, _newPoints: function(spec) { + return this._newText(); + }, + + _newText: function(spec) { var attrs = { type: 'text', value: spec.value From a251db461826470bdb694271db8bd7508fcec5d8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 10 Jan 2018 13:04:25 -0800 Subject: [PATCH 354/865] Remove the Maniphest-specific bulk job type Summary: Depends on D18863. Ref PHI173. Ref T13025. After D18863, this job type is no longer used: the workflow uses a genric worker instead which can apply transactions to any object. Test Plan: Grepped for callsites, found none. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13025 Differential Revision: https://secure.phabricator.com/D18864 --- src/__phutil_library_map__.php | 2 - .../bulk/ManiphestTaskEditBulkJobType.php | 303 ------------------ 2 files changed, 305 deletions(-) delete mode 100644 src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e9bb60fe22..d8cf93c9ab 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1559,7 +1559,6 @@ 'ManiphestTaskDescriptionTransaction' => 'applications/maniphest/xaction/ManiphestTaskDescriptionTransaction.php', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', 'ManiphestTaskEdgeTransaction' => 'applications/maniphest/xaction/ManiphestTaskEdgeTransaction.php', - 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php', 'ManiphestTaskFerretEngine' => 'applications/maniphest/search/ManiphestTaskFerretEngine.php', @@ -6778,7 +6777,6 @@ 'ManiphestTaskDescriptionTransaction' => 'ManiphestTaskTransactionType', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEdgeTransaction' => 'ManiphestTaskTransactionType', - 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock', 'ManiphestTaskFerretEngine' => 'PhabricatorFerretEngine', diff --git a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php b/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php deleted file mode 100644 index 1b083d88f9..0000000000 --- a/src/applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php +++ /dev/null @@ -1,303 +0,0 @@ -getSize())); - } - - public function getJobSize(PhabricatorWorkerBulkJob $job) { - return count($job->getParameter('taskPHIDs', array())); - } - - public function getDoneURI(PhabricatorWorkerBulkJob $job) { - return $job->getParameter('doneURI'); - } - - public function createTasks(PhabricatorWorkerBulkJob $job) { - $tasks = array(); - - foreach ($job->getParameter('taskPHIDs', array()) as $phid) { - $tasks[] = PhabricatorWorkerBulkTask::initializeNewTask($job, $phid); - } - - return $tasks; - } - - public function runTask( - PhabricatorUser $actor, - PhabricatorWorkerBulkJob $job, - PhabricatorWorkerBulkTask $task) { - - $object = id(new ManiphestTaskQuery()) - ->setViewer($actor) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withPHIDs(array($task->getObjectPHID())) - ->needProjectPHIDs(true) - ->needSubscriberPHIDs(true) - ->executeOne(); - if (!$object) { - return; - } - - $field_list = PhabricatorCustomField::getObjectFields( - $object, - PhabricatorCustomField::ROLE_EDIT); - $field_list->readFieldsFromStorage($object); - - $actions = $job->getParameter('actions'); - $xactions = $this->buildTransactions($actions, $object); - - $editor = id(new ManiphestTransactionEditor()) - ->setActor($actor) - ->setContentSource($job->newContentSource()) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true) - ->applyTransactions($object, $xactions); - } - - private function buildTransactions($actions, ManiphestTask $task) { - $value_map = array(); - $type_map = array( - 'add_comment' => PhabricatorTransactions::TYPE_COMMENT, - 'assign' => ManiphestTaskOwnerTransaction::TRANSACTIONTYPE, - 'status' => ManiphestTaskStatusTransaction::TRANSACTIONTYPE, - 'priority' => ManiphestTaskPriorityTransaction::TRANSACTIONTYPE, - 'add_project' => PhabricatorTransactions::TYPE_EDGE, - 'remove_project' => PhabricatorTransactions::TYPE_EDGE, - 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, - 'remove_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, - 'space' => PhabricatorTransactions::TYPE_SPACE, - ); - - $edge_edit_types = array( - 'add_project' => true, - 'remove_project' => true, - 'add_ccs' => true, - 'remove_ccs' => true, - ); - - $xactions = array(); - foreach ($actions as $action) { - if (empty($type_map[$action['action']])) { - throw new Exception(pht("Unknown batch edit action '%s'!", $action)); - } - - $type = $type_map[$action['action']]; - - // Figure out the current value, possibly after modifications by other - // batch actions of the same type. For example, if the user chooses to - // "Add Comment" twice, we should add both comments. More notably, if the - // user chooses "Remove Project..." and also "Add Project...", we should - // avoid restoring the removed project in the second transaction. - - if (array_key_exists($type, $value_map)) { - $current = $value_map[$type]; - } else { - switch ($type) { - case PhabricatorTransactions::TYPE_COMMENT: - $current = null; - break; - case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: - $current = $task->getOwnerPHID(); - break; - case ManiphestTaskStatusTransaction::TRANSACTIONTYPE: - $current = $task->getStatus(); - break; - case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: - $current = $task->getPriority(); - break; - case PhabricatorTransactions::TYPE_EDGE: - $current = $task->getProjectPHIDs(); - break; - case PhabricatorTransactions::TYPE_SUBSCRIBERS: - $current = $task->getSubscriberPHIDs(); - break; - case PhabricatorTransactions::TYPE_SPACE: - $current = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( - $task); - break; - } - } - - // Check if the value is meaningful / provided, and normalize it if - // necessary. This discards, e.g., empty comments and empty owner - // changes. - - $value = $action['value']; - switch ($type) { - case PhabricatorTransactions::TYPE_COMMENT: - if (!strlen($value)) { - continue 2; - } - break; - case PhabricatorTransactions::TYPE_SPACE: - if (empty($value)) { - continue 2; - } - $value = head($value); - break; - case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE: - if (empty($value)) { - continue 2; - } - $value = head($value); - $no_owner = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN; - if ($value === $no_owner) { - $value = null; - } - break; - case PhabricatorTransactions::TYPE_EDGE: - if (empty($value)) { - continue 2; - } - break; - case PhabricatorTransactions::TYPE_SUBSCRIBERS: - if (empty($value)) { - continue 2; - } - break; - } - - // If the edit doesn't change anything, go to the next action. This - // check is only valid for changes like "owner", "status", etc, not - // for edge edits, because we should still apply an edit like - // "Remove Projects: A, B" to a task with projects "A, B". - - if (empty($edge_edit_types[$action['action']])) { - if ($value == $current) { - continue; - } - } - - // Apply the value change; for most edits this is just replacement, but - // some need to merge the current and edited values (add/remove project). - - switch ($type) { - case PhabricatorTransactions::TYPE_COMMENT: - if (strlen($current)) { - $value = $current."\n\n".$value; - } - break; - case PhabricatorTransactions::TYPE_EDGE: - $is_remove = $action['action'] == 'remove_project'; - - $current = array_fill_keys($current, true); - $value = array_fill_keys($value, true); - - $new = $current; - $did_something = false; - - if ($is_remove) { - foreach ($value as $phid => $ignored) { - if (isset($new[$phid])) { - unset($new[$phid]); - $did_something = true; - } - } - } else { - foreach ($value as $phid => $ignored) { - if (empty($new[$phid])) { - $new[$phid] = true; - $did_something = true; - } - } - } - - if (!$did_something) { - continue 2; - } - - $value = array_keys($new); - break; - case PhabricatorTransactions::TYPE_SUBSCRIBERS: - $is_remove = $action['action'] == 'remove_ccs'; - - $current = array_fill_keys($current, true); - - $new = array(); - $did_something = false; - - if ($is_remove) { - foreach ($value as $phid) { - if (isset($current[$phid])) { - $new[$phid] = true; - $did_something = true; - } - } - if ($new) { - $value = array('-' => array_keys($new)); - } - } else { - $new = array(); - foreach ($value as $phid) { - $new[$phid] = true; - $did_something = true; - } - if ($new) { - $value = array('+' => array_keys($new)); - } - } - if (!$did_something) { - continue 2; - } - - break; - } - - $value_map[$type] = $value; - } - - $template = new ManiphestTransaction(); - - foreach ($value_map as $type => $value) { - $xaction = clone $template; - $xaction->setTransactionType($type); - - switch ($type) { - case PhabricatorTransactions::TYPE_COMMENT: - $xaction->attachComment( - id(new ManiphestTransactionComment()) - ->setContent($value)); - break; - case PhabricatorTransactions::TYPE_EDGE: - $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xaction - ->setMetadataValue('edge:type', $project_type) - ->setNewValue( - array( - '=' => array_fuse($value), - )); - break; - case ManiphestTaskPriorityTransaction::TRANSACTIONTYPE: - $keyword_map = ManiphestTaskPriority::getTaskPriorityKeywordsMap(); - $keyword = head(idx($keyword_map, $value)); - $xaction->setNewValue($keyword); - break; - default: - $xaction->setNewValue($value); - break; - } - - $xactions[] = $xaction; - } - - return $xactions; - } -} From bf1ac701c37c93791143ef31bacb1196f05fca44 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 11 Jan 2018 09:27:58 -0800 Subject: [PATCH 355/865] Support "select" types in bulk editor (status, priority) Summary: Depends on D18864. Ref T13025. Adds bulk edit support back for "status" and "priority" using `