Differential D18412 Diff 44256 src/applications/differential/editor/DifferentialTransactionEditor.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/differential/editor/DifferentialTransactionEditor.php
| Show First 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | final class DifferentialTransactionEditor | ||||
| public function getTransactionTypes() { | public function getTransactionTypes() { | ||||
| $types = parent::getTransactionTypes(); | $types = parent::getTransactionTypes(); | ||||
| $types[] = PhabricatorTransactions::TYPE_COMMENT; | $types[] = PhabricatorTransactions::TYPE_COMMENT; | ||||
| $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; | $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; | ||||
| $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; | $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; | ||||
| $types[] = PhabricatorTransactions::TYPE_INLINESTATE; | $types[] = PhabricatorTransactions::TYPE_INLINESTATE; | ||||
| $types[] = DifferentialTransaction::TYPE_ACTION; | |||||
| $types[] = DifferentialTransaction::TYPE_INLINE; | $types[] = DifferentialTransaction::TYPE_INLINE; | ||||
| $types[] = DifferentialTransaction::TYPE_UPDATE; | $types[] = DifferentialTransaction::TYPE_UPDATE; | ||||
| return $types; | return $types; | ||||
| } | } | ||||
| protected function getCustomTransactionOldValue( | protected function getCustomTransactionOldValue( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
| switch ($xaction->getTransactionType()) { | switch ($xaction->getTransactionType()) { | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| return null; | |||||
| case DifferentialTransaction::TYPE_INLINE: | case DifferentialTransaction::TYPE_INLINE: | ||||
| return null; | return null; | ||||
| case DifferentialTransaction::TYPE_UPDATE: | case DifferentialTransaction::TYPE_UPDATE: | ||||
| if ($this->getIsNewObject()) { | if ($this->getIsNewObject()) { | ||||
| return null; | return null; | ||||
| } else { | } else { | ||||
| return $object->getActiveDiff()->getPHID(); | return $object->getActiveDiff()->getPHID(); | ||||
| } | } | ||||
| } | } | ||||
| return parent::getCustomTransactionOldValue($object, $xaction); | return parent::getCustomTransactionOldValue($object, $xaction); | ||||
| } | } | ||||
| protected function getCustomTransactionNewValue( | protected function getCustomTransactionNewValue( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
| switch ($xaction->getTransactionType()) { | switch ($xaction->getTransactionType()) { | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| case DifferentialTransaction::TYPE_UPDATE: | case DifferentialTransaction::TYPE_UPDATE: | ||||
| return $xaction->getNewValue(); | return $xaction->getNewValue(); | ||||
| case DifferentialTransaction::TYPE_INLINE: | case DifferentialTransaction::TYPE_INLINE: | ||||
| return null; | return null; | ||||
| } | } | ||||
| return parent::getCustomTransactionNewValue($object, $xaction); | return parent::getCustomTransactionNewValue($object, $xaction); | ||||
| } | } | ||||
| protected function transactionHasEffect( | protected function transactionHasEffect( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
| $actor_phid = $this->getActingAsPHID(); | $actor_phid = $this->getActingAsPHID(); | ||||
| switch ($xaction->getTransactionType()) { | switch ($xaction->getTransactionType()) { | ||||
| case DifferentialTransaction::TYPE_INLINE: | case DifferentialTransaction::TYPE_INLINE: | ||||
| return $xaction->hasComment(); | return $xaction->hasComment(); | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; | |||||
| $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; | |||||
| $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; | |||||
| $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; | |||||
| $status_plan = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; | |||||
| $action_type = $xaction->getNewValue(); | |||||
| switch ($action_type) { | |||||
| case DifferentialAction::ACTION_CLOSE: | |||||
| return ($object->getStatus() != $status_closed); | |||||
| case DifferentialAction::ACTION_ABANDON: | |||||
| return ($object->getStatus() != $status_abandoned); | |||||
| case DifferentialAction::ACTION_RECLAIM: | |||||
| return ($object->getStatus() == $status_abandoned); | |||||
| case DifferentialAction::ACTION_REOPEN: | |||||
| return ($object->getStatus() == $status_closed); | |||||
| case DifferentialAction::ACTION_RETHINK: | |||||
| return ($object->getStatus() != $status_plan); | |||||
| case DifferentialAction::ACTION_CLAIM: | |||||
| return ($actor_phid != $object->getAuthorPHID()); | |||||
| } | |||||
| } | } | ||||
| return parent::transactionHasEffect($object, $xaction); | return parent::transactionHasEffect($object, $xaction); | ||||
| } | } | ||||
| protected function applyCustomInternalTransaction( | protected function applyCustomInternalTransaction( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
| Show All 25 Lines | switch ($xaction->getTransactionType()) { | ||||
| $object->setRepositoryPHID($this->repositoryPHIDOverride); | $object->setRepositoryPHID($this->repositoryPHIDOverride); | ||||
| } else { | } else { | ||||
| $object->setRepositoryPHID($diff->getRepositoryPHID()); | $object->setRepositoryPHID($diff->getRepositoryPHID()); | ||||
| } | } | ||||
| $object->attachActiveDiff($diff); | $object->attachActiveDiff($diff); | ||||
| // TODO: Update the `diffPHID` once we add that. | // TODO: Update the `diffPHID` once we add that. | ||||
| return; | return; | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| switch ($xaction->getNewValue()) { | |||||
| case DifferentialAction::ACTION_ABANDON: | |||||
| $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); | |||||
| return; | |||||
| case DifferentialAction::ACTION_RETHINK: | |||||
| $object->setStatus($status_plan); | |||||
| return; | |||||
| case DifferentialAction::ACTION_RECLAIM: | |||||
| $object->setStatus($status_review); | |||||
| return; | |||||
| case DifferentialAction::ACTION_REOPEN: | |||||
| $object->setStatus($status_review); | |||||
| return; | |||||
| case DifferentialAction::ACTION_CLOSE: | |||||
| $old_status = $object->getStatus(); | |||||
| $object->setStatus(ArcanistDifferentialRevisionStatus::CLOSED); | |||||
| $was_accepted = ($old_status == $status_accepted); | |||||
| $object->setProperty( | |||||
| DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED, | |||||
| $was_accepted); | |||||
| return; | |||||
| case DifferentialAction::ACTION_CLAIM: | |||||
| $object->setAuthorPHID($this->getActingAsPHID()); | |||||
| return; | |||||
| default: | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Differential action "%s" is not a valid action!', | |||||
| $xaction->getNewValue())); | |||||
| } | |||||
| break; | |||||
| } | } | ||||
| return parent::applyCustomInternalTransaction($object, $xaction); | return parent::applyCustomInternalTransaction($object, $xaction); | ||||
| } | } | ||||
| protected function expandTransactions( | protected function expandTransactions( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| array $xactions) { | array $xactions) { | ||||
| ▲ Show 20 Lines • Show All 125 Lines • ▼ Show 20 Lines | switch ($xaction->getTransactionType()) { | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| break; | break; | ||||
| case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE: | case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE: | ||||
| $is_commandeer = true; | $is_commandeer = true; | ||||
| break; | break; | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| $action_type = $xaction->getNewValue(); | |||||
| switch ($action_type) { | |||||
| case DifferentialAction::ACTION_CLAIM: | |||||
| $is_commandeer = true; | |||||
| break; | |||||
| } | |||||
| break; | |||||
| } | } | ||||
| if ($is_commandeer) { | if ($is_commandeer) { | ||||
| $results[] = $this->newCommandeerReviewerTransaction($object); | $results[] = $this->newCommandeerReviewerTransaction($object); | ||||
| } | } | ||||
| if (!$this->didExpandInlineState) { | if (!$this->didExpandInlineState) { | ||||
| switch ($xaction->getTransactionType()) { | switch ($xaction->getTransactionType()) { | ||||
| case PhabricatorTransactions::TYPE_COMMENT: | case PhabricatorTransactions::TYPE_COMMENT: | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| case DifferentialTransaction::TYPE_UPDATE: | case DifferentialTransaction::TYPE_UPDATE: | ||||
| case DifferentialTransaction::TYPE_INLINE: | case DifferentialTransaction::TYPE_INLINE: | ||||
| $this->didExpandInlineState = true; | $this->didExpandInlineState = true; | ||||
| $actor_phid = $this->getActingAsPHID(); | $actor_phid = $this->getActingAsPHID(); | ||||
| $actor_is_author = ($object->getAuthorPHID() == $actor_phid); | $actor_is_author = ($object->getAuthorPHID() == $actor_phid); | ||||
| if (!$actor_is_author) { | if (!$actor_is_author) { | ||||
| break; | break; | ||||
| Show All 29 Lines | protected function expandTransaction( | ||||
| return $results; | return $results; | ||||
| } | } | ||||
| protected function applyCustomExternalTransaction( | protected function applyCustomExternalTransaction( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| PhabricatorApplicationTransaction $xaction) { | PhabricatorApplicationTransaction $xaction) { | ||||
| switch ($xaction->getTransactionType()) { | switch ($xaction->getTransactionType()) { | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| return; | |||||
| case DifferentialTransaction::TYPE_INLINE: | case DifferentialTransaction::TYPE_INLINE: | ||||
| $reply = $xaction->getComment()->getReplyToComment(); | $reply = $xaction->getComment()->getReplyToComment(); | ||||
| if ($reply && !$reply->getHasReplies()) { | if ($reply && !$reply->getHasReplies()) { | ||||
| $reply->setHasReplies(1)->save(); | $reply->setHasReplies(1)->save(); | ||||
| } | } | ||||
| return; | return; | ||||
| case DifferentialTransaction::TYPE_UPDATE: | case DifferentialTransaction::TYPE_UPDATE: | ||||
| // Now that we're inside the transaction, do a final check. | // Now that we're inside the transaction, do a final check. | ||||
| ▲ Show 20 Lines • Show All 221 Lines • ▼ Show 20 Lines | foreach ($xactions as $xaction) { | ||||
| $type, | $type, | ||||
| pht('Invalid'), | pht('Invalid'), | ||||
| pht( | pht( | ||||
| 'You can not update this revision to the specified diff, '. | 'You can not update this revision to the specified diff, '. | ||||
| 'because the diff is already attached to another revision.'), | 'because the diff is already attached to another revision.'), | ||||
| $xaction); | $xaction); | ||||
| } | } | ||||
| break; | break; | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| $error = $this->validateDifferentialAction( | |||||
| $object, | |||||
| $type, | |||||
| $xaction, | |||||
| $xaction->getNewValue()); | |||||
| if ($error) { | |||||
| $errors[] = new PhabricatorApplicationTransactionValidationError( | |||||
| $type, | |||||
| pht('Invalid'), | |||||
| $error, | |||||
| $xaction); | |||||
| } | |||||
| break; | |||||
| } | } | ||||
| } | } | ||||
| return $errors; | return $errors; | ||||
| } | } | ||||
| private function validateDifferentialAction( | |||||
| DifferentialRevision $revision, | |||||
| $type, | |||||
| DifferentialTransaction $xaction, | |||||
| $action) { | |||||
| $author_phid = $revision->getAuthorPHID(); | |||||
| $actor_phid = $this->getActingAsPHID(); | |||||
| $actor_is_author = ($author_phid == $actor_phid); | |||||
| $config_abandon_key = 'differential.always-allow-abandon'; | |||||
| $always_allow_abandon = PhabricatorEnv::getEnvConfig($config_abandon_key); | |||||
| $config_close_key = 'differential.always-allow-close'; | |||||
| $always_allow_close = PhabricatorEnv::getEnvConfig($config_close_key); | |||||
| $config_reopen_key = 'differential.allow-reopen'; | |||||
| $allow_reopen = PhabricatorEnv::getEnvConfig($config_reopen_key); | |||||
| $config_self_accept_key = 'differential.allow-self-accept'; | |||||
| $allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key); | |||||
| $revision_status = $revision->getStatus(); | |||||
| $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; | |||||
| $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; | |||||
| $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; | |||||
| switch ($action) { | |||||
| case DifferentialAction::ACTION_CLAIM: | |||||
| // You can claim a revision if you're not the owner. If you are, this | |||||
| // is a no-op rather than invalid. | |||||
| if ($revision_status == $status_closed) { | |||||
| return pht( | |||||
| 'You can not commandeer this revision because it has already been '. | |||||
| 'closed.'); | |||||
| } | |||||
| break; | |||||
| case DifferentialAction::ACTION_ABANDON: | |||||
| if (!$actor_is_author && !$always_allow_abandon) { | |||||
| return pht( | |||||
| 'You can not abandon this revision because you do not own it. '. | |||||
| 'You can only abandon revisions you own.'); | |||||
| } | |||||
| if ($revision_status == $status_closed) { | |||||
| return pht( | |||||
| 'You can not abandon this revision because it has already been '. | |||||
| 'closed.'); | |||||
| } | |||||
| // NOTE: Abandons of already-abandoned revisions are treated as no-op | |||||
| // instead of invalid. Other abandons are OK. | |||||
| break; | |||||
| case DifferentialAction::ACTION_RECLAIM: | |||||
| if (!$actor_is_author) { | |||||
| return pht( | |||||
| 'You can not reclaim this revision because you do not own '. | |||||
| 'it. You can only reclaim revisions you own.'); | |||||
| } | |||||
| if ($revision_status == $status_closed) { | |||||
| return pht( | |||||
| 'You can not reclaim this revision because it has already been '. | |||||
| 'closed.'); | |||||
| } | |||||
| // NOTE: Reclaims of other non-abandoned revisions are treated as no-op | |||||
| // instead of invalid. | |||||
| break; | |||||
| case DifferentialAction::ACTION_REOPEN: | |||||
| if (!$allow_reopen) { | |||||
| return pht( | |||||
| 'The reopen action is not enabled on this Phabricator install. '. | |||||
| 'Adjust your configuration to enable it.'); | |||||
| } | |||||
| // NOTE: If the revision is not closed, this is caught as a no-op | |||||
| // instead of an invalid transaction. | |||||
| break; | |||||
| case DifferentialAction::ACTION_RETHINK: | |||||
| if (!$actor_is_author) { | |||||
| return pht( | |||||
| 'You can not plan changes to this revision because you do not '. | |||||
| 'own it. To plan changes to a revision, you must be its owner.'); | |||||
| } | |||||
| switch ($revision_status) { | |||||
| case ArcanistDifferentialRevisionStatus::ACCEPTED: | |||||
| case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: | |||||
| case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: | |||||
| // These are OK. | |||||
| break; | |||||
| case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED: | |||||
| // Let this through, it's a no-op. | |||||
| break; | |||||
| case ArcanistDifferentialRevisionStatus::ABANDONED: | |||||
| return pht( | |||||
| 'You can not plan changes to this revision because it has '. | |||||
| 'been abandoned.'); | |||||
| case ArcanistDifferentialRevisionStatus::CLOSED: | |||||
| return pht( | |||||
| 'You can not plan changes to this revision because it has '. | |||||
| 'already been closed.'); | |||||
| default: | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Encountered unexpected revision status ("%s") when '. | |||||
| 'validating "%s" action.', | |||||
| $revision_status, | |||||
| $action)); | |||||
| } | |||||
| break; | |||||
| case DifferentialAction::ACTION_CLOSE: | |||||
| // We force revisions closed when we discover a corresponding commit. | |||||
| // In this case, revisions are allowed to transition to closed from | |||||
| // any state. This is an automated action taken by the daemons. | |||||
| if (!$this->getIsCloseByCommit()) { | |||||
| if (!$actor_is_author && !$always_allow_close) { | |||||
| return pht( | |||||
| 'You can not close this revision because you do not own it. To '. | |||||
| 'close a revision, you must be its owner.'); | |||||
| } | |||||
| if ($revision_status != $status_accepted) { | |||||
| return pht( | |||||
| 'You can not close this revision because it has not been '. | |||||
| 'accepted. You can only close accepted revisions.'); | |||||
| } | |||||
| } | |||||
| break; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| protected function sortTransactions(array $xactions) { | protected function sortTransactions(array $xactions) { | ||||
| $xactions = parent::sortTransactions($xactions); | $xactions = parent::sortTransactions($xactions); | ||||
| $head = array(); | $head = array(); | ||||
| $tail = array(); | $tail = array(); | ||||
| foreach ($xactions as $xaction) { | foreach ($xactions as $xaction) { | ||||
| $type = $xaction->getTransactionType(); | $type = $xaction->getTransactionType(); | ||||
| ▲ Show 20 Lines • Show All 391 Lines • ▼ Show 20 Lines | foreach ($xactions as $xaction) { | ||||
| if (!$this->getIsCloseByCommit()) { | if (!$this->getIsCloseByCommit()) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| break; | break; | ||||
| case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE: | case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE: | ||||
| // When users commandeer revisions, we may need to trigger | // When users commandeer revisions, we may need to trigger | ||||
| // signatures or author-based rules. | // signatures or author-based rules. | ||||
| return true; | return true; | ||||
| case DifferentialTransaction::TYPE_ACTION: | |||||
| switch ($xaction->getNewValue()) { | |||||
| case DifferentialAction::ACTION_CLAIM: | |||||
| // When users commandeer revisions, we may need to trigger | |||||
| // signatures or author-based rules. | |||||
| return true; | |||||
| } | |||||
| break; | |||||
| } | } | ||||
| } | } | ||||
| return parent::shouldApplyHeraldRules($object, $xactions); | return parent::shouldApplyHeraldRules($object, $xactions); | ||||
| } | } | ||||
| protected function didApplyHeraldRules( | protected function didApplyHeraldRules( | ||||
| PhabricatorLiskDAO $object, | PhabricatorLiskDAO $object, | ||||
| ▲ Show 20 Lines • Show All 491 Lines • Show Last 20 Lines | |||||