@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+253 -3
+3
src/applications/differential/controller/DifferentialCommentSaveControllerPro.php
··· 133 133 return id(new PhabricatorApplicationTransactionNoEffectResponse()) 134 134 ->setCancelURI($revision_uri) 135 135 ->setException($ex); 136 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 137 + // TODO: Provide a nice Response for rendering these in a clean way. 138 + throw $ex; 136 139 } 137 140 138 141 $user = $request->getUser();
+229 -3
src/applications/differential/editor/DifferentialTransactionEditor.php
··· 63 63 switch ($xaction->getTransactionType()) { 64 64 case DifferentialTransaction::TYPE_INLINE: 65 65 return $xaction->hasComment(); 66 + case DifferentialTransaction::TYPE_ACTION: 67 + $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; 68 + $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; 69 + $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; 70 + $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; 71 + 72 + switch ($xaction->getNewValue()) { 73 + case DifferentialAction::ACTION_CLOSE: 74 + return ($object->getStatus() != $status_closed); 75 + case DifferentialAction::ACTION_ABANDON: 76 + return ($object->getStatus() != $status_abandoned); 77 + case DifferentialAction::ACTION_RECLAIM: 78 + return ($object->getStatus() == $status_abandoned); 79 + case DifferentialAction::ACTION_REOPEN: 80 + return ($object->getStatus() == $status_closed); 81 + case DifferentialAction::ACTION_RETHINK: 82 + return ($object->getStatus() != $status_revision); 83 + case DifferentialAction::ACTION_REQUEST: 84 + return ($object->getStatus() != $status_review); 85 + } 66 86 } 67 87 68 88 return parent::transactionHasEffect($object, $xaction); ··· 89 109 // to "Accepted". 90 110 return; 91 111 case DifferentialTransaction::TYPE_ACTION: 92 - // TODO: For now, we're just shipping these through without acting 93 - // on them. 112 + $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; 113 + $status_revision = ArcanistDifferentialRevisionStatus::NEEDS_REVISION; 114 + 115 + switch ($xaction->getNewValue()) { 116 + case DifferentialAction::ACTION_ABANDON: 117 + $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); 118 + break; 119 + case DifferentialAction::ACTION_RETHINK: 120 + $object->setStatus($status_revision); 121 + break; 122 + case DifferentialAction::ACTION_RECLAIM: 123 + $object->setStatus($status_review); 124 + // TODO: Update review status? 125 + break; 126 + case DifferentialAction::ACTION_REOPEN: 127 + $object->setStatus($status_review); 128 + // TODO: Update review status? 129 + break; 130 + case DifferentialAction::ACTION_REQUEST: 131 + $object->setStatus($status_review); 132 + // TODO: Update review status? 133 + break; 134 + case DifferentialAction::ACTION_CLOSE: 135 + if (!$object->getDateCommitted()) { 136 + // TODO: Can we remove this? It is probably no longer used by 137 + // anything anymore. See also T4434. 138 + $object->setDateCommitted(time()); 139 + } 140 + $object->setStatus(ArcanistDifferentialRevisionStatus::CLOSED); 141 + break; 142 + default: 143 + // TODO: For now, we're just shipping the rest of these through 144 + // without acting on them. 145 + break; 146 + } 94 147 return null; 95 148 } 96 149 ··· 123 176 124 177 $errors = parent::validateTransaction($object, $type, $xactions); 125 178 126 - switch ($type) { 179 + foreach ($xactions as $xaction) { 180 + switch ($type) { 181 + case DifferentialTransaction::TYPE_ACTION: 182 + $error = $this->validateDifferentialAction( 183 + $object, 184 + $type, 185 + $xaction, 186 + $xaction->getNewValue()); 187 + if ($error) { 188 + $errors[] = new PhabricatorApplicationTransactionValidationError( 189 + $type, 190 + pht('Invalid'), 191 + $error, 192 + $xaction); 193 + } 194 + break; 195 + } 127 196 } 128 197 129 198 return $errors; 199 + } 200 + 201 + private function validateDifferentialAction( 202 + DifferentialRevision $revision, 203 + $type, 204 + DifferentialTransaction $xaction, 205 + $action) { 206 + 207 + $author_phid = $revision->getAuthorPHID(); 208 + $actor_phid = $this->getActor()->getPHID(); 209 + $actor_is_author = ($author_phid == $actor_phid); 210 + 211 + $config_close_key = 'differential.always-allow-close'; 212 + $always_allow_close = PhabricatorEnv::getEnvConfig($config_close_key); 213 + 214 + $config_reopen_key = 'differential.allow-reopen'; 215 + $allow_reopen = PhabricatorEnv::getEnvConfig($config_reopen_key); 216 + 217 + $revision_status = $revision->getStatus(); 218 + 219 + $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; 220 + $status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED; 221 + $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; 222 + 223 + switch ($action) { 224 + case DifferentialAction::ACTION_ABANDON: 225 + if (!$actor_is_author) { 226 + return pht( 227 + "You can not abandon this revision because you do not own it. ". 228 + "You can only abandon revisions you own."); 229 + } 230 + 231 + if ($revision_status == $status_closed) { 232 + return pht( 233 + "You can not abandon this revision because it has already been ". 234 + "closed."); 235 + } 236 + 237 + // NOTE: Abandons of already-abandoned revisions are treated as no-op 238 + // instead of invalid. Other abandons are OK. 239 + 240 + break; 241 + 242 + case DifferentialAction::ACTION_RECLAIM: 243 + if (!$actor_is_author) { 244 + return pht( 245 + "You can not reclaim this revision because you do not own ". 246 + "it. You can only reclaim revisions you own."); 247 + } 248 + 249 + if ($revision_status == $status_closed) { 250 + return pht( 251 + "You can not reclaim this revision because it has already been ". 252 + "closed."); 253 + } 254 + 255 + // NOTE: Reclaims of other non-abandoned revisions are treated as no-op 256 + // instead of invalid. 257 + 258 + break; 259 + 260 + case DifferentialAction::ACTION_REOPEN: 261 + if (!$allow_reopen) { 262 + return pht( 263 + 'The reopen action is not enabled on this Phabricator install. '. 264 + 'Adjust your configuration to enable it.'); 265 + } 266 + 267 + // NOTE: If the revision is not closed, this is caught as a no-op 268 + // instead of an invalid transaction. 269 + 270 + break; 271 + 272 + case DifferentialAction::ACTION_RETHINK: 273 + if (!$actor_is_author) { 274 + return pht( 275 + "You can not plan changes to this revision because you do not ". 276 + "own it. To plan chagnes to a revision, you must be its owner."); 277 + } 278 + 279 + switch ($revision_status) { 280 + case ArcanistDifferentialRevisionStatus::ACCEPTED: 281 + case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 282 + case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 283 + // These are OK. 284 + break; 285 + case ArcanistDifferentialRevisionStatus::ABANDONED: 286 + return pht( 287 + "You can not plan changes to this revision because it has ". 288 + "been abandoned."); 289 + case ArcanistDifferentialRevisionStatus::CLOSED: 290 + return pht( 291 + "You can not plan changes to this revision because it has ". 292 + "already been closed."); 293 + default: 294 + throw new Exception( 295 + pht( 296 + 'Encountered unexpected revision status ("%s") when '. 297 + 'validating "%s" action.', 298 + $revision_status, 299 + $action)); 300 + } 301 + break; 302 + 303 + case DifferentialAction::ACTION_REQUEST: 304 + if (!$actor_is_author) { 305 + return pht( 306 + "You can not request review of this revision because you do ". 307 + "not own it. To request review of a revision, you must be its ". 308 + "owner."); 309 + } 310 + 311 + switch ($revision_status) { 312 + case ArcanistDifferentialRevisionStatus::ACCEPTED: 313 + case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: 314 + // These are OK. 315 + break; 316 + case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: 317 + // This will be caught as "no effect" later on. 318 + break; 319 + case ArcanistDifferentialRevisionStatus::ABANDONED: 320 + return pht( 321 + "You can not request review of this revision because it has ". 322 + "been abandoned. Instead, reclaim it."); 323 + case ArcanistDifferentialRevisionStatus::CLOSED: 324 + return pht( 325 + "You can not request review of this revision because it has ". 326 + "already been closed."); 327 + default: 328 + throw new Exception( 329 + pht( 330 + 'Encountered unexpected revision status ("%s") when '. 331 + 'validating "%s" action.', 332 + $revision_status, 333 + $action)); 334 + } 335 + break; 336 + 337 + case DifferentialAction::ACTION_CLOSE: 338 + 339 + // TODO: Permit the daemons to take this action in all cases. 340 + 341 + if (!$actor_is_author && !$always_allow_close) { 342 + return pht( 343 + "You can not close this revision because you do not own it. To ". 344 + "close a revision, you must be its owner."); 345 + } 346 + 347 + if ($revision_status != $status_accepted) { 348 + return pht( 349 + "You can not close this revision because it has not been ". 350 + "accepted. You can only close accepted revisions."); 351 + } 352 + break; 353 + } 354 + 355 + return null; 130 356 } 131 357 132 358 protected function sortTransactions(array $xactions) {
+21
src/applications/differential/storage/DifferentialTransaction.php
··· 123 123 return pht( 124 124 'Those reviewers are already reviewing this revision.'); 125 125 } 126 + break; 127 + case DifferentialTransaction::TYPE_ACTION: 128 + switch ($this->getNewValue()) { 129 + case DifferentialAction::ACTION_CLOSE: 130 + return pht('This revision is already closed.'); 131 + case DifferentialAction::ACTION_ABANDON: 132 + return pht('This revision has already been abandoned.'); 133 + case DifferentialAction::ACTION_RECLAIM: 134 + return pht( 135 + 'You can not reclaim this revision because his revision is '. 136 + 'not abandoned.'); 137 + case DifferentialAction::ACTION_REOPEN: 138 + return pht( 139 + 'You can not reopen this revision because this revision is '. 140 + 'not closed.'); 141 + case DifferentialAction::ACTION_RETHINK: 142 + return pht('This revision already requires changes.'); 143 + case DifferentialAction::ACTION_REQUEST: 144 + return pht('Review is already requested for this revision.'); 145 + } 146 + break; 126 147 } 127 148 128 149 return parent::getNoEffectDescription();