diff --git a/resources/sql/autopatches/20180504.owners.03.properties.sql b/resources/sql/autopatches/20180504.owners.03.properties.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20180504.owners.03.properties.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_owners.owners_package + ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20180504.owners.04.default.sql b/resources/sql/autopatches/20180504.owners.04.default.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20180504.owners.04.default.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_owners.owners_package + SET properties = '{}' WHERE properties = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3572,6 +3572,7 @@ 'PhabricatorOwnersPackageFerretEngine' => 'applications/owners/search/PhabricatorOwnersPackageFerretEngine.php', 'PhabricatorOwnersPackageFulltextEngine' => 'applications/owners/search/PhabricatorOwnersPackageFulltextEngine.php', 'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php', + 'PhabricatorOwnersPackageIgnoredTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageIgnoredTransaction.php', 'PhabricatorOwnersPackageNameNgrams' => 'applications/owners/storage/PhabricatorOwnersPackageNameNgrams.php', 'PhabricatorOwnersPackageNameTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageNameTransaction.php', 'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php', @@ -9321,6 +9322,7 @@ 'PhabricatorOwnersPackageFerretEngine' => 'PhabricatorFerretEngine', 'PhabricatorOwnersPackageFulltextEngine' => 'PhabricatorFulltextEngine', 'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorOwnersPackageIgnoredTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageNameNgrams' => 'PhabricatorSearchNgrams', 'PhabricatorOwnersPackageNameTransaction' => 'PhabricatorOwnersPackageTransactionType', 'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -201,6 +201,16 @@ } $view->addProperty(pht('Auditing'), $auditing); + $ignored = $package->getIgnoredPathAttributes(); + $ignored = array_keys($ignored); + if ($ignored) { + $ignored = implode(', ', $ignored); + } else { + $ignored = phutil_tag('em', array(), pht('None')); + } + + $view->addProperty(pht('Ignored Attributes'), $ignored); + $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 --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php @@ -162,6 +162,13 @@ ->setIsConduitOnly(true) ->setValue($object->getStatus()) ->setOptions($object->getStatusNameMap()), + id(new PhabricatorStringListEditField()) + ->setKey('ignored') + ->setLabel(pht('Ignored Attributes')) + ->setDescription(pht('Ignore paths with any of these attributes.')) + ->setTransactionType( + PhabricatorOwnersPackageIgnoredTransaction::TRANSACTIONTYPE) + ->setValue(array_keys($object->getIgnoredPathAttributes())), id(new PhabricatorConduitEditField()) ->setKey('paths.set') ->setLabel(pht('Paths')) diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -20,6 +20,7 @@ protected $viewPolicy; protected $editPolicy; protected $dominion; + protected $properties = array(); private $paths = self::ATTACHABLE; private $owners = self::ATTACHABLE; @@ -40,6 +41,8 @@ const DOMINION_STRONG = 'strong'; const DOMINION_WEAK = 'weak'; + const PROPERTY_IGNORED = 'ignored'; + public static function initializeNewPackage(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) @@ -117,6 +120,9 @@ // This information is better available from the history table. self::CONFIG_TIMESTAMPS => false, self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'sort', 'description' => 'text', @@ -137,8 +143,25 @@ } public function getMustMatchUngeneratedPaths() { - // TODO: For now, there's no way to actually configure this. - return false; + $ignore_attributes = $this->getIgnoredPathAttributes(); + return !empty($ignore_attributes['generated']); + } + + public function getPackageProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setPackageProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getIgnoredPathAttributes() { + return $this->getPackageProperty(self::PROPERTY_IGNORED, array()); + } + + public function setIgnoredPathAttributes(array $attributes) { + return $this->setPackageProperty(self::PROPERTY_IGNORED, $attributes); } public function loadOwners() { @@ -679,6 +702,10 @@ ->setKey('dominion') ->setType('map') ->setDescription(pht('Dominion setting information.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('ignored') + ->setType('map') + ->setDescription(pht('Ignored attribute information.')), ); } @@ -732,6 +759,13 @@ 'short' => $dominion_short, ); + // Force this to always emit as a JSON object even if empty, never as + // a JSON list. + $attributes = $this->getIgnoredPathAttributes(); + if (!$attributes) { + $attributes = (object)array(); + } + return array( 'name' => $this->getName(), 'description' => $this->getDescription(), @@ -740,6 +774,7 @@ 'review' => $review, 'audit' => $audit, 'dominion' => $dominion, + 'ignored' => $attributes, ); } diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageIgnoredTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageIgnoredTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/owners/xaction/PhabricatorOwnersPackageIgnoredTransaction.php @@ -0,0 +1,85 @@ +getIgnoredPathAttributes(); + } + + public function generateNewValue($object, $value) { + return array_fill_keys($value, true); + } + + public function applyInternalEffects($object, $value) { + $object->setIgnoredPathAttributes($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 ignored 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 ignored attribute(s), added %s: %s.', + $this->renderAuthor(), + $all_n, + $add_n, + $this->rendervalueList($add)); + } else { + return pht( + '%s changed %s ignored 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; + } + +}