[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/differential/storage/ -> DifferentialRevision.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1