diff --git a/.arcconfig b/.arcconfig index 88f04f72c5..3c1bbad847 100644 --- a/.arcconfig +++ b/.arcconfig @@ -1,5 +1,5 @@ { - "phabricator.uri": "https://secure.phabricator.com/", + "phabricator.uri": "https://phabricator.robinhood.com/", "load": ["src/"], "history.immutable": false } diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 421ee76400..0d0fda0820 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -542,6 +542,7 @@ 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', 'DifferentialDraftField' => 'applications/differential/customfield/DifferentialDraftField.php', + 'DifferentialExactResponsibleViewerFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactResponsibleViewerFunctionDatasource.php', 'DifferentialExactUserFunctionDatasource' => 'applications/differential/typeahead/DifferentialExactUserFunctionDatasource.php', 'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php', 'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php', @@ -5766,6 +5767,7 @@ 'PhutilRemarkupCodeBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php', 'PhutilRemarkupDefaultBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupDefaultBlockRule.php', 'PhutilRemarkupDelRule' => 'infrastructure/markup/markuprule/PhutilRemarkupDelRule.php', + 'PhutilRemarkupDisclosureRule' => 'infrastructure/markup/markuprule/PhutilRemarkupDisclosureRule.php', 'PhutilRemarkupDocumentLinkRule' => 'infrastructure/markup/markuprule/PhutilRemarkupDocumentLinkRule.php', 'PhutilRemarkupEngine' => 'infrastructure/markup/remarkup/PhutilRemarkupEngine.php', 'PhutilRemarkupEngineTestCase' => 'infrastructure/markup/remarkup/__tests__/PhutilRemarkupEngineTestCase.php', @@ -6661,6 +6663,7 @@ 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 'DifferentialDraftField' => 'DifferentialCoreCustomField', + 'DifferentialExactResponsibleViewerFunctionDatasource' => 'PhabricatorTypeaheadDatasource', 'DifferentialExactUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DifferentialFieldParseException' => 'Exception', 'DifferentialFieldValidationException' => 'Exception', @@ -12767,6 +12770,7 @@ 'PhutilRemarkupCodeBlockRule' => 'PhutilRemarkupBlockRule', 'PhutilRemarkupDefaultBlockRule' => 'PhutilRemarkupBlockRule', 'PhutilRemarkupDelRule' => 'PhutilRemarkupRule', + 'PhutilRemarkupDisclosureRule' => 'PhutilRemarkupRule', 'PhutilRemarkupDocumentLinkRule' => 'PhutilRemarkupRule', 'PhutilRemarkupEngine' => 'PhutilMarkupEngine', 'PhutilRemarkupEngineTestCase' => 'PhutilTestCase', diff --git a/src/applications/auth/controller/PhabricatorAuthRegisterController.php b/src/applications/auth/controller/PhabricatorAuthRegisterController.php index 30aa770f30..5cdb6871ed 100644 --- a/src/applications/auth/controller/PhabricatorAuthRegisterController.php +++ b/src/applications/auth/controller/PhabricatorAuthRegisterController.php @@ -198,7 +198,7 @@ public function handleRequest(AphrontRequest $request) { ->setDefaultUsername($default_username) ->setDefaultEmail($default_email) ->setDefaultRealName($default_realname) - ->setCanEditUsername(true) + ->setCanEditUsername(($default_username === null)) ->setCanEditEmail(($default_email === null)) ->setCanEditRealName(true) ->setShouldVerifyEmail(false); diff --git a/src/applications/differential/editor/DifferentialRevisionEditEngine.php b/src/applications/differential/editor/DifferentialRevisionEditEngine.php index 74fee82219..690fb0c5c4 100644 --- a/src/applications/differential/editor/DifferentialRevisionEditEngine.php +++ b/src/applications/differential/editor/DifferentialRevisionEditEngine.php @@ -200,7 +200,8 @@ protected function buildCustomEditFields($object) { $author_field->setCommentActionLabel(pht('Foist Upon')); } - $fields[] = $author_field; + // DEVX-2064 + // $fields[] = $author_field; $fields[] = id(new PhabricatorRemarkupEditField()) ->setKey(DifferentialRevisionSummaryTransaction::EDITKEY) diff --git a/src/applications/differential/engineextension/DifferentialReviewersSearchEngineAttachment.php b/src/applications/differential/engineextension/DifferentialReviewersSearchEngineAttachment.php index 33fb606a60..c74983b459 100644 --- a/src/applications/differential/engineextension/DifferentialReviewersSearchEngineAttachment.php +++ b/src/applications/differential/engineextension/DifferentialReviewersSearchEngineAttachment.php @@ -13,10 +13,12 @@ public function getAttachmentDescription() { public function willLoadAttachmentData($query, $spec) { $query->needReviewers(true); + $query->needActiveDiffs(true); } public function getAttachmentForObject($object, $data, $spec) { $reviewers = $object->getReviewers(); + $diff_phid = $object->getActiveDiff()->getPHID(); $status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING; @@ -30,6 +32,7 @@ public function getAttachmentForObject($object, $data, $spec) { 'status' => $status, 'isBlocking' => $is_blocking, 'actorPHID' => $reviewer->getLastActorPHID(), + 'isCurrentAction' => $reviewer->isCurrentAction($diff_phid), ); } diff --git a/src/applications/differential/field/DifferentialTagsCommitMessageField.php b/src/applications/differential/field/DifferentialTagsCommitMessageField.php index fc6267f1ce..704d32a5c8 100644 --- a/src/applications/differential/field/DifferentialTagsCommitMessageField.php +++ b/src/applications/differential/field/DifferentialTagsCommitMessageField.php @@ -22,7 +22,7 @@ public function getFieldAliases() { } public function isTemplateField() { - return false; + return true; } public function parseFieldValue($value) { diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php index 1ec5265630..5aef5a1800 100644 --- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php +++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php @@ -113,13 +113,13 @@ protected function buildCustomSearchFields() { id(new PhabricatorSearchDateField()) ->setLabel(pht('Modified After')) ->setKey('modifiedStart') - ->setIsHidden(true) + ->setIsHidden(false) ->setDescription( pht('Find revisions modified at or after a particular time.')), id(new PhabricatorSearchDateField()) ->setLabel(pht('Modified Before')) ->setKey('modifiedEnd') - ->setIsHidden(true) + ->setIsHidden(false) ->setDescription( pht('Find revisions modified at or before a particular time.')), id(new PhabricatorSearchStringListField()) diff --git a/src/applications/differential/storage/DifferentialReviewer.php b/src/applications/differential/storage/DifferentialReviewer.php index 823047f218..4870e73b84 100644 --- a/src/applications/differential/storage/DifferentialReviewer.php +++ b/src/applications/differential/storage/DifferentialReviewer.php @@ -134,7 +134,7 @@ public function isAccepted($diff_phid) { return false; } - private function isCurrentAction($diff_phid) { + public function isCurrentAction($diff_phid) { if (!$diff_phid) { return true; } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 92b303d0ba..cc825fcf98 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -223,6 +223,11 @@ public function canReviewerForceAccept( PhabricatorUser $viewer, DifferentialReviewer $reviewer) { + // NOTE(joel.jeske) + // We do not want to support force-acceptance if owning a parent package + // All ownership rules should be declared in OWNERS.toml files + return false; + if (!$reviewer->isPackage()) { return false; } diff --git a/src/applications/differential/typeahead/DifferentialExactResponsibleViewerFunctionDatasource.php b/src/applications/differential/typeahead/DifferentialExactResponsibleViewerFunctionDatasource.php new file mode 100644 index 0000000000..c0f29669d5 --- /dev/null +++ b/src/applications/differential/typeahead/DifferentialExactResponsibleViewerFunctionDatasource.php @@ -0,0 +1,73 @@ + array( + 'name' => pht('Exact Current Viewer'), + 'summary' => pht('Results matching the current viewing user exactly.'), + 'description' => pht( + 'Find revisions the current viewer is responsible for, exactly,'. + 'and not include those through their projects or packages. '), + ), + ); + } + + public function loadResults() { + if ($this->getViewer()->getPHID()) { + $results = array($this->renderViewerFunctionToken()); + } else { + $results = array(); + } + + return $this->filterResultsAgainstTokens($results); + } + + protected function canEvaluateFunction($function) { + if (!$this->getViewer()->getPHID()) { + return false; + } + + return parent::canEvaluateFunction($function); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + foreach ($argv_list as $argv) { + $results[] = $this->getViewer()->getPHID(); + } + return $results; + } + + public function renderFunctionTokens($function, array $argv_list) { + $tokens = array(); + foreach ($argv_list as $argv) { + $tokens[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( + $this->renderViewerFunctionToken()); + } + return $tokens; + } + + private function renderViewerFunctionToken() { + return $this->newFunctionResult() + ->setName(pht('Exact: Current Viewer')) + ->setPHID('exact-viewer()') + ->setIcon('fa-user') + ->setUnique(true); + } + +} diff --git a/src/applications/differential/typeahead/DifferentialResponsibleDatasource.php b/src/applications/differential/typeahead/DifferentialResponsibleDatasource.php index 5307ab1866..4e109ac183 100644 --- a/src/applications/differential/typeahead/DifferentialResponsibleDatasource.php +++ b/src/applications/differential/typeahead/DifferentialResponsibleDatasource.php @@ -18,6 +18,7 @@ public function getDatasourceApplicationClass() { public function getComponentDatasources() { return array( new DifferentialResponsibleUserDatasource(), + new DifferentialExactResponsibleViewerFunctionDatasource(), new DifferentialResponsibleViewerFunctionDatasource(), new DifferentialExactUserFunctionDatasource(), new PhabricatorProjectDatasource(), diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php index ad6bf1462b..1245530ffe 100644 --- a/src/applications/differential/view/DifferentialReviewersView.php +++ b/src/applications/differential/view/DifferentialReviewersView.php @@ -166,11 +166,14 @@ public function render() { null, $diff->getPHID())); - if ($reviewer->isPackage()) { - if (!$reviewer->getChangesets()) { - $item->setNote(pht('(Owns No Changed Paths)')); - } - } + // With advanced Ownership in OWNERS.toml, this is message is + // no longer accurate. + // + // if ($reviewer->isPackage()) { + // if (!$reviewer->getChangesets()) { + // $item->setNote(pht('(Owns No Changed Paths)')); + // } + // } if ($handle->hasCapabilities()) { if (!$handle->hasViewCapability($diff)) { diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index c19574acaa..7a648073f2 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -183,6 +183,13 @@ protected function loadPage() { $always_visible = true; } + // DEVX-2087: skip file policy check if it is PUBLIC or ALL_USERS + $file_view_policy = $file->getViewPolicy(); + if ($file_view_policy == PhabricatorPolicies::POLICY_PUBLIC || + $file_view_policy == PhabricatorPolicies::POLICY_USER) { + $always_visible = true; + } + if ($always_visible) { // We just treat these files as though they aren't attached to // anything. This saves a query in common cases when we're loading diff --git a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php index a7ee75b2a1..e9c07a5de8 100644 --- a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php @@ -593,9 +593,14 @@ private function sendToTarget( $unit_messages = $request->getValue('unit', array()); foreach ($unit_messages as $unit) { - $save[] = HarbormasterBuildUnitMessage::newFromDictionary( + $bu_message = HarbormasterBuildUnitMessage::newFromDictionary( $build_target, $unit); + // DEVX-2087:only save to db for failed test case and coverage information + if (($bu_message->getResult() == ArcanistUnitTestResult::RESULT_FAIL) || + ($bu_message->getProperty('coverage') != null)) { + $save[] = $bu_message; + } } $save[] = HarbormasterBuildMessage::initializeNewMessage($viewer) diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php index 59866d3b73..75b3362733 100644 --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -56,34 +56,73 @@ public function execute( 'vurisprintf', $settings['uri'], $variables); - $method = nonempty(idx($settings, 'method'), 'POST'); - $future = id(new HTTPSFuture($uri)) - ->setMethod($method) - ->setTimeout(60); - - $credential_phid = $this->getSetting('credential'); - if ($credential_phid) { - $key = PassphrasePasswordKey::loadFromPHID( - $credential_phid, - $viewer); - $future->setHTTPBasicAuthCredentials( - $key->getUsernameEnvelope()->openEnvelope(), - $key->getPasswordEnvelope()); + $sleepDuration = 30; + $retry = 0; + while (true) { + $future = id(new HTTPSFuture($uri)) + ->setMethod($method) + ->setTimeout(60); + + $credential_phid = $this->getSetting('credential'); + if ($credential_phid) { + $key = PassphrasePasswordKey::loadFromPHID( + $credential_phid, + $viewer); + $future->setHTTPBasicAuthCredentials( + $key->getUsernameEnvelope()->openEnvelope(), + $key->getPasswordEnvelope()); + } + + $this->resolveFutures( + $build, + $build_target, + array($future)); + + $this->logHTTPResponse($build, $build_target, $future, $uri); + + list($status) = $future->resolve(); + try { + $this->emitBuildResultMetric($build->getBuildPlan()->getID(), $status->getStatusCode(), $retry); + } catch (Exception $e) { + echo 'Caught exception while sending Jenkins metrics to statsd: ', $e->getMessage(), "\n"; + } + if (!$status->isError()) { + return; + } + if ($retry == 2) { + throw new HarbormasterBuildFailureException(); + } + sleep($sleepDuration); + $sleepDuration *= 2; + $retry++; } + } - $this->resolveFutures( - $build, - $build_target, - array($future)); - - $this->logHTTPResponse($build, $build_target, $future, $uri); - - list($status) = $future->resolve(); - if ($status->isError()) { - throw new HarbormasterBuildFailureException(); + public function emitBuildResultMetric($plan, $status, $retry) { + $template = 'echo "phabricator_build_result_total:1|c|#plan:$plan,status:$status,retry:$retry" | nc -w 3 -vu apollo-statsd.integration-tests.svc.cluster.local 8125'; + $vars = array( + '$plan' => $plan, + '$status' => $status, + '$retry' => $retry, + ); + $command = strtr($template, $vars); + echo $command; + + $metrics_retry = 0; + $retval = 0; + $output = null; + while ($metrics_retry < 3) { + exec($command, $output, $retval); + if ($retval == 0) { + return; + } + $metrics_retry++; + sleep(1); } + echo "Got error code $retval when sending metrics to statsd with output:\n"; + print_r($output); } public function getFieldSpecifications() { @@ -112,5 +151,4 @@ public function getFieldSpecifications() { public function supportsWaitForMessage() { return true; } - } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index 8b4972c154..b03e432ccc 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -196,6 +196,7 @@ public function getFieldValuesForConduit() { 'buildTargetPHID' => $this->getBuildTargetPHID(), 'artifactType' => $this->getArtifactType(), 'artifactKey' => $this->getArtifactKey(), + 'artifactData' => $this->artifactData, 'isReleased' => (bool)$this->getIsReleased(), ); } diff --git a/src/applications/home/view/PHUIHomeView.php b/src/applications/home/view/PHUIHomeView.php index 45750f5a93..00da3b88f3 100644 --- a/src/applications/home/view/PHUIHomeView.php +++ b/src/applications/home/view/PHUIHomeView.php @@ -86,7 +86,8 @@ private function buildRevisionPanel() { $panel = $this->newQueryPanel() ->setName(pht('Active Revisions')) ->setProperty('class', 'DifferentialRevisionSearchEngine') - ->setProperty('key', 'active'); + ->setProperty('key', 'active') + ->setProperty('modifiedStart', '30 days ago'); return $this->renderPanel($panel); } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 8512107e75..1ad380ef4d 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -220,8 +220,18 @@ private function buildPackageDetailView( $ignored = phutil_tag('em', array(), pht('None')); } + + $view->addProperty(pht('Ignored Attributes'), $ignored); + // Add Slack Channel Field + $slack_channel = $package->getSlackChannel(); + if ($slack_channel) { + $view->addProperty(pht('Slack Channel'), $slack_channel); + } else { + $view->addProperty(pht('Slack Channel'), phutil_tag('em', array(), pht('None'))); + } + $description = $package->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php index 416f2e38f2..ab4ff97e39 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php @@ -182,6 +182,14 @@ protected function buildCustomEditFields($object) { array( 'generated' => pht('Ignore generated files (review only).'), )), + id(new PhabricatorTextEditField()) + ->setKey('slackChannel') + ->setLabel(pht('Slack Channel')) + ->setDescription(pht('Slack channel associated with this package.')) + ->setTransactionType( + PhabricatorOwnersPackageSlackChannelTransaction::TRANSACTIONTYPE) + ->setIsCopyable(true) + ->setValue($object->getSlackChannel()), id(new PhabricatorConduitEditField()) ->setKey('paths.set') ->setLabel(pht('Paths')) diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 8885872706..5de5e41a56 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -23,7 +23,10 @@ public function getTransactionTypes() { protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - return true; + // Don't ever send email for changes to a Phab Owner Package. + // We perform bulk updates in response to OWNER file changes + // and this can cause queuing and a lot of email spam. + return false; } protected function getMailSubjectPrefix() { diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 7e58d586b2..77e1d5798b 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -727,6 +727,10 @@ public function getFieldSpecificationsForConduit() { ->setKey('ignored') ->setType('map') ->setDescription(pht('Ignored attribute information.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('slack') + ->setType('map') + ->setDescription(pht('Slack Channel to Ping for Review.')), ); } diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageSlackTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageSlackTransaction.php new file mode 100644 index 0000000000..ff186d1f36 --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageSlackTransaction.php @@ -0,0 +1,85 @@ +getSlackPathAttributes(); + } + + public function generateNewValue($object, $value) { + return array_fill_keys($value, true); + } + + public function applyInternalEffects($object, $value) { + $object->setSlackPathAttributes($value); + } + + public function getTitle() { + $old = array_keys($this->getOldValue()); + $new = array_keys($this->getNewValue()); + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $all_n = new PhutilNumber(count($add) + count($rem)); + $add_n = phutil_count($add); + $rem_n = phutil_count($rem); + + if ($new && $old) { + return pht( + '%s changed %s slack attribute(s), added %s: %s; removed %s: %s.', + $this->renderAuthor(), + $all_n, + $add_n, + $this->renderValueList($add), + $rem_n, + $this->renderValueList($rem)); + } else if ($new) { + return pht( + '%s changed %s slack attribute(s), added %s: %s.', + $this->renderAuthor(), + $all_n, + $add_n, + $this->rendervalueList($add)); + } else { + return pht( + '%s changed %s slack attribute(s), removed %s: %s.', + $this->renderAuthor(), + $all_n, + $rem_n, + $this->rendervalueList($rem)); + } + } + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + $valid_attributes = array( + 'generated' => true, + ); + + foreach ($xactions as $xaction) { + $new = $xaction->getNewValue(); + + foreach ($new as $attribute) { + if (isset($valid_attributes[$attribute])) { + continue; + } + + $errors[] = $this->newInvalidError( + pht( + 'Changeset attribute "%s" is not valid. Valid changeset '. + 'attributes are: %s.', + $attribute, + implode(', ', array_keys($valid_attributes))), + $xaction); + } + } + + return $errors; + } + +} diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index eb57c39b2c..c3e9ee4040 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -127,7 +127,10 @@ protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - return true; + // Don't ever send email for changes to a Phab Projects. + // We perform bulk updates in response to OWNER file changes + // and this can cause queuing and a lot of email spam. + return false; } protected function getMailSubjectPrefix() { diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php index 3ad5304945..13c469dacb 100644 --- a/src/infrastructure/markup/PhabricatorMarkupEngine.php +++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -541,6 +541,7 @@ public static function newMarkupEngine(array $options) { $rules[] = new PhutilRemarkupUnderlineRule(); $rules[] = new PhutilRemarkupHighlightRule(); $rules[] = new PhutilRemarkupAnchorRule(); + $rules[] = new PhutilRemarkupDisclosureRule(); foreach (self::loadCustomInlineRules() as $rule) { $rules[] = clone $rule; diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupDisclosureRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupDisclosureRule.php new file mode 100644 index 0000000000..4c530a14b7 --- /dev/null +++ b/src/infrastructure/markup/markuprule/PhutilRemarkupDisclosureRule.php @@ -0,0 +1,32 @@ +getEngine()->isTextMode()) { + return $text; + } + + // Tags to match in text and what the tag should look like after + // HTML sanitization. Altering the left margin creates indentation. + $replacements = array( + '
' => hsprintf('
'), + '
' => hsprintf('
'), + '' => hsprintf(''), + '' => hsprintf(''), + ); + + // Sanitize text and replace each sanitized tag with it's + // corresponding replacement text. + foreach ($replacements as $match => $replacement) { + $text = PhutilSafeHTML::applyFunction( + 'preg_replace', + hsprintf('@\s?%s\s?@s', $match), + $replacement, + $text); + } + + return $text; + } + +}