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 @@ -613,6 +613,7 @@ 'DiffusionCommandEngine' => 'applications/diffusion/protocol/DiffusionCommandEngine.php', 'DiffusionCommandEngineTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionCommandEngineTestCase.php', 'DiffusionCommitAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAffectedFilesHeraldField.php', + 'DiffusionCommitAuditorsTransaction' => 'applications/diffusion/xaction/DiffusionCommitAuditorsTransaction.php', 'DiffusionCommitAuthorHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuthorHeraldField.php', 'DiffusionCommitAutocloseHeraldField' => 'applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php', 'DiffusionCommitBranchesController' => 'applications/diffusion/controller/DiffusionCommitBranchesController.php', @@ -659,6 +660,7 @@ 'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php', 'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php', 'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php', + 'DiffusionCommitTransactionType' => 'applications/diffusion/xaction/DiffusionCommitTransactionType.php', 'DiffusionCompareController' => 'applications/diffusion/controller/DiffusionCompareController.php', 'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php', 'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php', @@ -5307,6 +5309,7 @@ 'DiffusionCommandEngine' => 'Phobject', 'DiffusionCommandEngineTestCase' => 'PhabricatorTestCase', 'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField', + 'DiffusionCommitAuditorsTransaction' => 'DiffusionCommitTransactionType', 'DiffusionCommitAuthorHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitAutocloseHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitBranchesController' => 'DiffusionController', @@ -5353,6 +5356,7 @@ 'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitTagsController' => 'DiffusionController', + 'DiffusionCommitTransactionType' => 'PhabricatorModularTransactionType', 'DiffusionCompareController' => 'DiffusionController', 'DiffusionConduitAPIMethod' => 'ConduitAPIMethod', 'DiffusionController' => 'PhabricatorController', @@ -6756,7 +6760,7 @@ 'PhabricatorAuditPreviewController' => 'PhabricatorAuditController', 'PhabricatorAuditReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorAuditStatusConstants' => 'Phobject', - 'PhabricatorAuditTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorAuditTransaction' => 'PhabricatorModularTransaction', 'PhabricatorAuditTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorAuditTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorAuditTransactionView' => 'PhabricatorApplicationTransactionView', diff --git a/src/applications/audit/storage/PhabricatorAuditTransaction.php b/src/applications/audit/storage/PhabricatorAuditTransaction.php --- a/src/applications/audit/storage/PhabricatorAuditTransaction.php +++ b/src/applications/audit/storage/PhabricatorAuditTransaction.php @@ -1,7 +1,7 @@ attachRepository($repository) - ->attachCommitData($data); + ->attachCommitData($data) + ->attachAudits(array()); } protected function newObjectQuery() { return id(new DiffusionCommitQuery()) - ->needCommitData(true); + ->needCommitData(true) + ->needAuditRequests(true); } protected function getObjectCreateTitleText($object) { @@ -77,6 +79,19 @@ $fields = array(); + $fields[] = id(new PhabricatorDatasourceEditField()) + ->setKey('auditors') + ->setLabel(pht('Auditors')) + ->setDatasource(new DiffusionAuditorDatasource()) + ->setUseEdgeTransactions(true) + ->setTransactionType( + DiffusionCommitAuditorsTransaction::TRANSACTIONTYPE) + ->setCommentActionLabel(pht('Change Auditors')) + ->setDescription(pht('Auditors for this commit.')) + ->setConduitDescription(pht('Change the auditors for this commit.')) + ->setConduitTypeDescription(pht('New auditors.')) + ->setValue($object->getAuditorPHIDsForEdit()); + $reason = $data->getCommitDetail('autocloseReason', false); $reason = PhabricatorRepository::BECAUSE_AUTOCLOSE_FORCED; if ($reason !== false) { diff --git a/src/applications/diffusion/xaction/DiffusionCommitAuditorsTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitAuditorsTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/xaction/DiffusionCommitAuditorsTransaction.php @@ -0,0 +1,231 @@ +getAudits(); + return mpull($auditors, 'getAuditStatus', 'getAuditorPHID'); + } + + public function generateNewValue($object, $value) { + $actor = $this->getActor(); + + $auditors = $this->generateOldValue($object); + $old_auditors = $auditors; + + $request_status = PhabricatorAuditStatusConstants::AUDIT_REQUESTED; + + $rem = idx($value, '-', array()); + foreach ($rem as $phid) { + unset($auditors[$phid]); + } + + $add = idx($value, '+', array()); + $add_map = array(); + foreach ($add as $phid) { + $add_map[$phid] = $request_status; + } + + $set = idx($value, '=', null); + if ($set !== null) { + foreach ($set as $phid) { + $add_map[$phid] = $request_status; + } + + $auditors = array(); + } + + foreach ($add_map as $phid => $new_status) { + $old_status = idx($old_auditors, $phid); + + if ($old_status) { + $auditors[$phid] = $old_status; + continue; + } + + $auditors[$phid] = $new_status; + } + + return $auditors; + } + + public function getTransactionHasEffect($object, $old, $new) { + ksort($old); + ksort($new); + return ($old !== $new); + } + + public function applyExternalEffects($object, $value) { + $src_phid = $object->getPHID(); + + $old = $this->generateOldValue($object); + $new = $value; + + $auditors = $object->getAudits(); + $auditors = mpull($auditors, null, 'getAuditorPHID'); + + $rem = array_diff_key($old, $new); + foreach ($rem as $phid => $status) { + $auditor = idx($auditors, $phid); + if ($auditor) { + $auditor->delete(); + } + } + + foreach ($new as $phid => $status) { + $auditor = idx($auditors, $phid); + if (!$auditor) { + $auditor = id(new PhabricatorRepositoryAuditRequest()) + ->setAuditorPHID($phid) + ->setCommitPHID($object->getPHID()); + } else { + if ($auditor->getAuditStatus() === $status) { + continue; + } + } + + $auditor + ->setAuditStatus($status) + ->save(); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $rem = array_diff_key($old, $new); + $add = array_diff_key($new, $old); + $rem_phids = array_keys($rem); + $add_phids = array_keys($add); + $total_count = count($rem) + count($add); + + if ($rem && $add) { + return pht( + '%s edited %s auditor(s), removed %s: %s; added %s: %s.', + $this->renderAuthor(), + new PhutilNumber($total_count), + phutil_count($rem_phids), + $this->renderHandleList($rem_phids), + phutil_count($add_phids), + $this->renderHandleList($add_phids)); + } else if ($add) { + return pht( + '%s added %s auditor(s): %s.', + $this->renderAuthor(), + phutil_count($add_phids), + $this->renderHandleList($add_phids)); + } else { + return pht( + '%s removed %s auditor(s): %s.', + $this->renderAuthor(), + phutil_count($rem_phids), + $this->renderHandleList($rem_phids)); + } + } + + public function getTitleForFeed() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $rem = array_diff_key($old, $new); + $add = array_diff_key($new, $old); + $rem_phids = array_keys($rem); + $add_phids = array_keys($add); + $total_count = count($rem) + count($add); + + if ($rem && $add) { + return pht( + '%s edited %s auditor(s) for %s, removed %s: %s; added %s: %s.', + $this->renderAuthor(), + new PhutilNumber($total_count), + $this->renderObject(), + phutil_count($rem_phids), + $this->renderHandleList($rem_phids), + phutil_count($add_phids), + $this->renderHandleList($add_phids)); + } else if ($add) { + return pht( + '%s added %s auditor(s) for %s: %s.', + $this->renderAuthor(), + phutil_count($add_phids), + $this->renderObject(), + $this->renderHandleList($add_phids)); + } else { + return pht( + '%s removed %s auditor(s) for %s: %s.', + $this->renderAuthor(), + phutil_count($rem_phids), + $this->renderObject(), + $this->renderHandleList($rem_phids)); + } + } + + public function validateTransactions($object, array $xactions) { + $actor = $this->getActor(); + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $author_phid = $object->getAuthorPHID(); + $can_author_close_key = 'audit.can-author-close-audit'; + $can_author_close = PhabricatorEnv::getEnvConfig($can_author_close_key); + + $old = $this->generateOldValue($object); + foreach ($xactions as $xaction) { + $new = $this->generateNewValue($object, $xaction->getNewValue()); + + $add = array_diff_key($new, $old); + if (!$add) { + continue; + } + + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($actor) + ->withPHIDs(array_keys($add)) + ->execute(); + $objects = mpull($objects, null, 'getPHID'); + + foreach ($add as $phid => $status) { + if (!isset($objects[$phid])) { + $errors[] = $this->newInvalidError( + pht( + 'Auditor "%s" is not a valid object.', + $phid), + $xaction); + continue; + } + + switch (phid_get_type($phid)) { + case PhabricatorPeopleUserPHIDType::TYPECONST: + case PhabricatorOwnersPackagePHIDType::TYPECONST: + case PhabricatorProjectProjectPHIDType::TYPECONST: + break; + default: + $errors[] = $this->newInvalidError( + pht( + 'Auditor "%s" must be a user, a package, or a project.', + $phid), + $xaction); + continue 2; + } + + $is_self = ($phid === $author_phid); + if ($is_self && !$can_author_close) { + $errors[] = $this->newInvalidError( + pht('The author of a commit can not be an auditor.'), + $xaction); + continue; + } + } + } + + return $errors; + } + +} diff --git a/src/applications/diffusion/xaction/DiffusionCommitTransactionType.php b/src/applications/diffusion/xaction/DiffusionCommitTransactionType.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/xaction/DiffusionCommitTransactionType.php @@ -0,0 +1,4 @@ +getAudits(); + return mpull($audits, 'getAuditorPHID'); + } + public function save() { if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20);