1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-19 21:32:43 +01:00

Implement most Differential actions against ApplicationTransactions

Summary: Ref T2222. Implements the simpler actions (abandon, reclaim, close, reopen, plan changes, request review) in a transactional way with validation and effect checks.

Test Plan:
  - Rigged submissions to point at the Pro controller.
  - Rigged dropdown to have all these options all the time.
  - Tried to apply about 20-30 of these operations to various revisions and always got the expected result (success, error, or no-op).

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2222

Differential Revision: https://secure.phabricator.com/D8307
This commit is contained in:
epriestley 2014-02-24 15:57:49 -08:00
parent d48a88d4cd
commit 4eadcf975a
3 changed files with 253 additions and 3 deletions

View file

@ -133,6 +133,9 @@ final class DifferentialCommentSaveControllerPro
return id(new PhabricatorApplicationTransactionNoEffectResponse()) return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($revision_uri) ->setCancelURI($revision_uri)
->setException($ex); ->setException($ex);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
// TODO: Provide a nice Response for rendering these in a clean way.
throw $ex;
} }
$user = $request->getUser(); $user = $request->getUser();

View file

@ -63,6 +63,26 @@ final class DifferentialTransactionEditor
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;
switch ($xaction->getNewValue()) {
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_revision);
case DifferentialAction::ACTION_REQUEST:
return ($object->getStatus() != $status_review);
}
} }
return parent::transactionHasEffect($object, $xaction); return parent::transactionHasEffect($object, $xaction);
@ -89,8 +109,41 @@ final class DifferentialTransactionEditor
// to "Accepted". // to "Accepted".
return; return;
case DifferentialTransaction::TYPE_ACTION: case DifferentialTransaction::TYPE_ACTION:
// TODO: For now, we're just shipping these through without acting $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
// on them. $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION;
switch ($xaction->getNewValue()) {
case DifferentialAction::ACTION_ABANDON:
$object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED);
break;
case DifferentialAction::ACTION_RETHINK:
$object->setStatus($status_revision);
break;
case DifferentialAction::ACTION_RECLAIM:
$object->setStatus($status_review);
// TODO: Update review status?
break;
case DifferentialAction::ACTION_REOPEN:
$object->setStatus($status_review);
// TODO: Update review status?
break;
case DifferentialAction::ACTION_REQUEST:
$object->setStatus($status_review);
// TODO: Update review status?
break;
case DifferentialAction::ACTION_CLOSE:
if (!$object->getDateCommitted()) {
// TODO: Can we remove this? It is probably no longer used by
// anything anymore. See also T4434.
$object->setDateCommitted(time());
}
$object->setStatus(ArcanistDifferentialRevisionStatus::CLOSED);
break;
default:
// TODO: For now, we're just shipping the rest of these through
// without acting on them.
break;
}
return null; return null;
} }
@ -123,12 +176,185 @@ final class DifferentialTransactionEditor
$errors = parent::validateTransaction($object, $type, $xactions); $errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) { foreach ($xactions as $xaction) {
switch ($type) {
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->getActor()->getPHID();
$actor_is_author = ($author_phid == $actor_phid);
$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);
$revision_status = $revision->getStatus();
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
$status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED;
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
switch ($action) {
case DifferentialAction::ACTION_ABANDON:
if (!$actor_is_author) {
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 chagnes 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::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_REQUEST:
if (!$actor_is_author) {
return pht(
"You can not request review of this revision because you do ".
"not own it. To request review of a revision, you must be its ".
"owner.");
}
switch ($revision_status) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
// These are OK.
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
// This will be caught as "no effect" later on.
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
return pht(
"You can not request review of this revision because it has ".
"been abandoned. Instead, reclaim it.");
case ArcanistDifferentialRevisionStatus::CLOSED:
return pht(
"You can not request review of 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:
// TODO: Permit the daemons to take this action in all cases.
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) {
$head = array(); $head = array();
$tail = array(); $tail = array();

View file

@ -123,6 +123,27 @@ final class DifferentialTransaction extends PhabricatorApplicationTransaction {
return pht( return pht(
'Those reviewers are already reviewing this revision.'); 'Those reviewers are already reviewing this revision.');
} }
break;
case DifferentialTransaction::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return pht('This revision is already closed.');
case DifferentialAction::ACTION_ABANDON:
return pht('This revision has already been abandoned.');
case DifferentialAction::ACTION_RECLAIM:
return pht(
'You can not reclaim this revision because his revision is '.
'not abandoned.');
case DifferentialAction::ACTION_REOPEN:
return pht(
'You can not reopen this revision because this revision is '.
'not closed.');
case DifferentialAction::ACTION_RETHINK:
return pht('This revision already requires changes.');
case DifferentialAction::ACTION_REQUEST:
return pht('Review is already requested for this revision.');
}
break;
} }
return parent::getNoEffectDescription(); return parent::getNoEffectDescription();