[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class DifferentialRevision extends DifferentialDAO 4 implements 5 PhabricatorTokenReceiverInterface, 6 PhabricatorPolicyInterface, 7 PhabricatorFlaggableInterface, 8 PhrequentTrackableInterface, 9 HarbormasterBuildableInterface, 10 PhabricatorSubscribableInterface, 11 PhabricatorCustomFieldInterface, 12 PhabricatorApplicationTransactionInterface, 13 PhabricatorMentionableInterface, 14 PhabricatorDestructibleInterface, 15 PhabricatorProjectInterface { 16 17 protected $title = ''; 18 protected $originalTitle; 19 protected $status; 20 21 protected $summary = ''; 22 protected $testPlan = ''; 23 24 protected $authorPHID; 25 protected $lastReviewerPHID; 26 27 protected $lineCount = 0; 28 protected $attached = array(); 29 30 protected $mailKey; 31 protected $branchName; 32 protected $arcanistProjectPHID; 33 protected $repositoryPHID; 34 protected $viewPolicy = PhabricatorPolicies::POLICY_USER; 35 protected $editPolicy = PhabricatorPolicies::POLICY_USER; 36 37 private $relationships = self::ATTACHABLE; 38 private $commits = self::ATTACHABLE; 39 private $activeDiff = self::ATTACHABLE; 40 private $diffIDs = self::ATTACHABLE; 41 private $hashes = self::ATTACHABLE; 42 private $repository = self::ATTACHABLE; 43 44 private $reviewerStatus = self::ATTACHABLE; 45 private $customFields = self::ATTACHABLE; 46 private $drafts = array(); 47 private $flags = array(); 48 49 const TABLE_COMMIT = 'differential_commit'; 50 51 const RELATION_REVIEWER = 'revw'; 52 const RELATION_SUBSCRIBED = 'subd'; 53 54 public static function initializeNewRevision(PhabricatorUser $actor) { 55 $app = id(new PhabricatorApplicationQuery()) 56 ->setViewer($actor) 57 ->withClasses(array('PhabricatorDifferentialApplication')) 58 ->executeOne(); 59 60 $view_policy = $app->getPolicy( 61 DifferentialDefaultViewCapability::CAPABILITY); 62 63 return id(new DifferentialRevision()) 64 ->setViewPolicy($view_policy) 65 ->setAuthorPHID($actor->getPHID()) 66 ->attachRelationships(array()) 67 ->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); 68 } 69 70 public function getConfiguration() { 71 return array( 72 self::CONFIG_AUX_PHID => true, 73 self::CONFIG_SERIALIZATION => array( 74 'attached' => self::SERIALIZATION_JSON, 75 'unsubscribed' => self::SERIALIZATION_JSON, 76 ), 77 self::CONFIG_COLUMN_SCHEMA => array( 78 'title' => 'text255', 79 'originalTitle' => 'text255', 80 'status' => 'text32', 81 'summary' => 'text', 82 'testPlan' => 'text', 83 'authorPHID' => 'phid?', 84 'lastReviewerPHID' => 'phid?', 85 'lineCount' => 'uint32?', 86 'mailKey' => 'bytes40', 87 'branchName' => 'text255?', 88 'arcanistProjectPHID' => 'phid?', 89 'repositoryPHID' => 'phid?', 90 ), 91 self::CONFIG_KEY_SCHEMA => array( 92 'key_phid' => null, 93 'phid' => array( 94 'columns' => array('phid'), 95 'unique' => true, 96 ), 97 'authorPHID' => array( 98 'columns' => array('authorPHID', 'status'), 99 ), 100 'repositoryPHID' => array( 101 'columns' => array('repositoryPHID'), 102 ), 103 ), 104 ) + parent::getConfiguration(); 105 } 106 107 public function getMonogram() { 108 $id = $this->getID(); 109 return "D{$id}"; 110 } 111 112 public function setTitle($title) { 113 $this->title = $title; 114 if (!$this->getID()) { 115 $this->originalTitle = $title; 116 } 117 return $this; 118 } 119 120 public function loadIDsByCommitPHIDs($phids) { 121 if (!$phids) { 122 return array(); 123 } 124 $revision_ids = queryfx_all( 125 $this->establishConnection('r'), 126 'SELECT * FROM %T WHERE commitPHID IN (%Ls)', 127 self::TABLE_COMMIT, 128 $phids); 129 return ipull($revision_ids, 'revisionID', 'commitPHID'); 130 } 131 132 public function loadCommitPHIDs() { 133 if (!$this->getID()) { 134 return ($this->commits = array()); 135 } 136 137 $commits = queryfx_all( 138 $this->establishConnection('r'), 139 'SELECT commitPHID FROM %T WHERE revisionID = %d', 140 self::TABLE_COMMIT, 141 $this->getID()); 142 $commits = ipull($commits, 'commitPHID'); 143 144 return ($this->commits = $commits); 145 } 146 147 public function getCommitPHIDs() { 148 return $this->assertAttached($this->commits); 149 } 150 151 public function getActiveDiff() { 152 // TODO: Because it's currently technically possible to create a revision 153 // without an associated diff, we allow an attached-but-null active diff. 154 // It would be good to get rid of this once we make diff-attaching 155 // transactional. 156 157 return $this->assertAttached($this->activeDiff); 158 } 159 160 public function attachActiveDiff($diff) { 161 $this->activeDiff = $diff; 162 return $this; 163 } 164 165 public function getDiffIDs() { 166 return $this->assertAttached($this->diffIDs); 167 } 168 169 public function attachDiffIDs(array $ids) { 170 rsort($ids); 171 $this->diffIDs = array_values($ids); 172 return $this; 173 } 174 175 public function attachCommitPHIDs(array $phids) { 176 $this->commits = array_values($phids); 177 return $this; 178 } 179 180 public function getAttachedPHIDs($type) { 181 return array_keys(idx($this->attached, $type, array())); 182 } 183 184 public function setAttachedPHIDs($type, array $phids) { 185 $this->attached[$type] = array_fill_keys($phids, array()); 186 return $this; 187 } 188 189 public function generatePHID() { 190 return PhabricatorPHID::generateNewPHID( 191 DifferentialRevisionPHIDType::TYPECONST); 192 } 193 194 public function loadActiveDiff() { 195 return id(new DifferentialDiff())->loadOneWhere( 196 'revisionID = %d ORDER BY id DESC LIMIT 1', 197 $this->getID()); 198 } 199 200 public function save() { 201 if (!$this->getMailKey()) { 202 $this->mailKey = Filesystem::readRandomCharacters(40); 203 } 204 return parent::save(); 205 } 206 207 public function loadRelationships() { 208 if (!$this->getID()) { 209 $this->relationships = array(); 210 return; 211 } 212 213 $data = array(); 214 215 $subscriber_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 216 $this->getPHID(), 217 PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER); 218 $subscriber_phids = array_reverse($subscriber_phids); 219 foreach ($subscriber_phids as $phid) { 220 $data[] = array( 221 'relation' => self::RELATION_SUBSCRIBED, 222 'objectPHID' => $phid, 223 'reasonPHID' => null, 224 ); 225 } 226 227 $reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 228 $this->getPHID(), 229 PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER); 230 $reviewer_phids = array_reverse($reviewer_phids); 231 foreach ($reviewer_phids as $phid) { 232 $data[] = array( 233 'relation' => self::RELATION_REVIEWER, 234 'objectPHID' => $phid, 235 'reasonPHID' => null, 236 ); 237 } 238 239 return $this->attachRelationships($data); 240 } 241 242 public function attachRelationships(array $relationships) { 243 $this->relationships = igroup($relationships, 'relation'); 244 return $this; 245 } 246 247 public function getReviewers() { 248 return $this->getRelatedPHIDs(self::RELATION_REVIEWER); 249 } 250 251 public function getCCPHIDs() { 252 return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED); 253 } 254 255 private function getRelatedPHIDs($relation) { 256 $this->assertAttached($this->relationships); 257 258 return ipull($this->getRawRelations($relation), 'objectPHID'); 259 } 260 261 public function getRawRelations($relation) { 262 return idx($this->relationships, $relation, array()); 263 } 264 265 public function getPrimaryReviewer() { 266 $reviewers = $this->getReviewers(); 267 $last = $this->lastReviewerPHID; 268 if (!$last || !in_array($last, $reviewers)) { 269 return head($this->getReviewers()); 270 } 271 return $last; 272 } 273 274 public function getHashes() { 275 return $this->assertAttached($this->hashes); 276 } 277 278 public function attachHashes(array $hashes) { 279 $this->hashes = $hashes; 280 return $this; 281 } 282 283 public function getCapabilities() { 284 return array( 285 PhabricatorPolicyCapability::CAN_VIEW, 286 PhabricatorPolicyCapability::CAN_EDIT, 287 ); 288 } 289 290 public function getPolicy($capability) { 291 switch ($capability) { 292 case PhabricatorPolicyCapability::CAN_VIEW: 293 return $this->getViewPolicy(); 294 case PhabricatorPolicyCapability::CAN_EDIT: 295 return $this->getEditPolicy(); 296 } 297 } 298 299 public function hasAutomaticCapability($capability, PhabricatorUser $user) { 300 // A revision's author (which effectively means "owner" after we added 301 // commandeering) can always view and edit it. 302 $author_phid = $this->getAuthorPHID(); 303 if ($author_phid) { 304 if ($user->getPHID() == $author_phid) { 305 return true; 306 } 307 } 308 309 return false; 310 } 311 312 public function describeAutomaticCapability($capability) { 313 $description = array( 314 pht('The owner of a revision can always view and edit it.'), 315 ); 316 317 switch ($capability) { 318 case PhabricatorPolicyCapability::CAN_VIEW: 319 $description[] = pht( 320 "A revision's reviewers can always view it."); 321 $description[] = pht( 322 'If a revision belongs to a repository, other users must be able '. 323 'to view the repository in order to view the revision.'); 324 break; 325 } 326 327 return $description; 328 } 329 330 public function getUsersToNotifyOfTokenGiven() { 331 return array( 332 $this->getAuthorPHID(), 333 ); 334 } 335 336 public function getReviewerStatus() { 337 return $this->assertAttached($this->reviewerStatus); 338 } 339 340 public function attachReviewerStatus(array $reviewers) { 341 assert_instances_of($reviewers, 'DifferentialReviewer'); 342 343 $this->reviewerStatus = $reviewers; 344 return $this; 345 } 346 347 public function getRepository() { 348 return $this->assertAttached($this->repository); 349 } 350 351 public function attachRepository(PhabricatorRepository $repository = null) { 352 $this->repository = $repository; 353 return $this; 354 } 355 356 public function isClosed() { 357 return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); 358 } 359 360 public function getFlag(PhabricatorUser $viewer) { 361 return $this->assertAttachedKey($this->flags, $viewer->getPHID()); 362 } 363 364 public function attachFlag( 365 PhabricatorUser $viewer, 366 PhabricatorFlag $flag = null) { 367 $this->flags[$viewer->getPHID()] = $flag; 368 return $this; 369 } 370 371 public function getDrafts(PhabricatorUser $viewer) { 372 return $this->assertAttachedKey($this->drafts, $viewer->getPHID()); 373 } 374 375 public function attachDrafts(PhabricatorUser $viewer, array $drafts) { 376 $this->drafts[$viewer->getPHID()] = $drafts; 377 return $this; 378 } 379 380 381 /* -( HarbormasterBuildableInterface )------------------------------------- */ 382 383 384 public function getHarbormasterBuildablePHID() { 385 return $this->loadActiveDiff()->getPHID(); 386 } 387 388 public function getHarbormasterContainerPHID() { 389 return $this->getPHID(); 390 } 391 392 public function getBuildVariables() { 393 return array(); 394 } 395 396 public function getAvailableBuildVariables() { 397 return array(); 398 } 399 400 401 /* -( PhabricatorSubscribableInterface )----------------------------------- */ 402 403 404 public function isAutomaticallySubscribed($phid) { 405 if ($phid == $this->getAuthorPHID()) { 406 return true; 407 } 408 409 // TODO: This only happens when adding or removing CCs, and is safe from a 410 // policy perspective, but the subscription pathway should have some 411 // opportunity to load this data properly. For now, this is the only case 412 // where implicit subscription is not an intrinsic property of the object. 413 if ($this->reviewerStatus == self::ATTACHABLE) { 414 $reviewers = id(new DifferentialRevisionQuery()) 415 ->setViewer(PhabricatorUser::getOmnipotentUser()) 416 ->withPHIDs(array($this->getPHID())) 417 ->needReviewerStatus(true) 418 ->executeOne() 419 ->getReviewerStatus(); 420 } else { 421 $reviewers = $this->getReviewerStatus(); 422 } 423 424 foreach ($reviewers as $reviewer) { 425 if ($reviewer->getReviewerPHID() == $phid) { 426 return true; 427 } 428 } 429 430 return false; 431 } 432 433 public function shouldShowSubscribersProperty() { 434 return true; 435 } 436 437 public function shouldAllowSubscription($phid) { 438 return true; 439 } 440 441 442 /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 443 444 445 public function getCustomFieldSpecificationForRole($role) { 446 return PhabricatorEnv::getEnvConfig('differential.fields'); 447 } 448 449 public function getCustomFieldBaseClass() { 450 return 'DifferentialCustomField'; 451 } 452 453 public function getCustomFields() { 454 return $this->assertAttached($this->customFields); 455 } 456 457 public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 458 $this->customFields = $fields; 459 return $this; 460 } 461 462 463 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 464 465 466 public function getApplicationTransactionEditor() { 467 return new DifferentialTransactionEditor(); 468 } 469 470 public function getApplicationTransactionObject() { 471 return $this; 472 } 473 474 public function getApplicationTransactionTemplate() { 475 return new DifferentialTransaction(); 476 } 477 478 479 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 480 481 482 public function destroyObjectPermanently( 483 PhabricatorDestructionEngine $engine) { 484 485 $this->openTransaction(); 486 $diffs = id(new DifferentialDiffQuery()) 487 ->setViewer(PhabricatorUser::getOmnipotentUser()) 488 ->withRevisionIDs(array($this->getID())) 489 ->execute(); 490 foreach ($diffs as $diff) { 491 $engine->destroyObject($diff); 492 } 493 494 $conn_w = $this->establishConnection('w'); 495 496 queryfx( 497 $conn_w, 498 'DELETE FROM %T WHERE revisionID = %d', 499 self::TABLE_COMMIT, 500 $this->getID()); 501 502 try { 503 $inlines = id(new DifferentialInlineCommentQuery()) 504 ->withRevisionIDs(array($this->getID())) 505 ->execute(); 506 foreach ($inlines as $inline) { 507 $inline->delete(); 508 } 509 } catch (PhabricatorEmptyQueryException $ex) { 510 // TODO: There's still some funky legacy wrapping going on here, and 511 // we might catch a raw query exception. 512 } 513 514 // we have to do paths a little differentally as they do not have 515 // an id or phid column for delete() to act on 516 $dummy_path = new DifferentialAffectedPath(); 517 queryfx( 518 $conn_w, 519 'DELETE FROM %T WHERE revisionID = %d', 520 $dummy_path->getTableName(), 521 $this->getID()); 522 523 $this->delete(); 524 $this->saveTransaction(); 525 } 526 527 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |