diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index d0cdcf1830..889b46d470 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -1,588 +1,597 @@ true, self::CONFIG_COLUMN_SCHEMA => array( 'revisionID' => 'id?', 'authorPHID' => 'phid?', 'repositoryPHID' => 'phid?', 'sourceMachine' => 'text255?', 'sourcePath' => 'text255?', 'sourceControlSystem' => 'text64?', 'sourceControlBaseRevision' => 'text255?', 'sourceControlPath' => 'text255?', 'lintStatus' => 'uint32', 'unitStatus' => 'uint32', 'lineCount' => 'uint32', 'branch' => 'text255?', 'bookmark' => 'text255?', 'repositoryUUID' => 'text64?', // T6203/NULLABILITY // These should be non-null; all diffs should have a creation method // and the description should just be empty. 'creationMethod' => 'text255?', 'description' => 'text255?', ), self::CONFIG_KEY_SCHEMA => array( 'revisionID' => array( 'columns' => array('revisionID'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( DifferentialDiffPHIDType::TYPECONST); } public function addUnsavedChangeset(DifferentialChangeset $changeset) { if ($this->changesets === null) { $this->changesets = array(); } $this->unsavedChangesets[] = $changeset; $this->changesets[] = $changeset; return $this; } public function attachChangesets(array $changesets) { assert_instances_of($changesets, 'DifferentialChangeset'); $this->changesets = $changesets; return $this; } public function getChangesets() { return $this->assertAttached($this->changesets); } public function loadChangesets() { if (!$this->getID()) { return array(); } $changesets = id(new DifferentialChangeset())->loadAllWhere( 'diffID = %d', $this->getID()); foreach ($changesets as $changeset) { $changeset->attachDiff($this); } return $changesets; } public function save() { $this->openTransaction(); $ret = parent::save(); foreach ($this->unsavedChangesets as $changeset) { $changeset->setDiffID($this->getID()); $changeset->save(); } $this->saveTransaction(); return $ret; } public static function initializeNewDiff(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorDifferentialApplication')) ->executeOne(); $view_policy = $app->getPolicy( DifferentialDefaultViewCapability::CAPABILITY); $diff = id(new DifferentialDiff()) ->setViewPolicy($view_policy); return $diff; } public static function newFromRawChanges( PhabricatorUser $actor, array $changes) { assert_instances_of($changes, 'ArcanistDiffChange'); $diff = self::initializeNewDiff($actor); return self::buildChangesetsFromRawChanges($diff, $changes); } public static function newEphemeralFromRawChanges(array $changes) { assert_instances_of($changes, 'ArcanistDiffChange'); $diff = id(new DifferentialDiff())->makeEphemeral(); return self::buildChangesetsFromRawChanges($diff, $changes); } private static function buildChangesetsFromRawChanges( DifferentialDiff $diff, array $changes) { // There may not be any changes; initialize the changesets list so that // we don't throw later when accessing it. $diff->attachChangesets(array()); $lines = 0; foreach ($changes as $change) { if ($change->getType() == ArcanistDiffChangeType::TYPE_MESSAGE) { // If a user pastes a diff into Differential which includes a commit // message (e.g., they ran `git show` to generate it), discard that // change when constructing a DifferentialDiff. continue; } $changeset = new DifferentialChangeset(); $add_lines = 0; $del_lines = 0; $first_line = PHP_INT_MAX; $hunks = $change->getHunks(); if ($hunks) { foreach ($hunks as $hunk) { $dhunk = new DifferentialModernHunk(); $dhunk->setOldOffset($hunk->getOldOffset()); $dhunk->setOldLen($hunk->getOldLength()); $dhunk->setNewOffset($hunk->getNewOffset()); $dhunk->setNewLen($hunk->getNewLength()); $dhunk->setChanges($hunk->getCorpus()); $changeset->addUnsavedHunk($dhunk); $add_lines += $hunk->getAddLines(); $del_lines += $hunk->getDelLines(); $added_lines = $hunk->getChangedLines('new'); if ($added_lines) { $first_line = min($first_line, head_key($added_lines)); } } $lines += $add_lines + $del_lines; } else { // This happens when you add empty files. $changeset->attachHunks(array()); } $metadata = $change->getAllMetadata(); if ($first_line != PHP_INT_MAX) { $metadata['line:first'] = $first_line; } $changeset->setOldFile($change->getOldPath()); $changeset->setFilename($change->getCurrentPath()); $changeset->setChangeType($change->getType()); $changeset->setFileType($change->getFileType()); $changeset->setMetadata($metadata); $changeset->setOldProperties($change->getOldProperties()); $changeset->setNewProperties($change->getNewProperties()); $changeset->setAwayPaths($change->getAwayPaths()); $changeset->setAddLines($add_lines); $changeset->setDelLines($del_lines); $diff->addUnsavedChangeset($changeset); } $diff->setLineCount($lines); $parser = new DifferentialChangesetParser(); $changesets = $parser->detectCopiedCode( $diff->getChangesets(), $min_width = 30, $min_lines = 3); $diff->attachChangesets($changesets); return $diff; } public function getDiffDict() { $dict = array( 'id' => $this->getID(), 'revisionID' => $this->getRevisionID(), 'dateCreated' => $this->getDateCreated(), 'dateModified' => $this->getDateModified(), 'sourceControlBaseRevision' => $this->getSourceControlBaseRevision(), 'sourceControlPath' => $this->getSourceControlPath(), 'sourceControlSystem' => $this->getSourceControlSystem(), 'branch' => $this->getBranch(), 'bookmark' => $this->getBookmark(), 'creationMethod' => $this->getCreationMethod(), 'description' => $this->getDescription(), 'unitStatus' => $this->getUnitStatus(), 'lintStatus' => $this->getLintStatus(), 'changes' => array(), 'properties' => array(), ); $dict['changes'] = $this->buildChangesList(); return $dict + $this->getDiffAuthorshipDict(); } public function getDiffAuthorshipDict() { $dict = array(); $properties = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $this->getID()); foreach ($properties as $property) { $dict['properties'][$property->getName()] = $property->getData(); if ($property->getName() == 'local:commits') { foreach ($property->getData() as $commit) { $dict['authorName'] = $commit['author']; $dict['authorEmail'] = idx($commit, 'authorEmail'); break; } } } return $dict; } public function buildChangesList() { $changes = array(); foreach ($this->getChangesets() as $changeset) { $hunks = array(); foreach ($changeset->getHunks() as $hunk) { $hunks[] = array( 'oldOffset' => $hunk->getOldOffset(), 'newOffset' => $hunk->getNewOffset(), 'oldLength' => $hunk->getOldLen(), 'newLength' => $hunk->getNewLen(), 'addLines' => null, 'delLines' => null, 'isMissingOldNewline' => null, 'isMissingNewNewline' => null, 'corpus' => $hunk->getChanges(), ); } $change = array( 'id' => $changeset->getID(), 'metadata' => $changeset->getMetadata(), 'oldPath' => $changeset->getOldFile(), 'currentPath' => $changeset->getFilename(), 'awayPaths' => $changeset->getAwayPaths(), 'oldProperties' => $changeset->getOldProperties(), 'newProperties' => $changeset->getNewProperties(), 'type' => $changeset->getChangeType(), 'fileType' => $changeset->getFileType(), 'commitHash' => null, 'addLines' => $changeset->getAddLines(), 'delLines' => $changeset->getDelLines(), 'hunks' => $hunks, ); $changes[] = $change; } return $changes; } public function hasRevision() { return $this->revision !== self::ATTACHABLE; } public function getRevision() { return $this->assertAttached($this->revision); } public function attachRevision(DifferentialRevision $revision = null) { $this->revision = $revision; return $this; } public function attachProperty($key, $value) { $this->properties[$key] = $value; return $this; } public function getProperty($key) { return $this->assertAttachedKey($this->properties, $key); } public function attachBuildable(HarbormasterBuildable $buildable = null) { $this->buildable = $buildable; return $this; } public function getBuildable() { return $this->assertAttached($this->buildable); } public function getBuildTargetPHIDs() { $buildable = $this->getBuildable(); if (!$buildable) { return array(); } $target_phids = array(); foreach ($buildable->getBuilds() as $build) { foreach ($build->getBuildTargets() as $target) { $target_phids[] = $target->getPHID(); } } return $target_phids; } public function loadCoverageMap(PhabricatorUser $viewer) { $target_phids = $this->getBuildTargetPHIDs(); if (!$target_phids) { return array(); } $unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere( 'buildTargetPHID IN (%Ls)', $target_phids); $map = array(); foreach ($unit as $message) { $coverage = $message->getProperty('coverage', array()); foreach ($coverage as $path => $coverage_data) { $map[$path][] = $coverage_data; } } foreach ($map as $path => $coverage_items) { $map[$path] = ArcanistUnitTestResult::mergeCoverage($coverage_items); } return $map; } public function getURI() { $id = $this->getID(); return "/differential/diff/{$id}/"; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { if ($this->hasRevision()) { return $this->getRevision()->getPolicy($capability); } return $this->viewPolicy; } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { if ($this->hasRevision()) { return $this->getRevision()->hasAutomaticCapability($capability, $viewer); } return ($this->getAuthorPHID() == $viewer->getPhid()); } public function describeAutomaticCapability($capability) { if ($this->hasRevision()) { return pht( 'This diff is attached to a revision, and inherits its policies.'); } return pht('The author of a diff can see it.'); } /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + $container_phid = $this->getHarbormasterContainerPHID(); + if ($container_phid) { + return $container_phid; + } + + return $this->getHarbormasterBuildablePHID(); + } + public function getHarbormasterBuildablePHID() { return $this->getPHID(); } public function getHarbormasterContainerPHID() { if ($this->getRevisionID()) { $revision = id(new DifferentialRevision())->load($this->getRevisionID()); if ($revision) { return $revision->getPHID(); } } return null; } public function getBuildVariables() { $results = array(); $results['buildable.diff'] = $this->getID(); if ($this->revisionID) { $revision = $this->getRevision(); $results['buildable.revision'] = $revision->getID(); $repo = $revision->getRepository(); if ($repo) { $results['repository.callsign'] = $repo->getCallsign(); $results['repository.phid'] = $repo->getPHID(); $results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.uri'] = $repo->getPublicCloneURI(); $results['repository.staging.uri'] = $repo->getStagingURI(); $results['repository.staging.ref'] = $this->getStagingRef(); } } return $results; } public function getAvailableBuildVariables() { return array( 'buildable.diff' => pht('The differential diff ID, if applicable.'), 'buildable.revision' => pht('The differential revision ID, if applicable.'), 'repository.callsign' => pht('The callsign of the repository in Phabricator.'), 'repository.phid' => pht('The PHID of the repository in Phabricator.'), 'repository.vcs' => pht('The version control system, either "svn", "hg" or "git".'), 'repository.uri' => pht('The URI to clone or checkout the repository from.'), 'repository.staging.uri' => pht('The URI of the staging repository.'), 'repository.staging.ref' => pht('The ref name for this change in the staging repository.'), ); } public function getStagingRef() { // TODO: We're just hoping to get lucky. Instead, `arc` should store // where it sent changes and we should only provide staging details // if we reasonably believe they are accurate. return 'refs/tags/phabricator/diff/'.$this->getID(); } public function loadTargetBranch() { // TODO: This is sketchy, but just eat the query cost until this can get // cleaned up. // For now, we're only returning a target if there's exactly one and it's // a branch, since we don't support landing to more esoteric targets like // tags yet. $property = id(new DifferentialDiffProperty())->loadOneWhere( 'diffID = %d AND name = %s', $this->getID(), 'arc:onto'); if (!$property) { return null; } $data = $property->getData(); if (!$data) { return null; } if (!is_array($data)) { return null; } if (count($data) != 1) { return null; } $onto = head($data); if (!is_array($onto)) { return null; } $type = idx($onto, 'type'); if ($type != 'branch') { return null; } return idx($onto, 'name'); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new DifferentialDiffEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new DifferentialDiffTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); foreach ($this->loadChangesets() as $changeset) { $changeset->delete(); } $properties = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d', $this->getID()); foreach ($properties as $prop) { $prop->delete(); } $this->saveTransaction(); } } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index ff72b46de7..e67208c53a 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -1,638 +1,642 @@ setViewer($actor) ->withClasses(array('PhabricatorDifferentialApplication')) ->executeOne(); $view_policy = $app->getPolicy( DifferentialDefaultViewCapability::CAPABILITY); return id(new DifferentialRevision()) ->setViewPolicy($view_policy) ->setAuthorPHID($actor->getPHID()) ->attachRelationships(array()) ->attachRepository(null) ->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'attached' => self::SERIALIZATION_JSON, 'unsubscribed' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', 'originalTitle' => 'text255', 'status' => 'text32', 'summary' => 'text', 'testPlan' => 'text', 'authorPHID' => 'phid?', 'lastReviewerPHID' => 'phid?', 'lineCount' => 'uint32?', 'mailKey' => 'bytes40', 'branchName' => 'text255?', 'repositoryPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'authorPHID' => array( 'columns' => array('authorPHID', 'status'), ), 'repositoryPHID' => array( 'columns' => array('repositoryPHID'), ), // If you (or a project you are a member of) is reviewing a significant // fraction of the revisions on an install, the result set of open // revisions may be smaller than the result set of revisions where you // are a reviewer. In these cases, this key is better than keys on the // edge table. 'key_status' => array( 'columns' => array('status', 'phid'), ), ), ) + parent::getConfiguration(); } public function getMonogram() { $id = $this->getID(); return "D{$id}"; } public function setTitle($title) { $this->title = $title; if (!$this->getID()) { $this->originalTitle = $title; } return $this; } public function loadIDsByCommitPHIDs($phids) { if (!$phids) { return array(); } $revision_ids = queryfx_all( $this->establishConnection('r'), 'SELECT * FROM %T WHERE commitPHID IN (%Ls)', self::TABLE_COMMIT, $phids); return ipull($revision_ids, 'revisionID', 'commitPHID'); } public function loadCommitPHIDs() { if (!$this->getID()) { return ($this->commits = array()); } $commits = queryfx_all( $this->establishConnection('r'), 'SELECT commitPHID FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID()); $commits = ipull($commits, 'commitPHID'); return ($this->commits = $commits); } public function getCommitPHIDs() { return $this->assertAttached($this->commits); } public function getActiveDiff() { // TODO: Because it's currently technically possible to create a revision // without an associated diff, we allow an attached-but-null active diff. // It would be good to get rid of this once we make diff-attaching // transactional. return $this->assertAttached($this->activeDiff); } public function attachActiveDiff($diff) { $this->activeDiff = $diff; return $this; } public function getDiffIDs() { return $this->assertAttached($this->diffIDs); } public function attachDiffIDs(array $ids) { rsort($ids); $this->diffIDs = array_values($ids); return $this; } public function attachCommitPHIDs(array $phids) { $this->commits = array_values($phids); return $this; } public function getAttachedPHIDs($type) { return array_keys(idx($this->attached, $type, array())); } public function setAttachedPHIDs($type, array $phids) { $this->attached[$type] = array_fill_keys($phids, array()); return $this; } public function generatePHID() { return PhabricatorPHID::generateNewPHID( DifferentialRevisionPHIDType::TYPECONST); } public function loadActiveDiff() { return id(new DifferentialDiff())->loadOneWhere( 'revisionID = %d ORDER BY id DESC LIMIT 1', $this->getID()); } public function save() { if (!$this->getMailKey()) { $this->mailKey = Filesystem::readRandomCharacters(40); } return parent::save(); } public function loadRelationships() { if (!$this->getID()) { $this->relationships = array(); return; } $data = array(); $subscriber_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getPHID(), PhabricatorObjectHasSubscriberEdgeType::EDGECONST); $subscriber_phids = array_reverse($subscriber_phids); foreach ($subscriber_phids as $phid) { $data[] = array( 'relation' => self::RELATION_SUBSCRIBED, 'objectPHID' => $phid, 'reasonPHID' => null, ); } $reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $this->getPHID(), DifferentialRevisionHasReviewerEdgeType::EDGECONST); $reviewer_phids = array_reverse($reviewer_phids); foreach ($reviewer_phids as $phid) { $data[] = array( 'relation' => self::RELATION_REVIEWER, 'objectPHID' => $phid, 'reasonPHID' => null, ); } return $this->attachRelationships($data); } public function attachRelationships(array $relationships) { $this->relationships = igroup($relationships, 'relation'); return $this; } public function getReviewers() { return $this->getRelatedPHIDs(self::RELATION_REVIEWER); } public function getCCPHIDs() { return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED); } private function getRelatedPHIDs($relation) { $this->assertAttached($this->relationships); return ipull($this->getRawRelations($relation), 'objectPHID'); } public function getRawRelations($relation) { return idx($this->relationships, $relation, array()); } public function getPrimaryReviewer() { $reviewers = $this->getReviewers(); $last = $this->lastReviewerPHID; if (!$last || !in_array($last, $reviewers)) { return head($this->getReviewers()); } return $last; } public function getHashes() { return $this->assertAttached($this->hashes); } public function attachHashes(array $hashes) { $this->hashes = $hashes; return $this; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { // A revision's author (which effectively means "owner" after we added // commandeering) can always view and edit it. $author_phid = $this->getAuthorPHID(); if ($author_phid) { if ($user->getPHID() == $author_phid) { return true; } } return false; } public function describeAutomaticCapability($capability) { $description = array( pht('The owner of a revision can always view and edit it.'), ); switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: $description[] = pht("A revision's reviewers can always view it."); $description[] = pht( 'If a revision belongs to a repository, other users must be able '. 'to view the repository in order to view the revision.'); break; } return $description; } /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ public function getExtendedPolicy($capability, PhabricatorUser $viewer) { $extended = array(); switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: // NOTE: In Differential, an automatic capability on a revision (being // an author) is sufficient to view it, even if you can not see the // repository the revision belongs to. We can bail out early in this // case. if ($this->hasAutomaticCapability($capability, $viewer)) { break; } $repository_phid = $this->getRepositoryPHID(); $repository = $this->getRepository(); // Try to use the object if we have it, since it will save us some // data fetching later on. In some cases, we might not have it. $repository_ref = nonempty($repository, $repository_phid); if ($repository_ref) { $extended[] = array( $repository_ref, PhabricatorPolicyCapability::CAN_VIEW, ); } break; } return $extended; } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getAuthorPHID(), ); } public function getReviewerStatus() { return $this->assertAttached($this->reviewerStatus); } public function attachReviewerStatus(array $reviewers) { assert_instances_of($reviewers, 'DifferentialReviewer'); $this->reviewerStatus = $reviewers; return $this; } public function getRepository() { return $this->assertAttached($this->repository); } public function attachRepository(PhabricatorRepository $repository = null) { $this->repository = $repository; return $this; } public function isClosed() { return DifferentialRevisionStatus::isClosedStatus($this->getStatus()); } public function getFlag(PhabricatorUser $viewer) { return $this->assertAttachedKey($this->flags, $viewer->getPHID()); } public function attachFlag( PhabricatorUser $viewer, PhabricatorFlag $flag = null) { $this->flags[$viewer->getPHID()] = $flag; return $this; } public function getDrafts(PhabricatorUser $viewer) { return $this->assertAttachedKey($this->drafts, $viewer->getPHID()); } public function attachDrafts(PhabricatorUser $viewer, array $drafts) { $this->drafts[$viewer->getPHID()] = $drafts; return $this; } /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getHarbormasterContainerPHID(); + } + public function getHarbormasterBuildablePHID() { return $this->loadActiveDiff()->getPHID(); } public function getHarbormasterContainerPHID() { return $this->getPHID(); } public function getBuildVariables() { return array(); } public function getAvailableBuildVariables() { return array(); } /* -( PhabricatorSubscribableInterface )----------------------------------- */ public function isAutomaticallySubscribed($phid) { if ($phid == $this->getAuthorPHID()) { return true; } // TODO: This only happens when adding or removing CCs, and is safe from a // policy perspective, but the subscription pathway should have some // opportunity to load this data properly. For now, this is the only case // where implicit subscription is not an intrinsic property of the object. if ($this->reviewerStatus == self::ATTACHABLE) { $reviewers = id(new DifferentialRevisionQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($this->getPHID())) ->needReviewerStatus(true) ->executeOne() ->getReviewerStatus(); } else { $reviewers = $this->getReviewerStatus(); } foreach ($reviewers as $reviewer) { if ($reviewer->getReviewerPHID() == $phid) { return true; } } return false; } public function shouldShowSubscribersProperty() { return true; } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return PhabricatorEnv::getEnvConfig('differential.fields'); } public function getCustomFieldBaseClass() { return 'DifferentialCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new DifferentialTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new DifferentialTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { $viewer = $request->getViewer(); $render_data = $timeline->getRenderData(); $left = $request->getInt('left', idx($render_data, 'left')); $right = $request->getInt('right', idx($render_data, 'right')); $diffs = id(new DifferentialDiffQuery()) ->setViewer($request->getUser()) ->withIDs(array($left, $right)) ->execute(); $diffs = mpull($diffs, null, 'getID'); $left_diff = $diffs[$left]; $right_diff = $diffs[$right]; $old_ids = $request->getStr('old', idx($render_data, 'old')); $new_ids = $request->getStr('new', idx($render_data, 'new')); $old_ids = array_filter(explode(',', $old_ids)); $new_ids = array_filter(explode(',', $new_ids)); $type_inline = DifferentialTransaction::TYPE_INLINE; $changeset_ids = array_merge($old_ids, $new_ids); $inlines = array(); foreach ($timeline->getTransactions() as $xaction) { if ($xaction->getTransactionType() == $type_inline) { $inlines[] = $xaction->getComment(); $changeset_ids[] = $xaction->getComment()->getChangesetID(); } } if ($changeset_ids) { $changesets = id(new DifferentialChangesetQuery()) ->setViewer($request->getUser()) ->withIDs($changeset_ids) ->execute(); $changesets = mpull($changesets, null, 'getID'); } else { $changesets = array(); } foreach ($inlines as $key => $inline) { $inlines[$key] = DifferentialInlineComment::newFromModernComment( $inline); } $query = id(new DifferentialInlineCommentQuery()) ->needHidden(true) ->setViewer($viewer); // NOTE: This is a bit sketchy: this method adjusts the inlines as a // side effect, which means it will ultimately adjust the transaction // comments and affect timeline rendering. $query->adjustInlinesForChangesets( $inlines, array_select_keys($changesets, $old_ids), array_select_keys($changesets, $new_ids), $this); return $timeline ->setChangesets($changesets) ->setRevision($this) ->setLeftDiff($left_diff) ->setRightDiff($right_diff); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $diffs = id(new DifferentialDiffQuery()) ->setViewer($engine->getViewer()) ->withRevisionIDs(array($this->getID())) ->execute(); foreach ($diffs as $diff) { $engine->destroyObject($diff); } $conn_w = $this->establishConnection('w'); queryfx( $conn_w, 'DELETE FROM %T WHERE revisionID = %d', self::TABLE_COMMIT, $this->getID()); // we have to do paths a little differentally as they do not have // an id or phid column for delete() to act on $dummy_path = new DifferentialAffectedPath(); queryfx( $conn_w, 'DELETE FROM %T WHERE revisionID = %d', $dummy_path->getTableName(), $this->getID()); $this->delete(); $this->saveTransaction(); } /* -( PhabricatorFulltextInterface )--------------------------------------- */ public function newFulltextEngine() { return new DifferentialRevisionFulltextEngine(); } } diff --git a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php index d2b2d332aa..0e852b72a3 100644 --- a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php +++ b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php @@ -1,11 +1,24 @@ readPHIDsFromRequest( $request, 'revisions', array( DifferentialRevisionPHIDType::TYPECONST, )); $repositories = $this->readPHIDsFromRequest( $request, 'repositories', array( PhabricatorRepositoryRepositoryPHIDType::TYPECONST, )); $container_phids = array_merge($revisions, $repositories); $saved->setParameter('containerPHIDs', $container_phids); $commits = $this->readPHIDsFromRequest( $request, 'commits', array( PhabricatorRepositoryCommitPHIDType::TYPECONST, )); $diffs = $this->readListFromRequest($request, 'diffs'); if ($diffs) { $diffs = id(new DifferentialDiffQuery()) ->setViewer($this->requireViewer()) ->withIDs($diffs) ->execute(); $diffs = mpull($diffs, 'getPHID', 'getPHID'); } $buildable_phids = array_merge($commits, $diffs); $saved->setParameter('buildablePHIDs', $buildable_phids); $saved->setParameter( 'manual', $this->readBoolFromRequest($request, 'manual')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new HarbormasterBuildableQuery()); $container_phids = $saved->getParameter('containerPHIDs', array()); if ($container_phids) { $query->withContainerPHIDs($container_phids); } $buildable_phids = $saved->getParameter('buildablePHIDs', array()); if ($buildable_phids) { $query->withBuildablePHIDs($buildable_phids); } $manual = $saved->getParameter('manual'); if ($manual !== null) { $query->withManualBuildables($manual); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $container_phids = $saved_query->getParameter('containerPHIDs', array()); $buildable_phids = $saved_query->getParameter('buildablePHIDs', array()); $all_phids = array_merge($container_phids, $buildable_phids); $revision_names = array(); $diff_names = array(); $repository_names = array(); $commit_names = array(); if ($all_phids) { $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($all_phids) ->execute(); foreach ($all_phids as $phid) { $object = idx($objects, $phid); if (!$object) { continue; } if ($object instanceof DifferentialRevision) { $revision_names[] = 'D'.$object->getID(); } else if ($object instanceof DifferentialDiff) { $diff_names[] = $object->getID(); } else if ($object instanceof PhabricatorRepository) { $repository_names[] = $object->getMonogram(); } else if ($object instanceof PhabricatorRepositoryCommit) { $repository = $object->getRepository(); $commit_names[] = $repository->formatCommitName( $object->getCommitIdentifier()); } } } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Differential Revisions')) ->setName('revisions') ->setValue(implode(', ', $revision_names))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Differential Diffs')) ->setName('diffs') ->setValue(implode(', ', $diff_names))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Repositories')) ->setName('repositories') ->setValue(implode(', ', $repository_names))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Commits')) ->setName('commits') ->setValue(implode(', ', $commit_names))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Origin')) ->setName('manual') ->setValue($this->getBoolFromQuery($saved_query, 'manual')) ->setOptions( array( '' => pht('(All Origins)'), 'true' => pht('Manual Buildables'), 'false' => pht('Automatic Buildables'), ))); } protected function getURI($path) { return '/harbormaster/'.$path; } protected function getBuiltinQueryNames() { return array( 'all' => pht('All Buildables'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $buildables, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($buildables, 'HarbormasterBuildable'); $viewer = $this->requireViewer(); $phids = array(); foreach ($buildables as $buildable) { + $phids[] = $buildable->getBuildableObject() + ->getHarbormasterBuildableDisplayPHID(); + $phids[] = $buildable->getContainerPHID(); $phids[] = $buildable->getBuildablePHID(); } $handles = $viewer->loadHandles($phids); $list = new PHUIObjectItemListView(); foreach ($buildables as $buildable) { $id = $buildable->getID(); + $display_phid = $buildable->getBuildableObject() + ->getHarbormasterBuildableDisplayPHID(); + $container_phid = $buildable->getContainerPHID(); $buildable_phid = $buildable->getBuildablePHID(); $item = id(new PHUIObjectItemView()) - ->setHeader(pht('Buildable %d', $buildable->getID())); + ->setObjectName(pht('Buildable %d', $buildable->getID())); + + if ($display_phid) { + $handle = $handles[$display_phid]; + $item->setHeader($handle->getFullName()); + } - if ($container_phid) { + if ($container_phid && ($container_phid != $display_phid)) { $handle = $handles[$container_phid]; $item->addAttribute($handle->getName()); } - if ($buildable_phid) { + if ($buildable_phid && ($buildable_phid != $display_phid)) { $handle = $handles[$buildable_phid]; $item->addAttribute($handle->getFullName()); } $item->setHref($buildable->getURI()); if ($buildable->getIsManualBuildable()) { $item->addIcon('fa-wrench grey', pht('Manual')); } - $item->setStatusIcon('fa-wrench '. - HarbormasterBuildable::getBuildableStatusColor( - $buildable->getBuildableStatus())); - $item->addByline(HarbormasterBuildable::getBuildableStatusName( - $buildable->getBuildableStatus())); + $status = $buildable->getBuildableStatus(); - $list->addItem($item); + $status_icon = HarbormasterBuildable::getBuildableStatusIcon($status); + $status_color = HarbormasterBuildable::getBuildableStatusColor($status); + $status_label = HarbormasterBuildable::getBuildableStatusName($status); + + $item->setStatusIcon("{$status_icon} {$status_color}", $status_label); + $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No buildables found.')); return $result; } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 7ee6981477..f02fff270b 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -1,325 +1,329 @@ setIsManualBuildable(0) ->setBuildableStatus(self::STATUS_BUILDING); } public function getMonogram() { return 'B'.$this->getID(); } public function getURI() { return '/'.$this->getMonogram(); } /** * Returns an existing buildable for the object's PHID or creates a * new buildable implicitly if needed. */ public static function createOrLoadExisting( PhabricatorUser $actor, $buildable_object_phid, $container_object_phid) { $buildable = id(new HarbormasterBuildableQuery()) ->setViewer($actor) ->withBuildablePHIDs(array($buildable_object_phid)) ->withManualBuildables(false) ->setLimit(1) ->executeOne(); if ($buildable) { return $buildable; } $buildable = self::initializeNewBuildable($actor) ->setBuildablePHID($buildable_object_phid) ->setContainerPHID($container_object_phid); $buildable->save(); return $buildable; } /** * Start builds for a given buildable. * * @param phid PHID of the object to build. * @param phid Container PHID for the buildable. * @param list List of builds to perform. * @return void */ public static function applyBuildPlans( $phid, $container_phid, array $requests) { assert_instances_of($requests, 'HarbormasterBuildRequest'); if (!$requests) { return; } // Skip all of this logic if the Harbormaster application // isn't currently installed. $harbormaster_app = 'PhabricatorHarbormasterApplication'; if (!PhabricatorApplication::isClassInstalled($harbormaster_app)) { return; } $viewer = PhabricatorUser::getOmnipotentUser(); $buildable = self::createOrLoadExisting( $viewer, $phid, $container_phid); $plan_phids = mpull($requests, 'getBuildPlanPHID'); $plans = id(new HarbormasterBuildPlanQuery()) ->setViewer($viewer) ->withPHIDs($plan_phids) ->execute(); $plans = mpull($plans, null, 'getPHID'); foreach ($requests as $request) { $plan_phid = $request->getBuildPlanPHID(); $plan = idx($plans, $plan_phid); if (!$plan) { throw new Exception( pht( 'Failed to load build plan ("%s").', $plan_phid)); } if ($plan->isDisabled()) { // TODO: This should be communicated more clearly -- maybe we should // create the build but set the status to "disabled" or "derelict". continue; } $parameters = $request->getBuildParameters(); $buildable->applyPlan($plan, $parameters, $request->getInitiatorPHID()); } } public function applyPlan( HarbormasterBuildPlan $plan, array $parameters, $initiator_phid) { $viewer = PhabricatorUser::getOmnipotentUser(); $build = HarbormasterBuild::initializeNewBuild($viewer) ->setBuildablePHID($this->getPHID()) ->setBuildPlanPHID($plan->getPHID()) ->setBuildParameters($parameters) ->setBuildStatus(HarbormasterBuild::STATUS_PENDING); if ($initiator_phid) { $build->setInitiatorPHID($initiator_phid); } $auto_key = $plan->getPlanAutoKey(); if ($auto_key) { $build->setPlanAutoKey($auto_key); } $build->save(); PhabricatorWorker::scheduleTask( 'HarbormasterBuildWorker', array( 'buildID' => $build->getID(), ), array( 'objectPHID' => $build->getPHID(), )); return $build; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'containerPHID' => 'phid?', 'buildableStatus' => 'text32', 'isManualBuildable' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_buildable' => array( 'columns' => array('buildablePHID'), ), 'key_container' => array( 'columns' => array('containerPHID'), ), 'key_manual' => array( 'columns' => array('isManualBuildable'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildablePHIDType::TYPECONST); } public function attachBuildableObject($buildable_object) { $this->buildableObject = $buildable_object; return $this; } public function getBuildableObject() { return $this->assertAttached($this->buildableObject); } public function attachContainerObject($container_object) { $this->containerObject = $container_object; return $this; } public function getContainerObject() { return $this->assertAttached($this->containerObject); } public function attachBuilds(array $builds) { assert_instances_of($builds, 'HarbormasterBuild'); $this->builds = $builds; return $this; } public function getBuilds() { return $this->assertAttached($this->builds); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new HarbormasterBuildableTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new HarbormasterBuildableTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBuildableObject()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildableObject()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('A buildable inherits policies from the underlying object.'); } /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getBuildableObject()->getHarbormasterBuildableDisplayPHID(); + } + public function getHarbormasterBuildablePHID() { // NOTE: This is essentially just for convenience, as it allows you create // a copy of a buildable by specifying `B123` without bothering to go // look up the underlying object. return $this->getBuildablePHID(); } public function getHarbormasterContainerPHID() { return $this->getContainerPHID(); } public function getBuildVariables() { return array(); } public function getAvailableBuildVariables() { return array(); } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 348b94d021..9fd218db9a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -1,496 +1,500 @@ repository = $repository; return $this; } public function getRepository($assert_attached = true) { if ($assert_attached) { return $this->assertAttached($this->repository); } return $this->repository; } public function isPartiallyImported($mask) { return (($mask & $this->getImportStatus()) == $mask); } public function isImported() { return $this->isPartiallyImported(self::IMPORTED_ALL); } public function writeImportStatusFlag($flag) { queryfx( $this->establishConnection('w'), 'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d', $this->getTableName(), $flag, $this->getID()); $this->setImportStatus($this->getImportStatus() | $flag); return $this; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_TIMESTAMPS => false, self::CONFIG_COLUMN_SCHEMA => array( 'commitIdentifier' => 'text40', 'mailKey' => 'bytes20', 'authorPHID' => 'phid?', 'auditStatus' => 'uint32', 'summary' => 'text80', 'importStatus' => 'uint32', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'repositoryID' => array( 'columns' => array('repositoryID', 'importStatus'), ), 'authorPHID' => array( 'columns' => array('authorPHID', 'auditStatus', 'epoch'), ), 'repositoryID_2' => array( 'columns' => array('repositoryID', 'epoch'), ), 'key_commit_identity' => array( 'columns' => array('commitIdentifier', 'repositoryID'), 'unique' => true, ), 'key_epoch' => array( 'columns' => array('epoch'), ), 'key_author' => array( 'columns' => array('authorPHID', 'epoch'), ), ), self::CONFIG_NO_MUTATE => array( 'importStatus', ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorRepositoryCommitPHIDType::TYPECONST); } public function loadCommitData() { if (!$this->getID()) { return null; } return id(new PhabricatorRepositoryCommitData())->loadOneWhere( 'commitID = %d', $this->getID()); } public function attachCommitData( PhabricatorRepositoryCommitData $data = null) { $this->commitData = $data; return $this; } public function getCommitData() { return $this->assertAttached($this->commitData); } public function attachAudits(array $audits) { assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest'); $this->audits = $audits; return $this; } public function getAudits() { return $this->assertAttached($this->audits); } public function getAuthorityAudits( PhabricatorUser $user, array $authority_phids) { $authority = array_fill_keys($authority_phids, true); $audits = $this->getAudits(); $authority_audits = array(); foreach ($audits as $audit) { $has_authority = !empty($authority[$audit->getAuditorPHID()]); if ($has_authority) { $commit_author = $this->getAuthorPHID(); // You don't have authority over package and project audits on your // own commits. $auditor_is_user = ($audit->getAuditorPHID() == $user->getPHID()); $user_is_author = ($commit_author == $user->getPHID()); if ($auditor_is_user || !$user_is_author) { $authority_audits[$audit->getID()] = $audit; } } } return $authority_audits; } public function save() { if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } return parent::save(); } public function delete() { $data = $this->loadCommitData(); $audits = id(new PhabricatorRepositoryAuditRequest()) ->loadAllWhere('commitPHID = %s', $this->getPHID()); $this->openTransaction(); if ($data) { $data->delete(); } foreach ($audits as $audit) { $audit->delete(); } $result = parent::delete(); $this->saveTransaction(); return $result; } public function getDateCreated() { // This is primarily to make analysis of commits with the Fact engine work. return $this->getEpoch(); } public function getURI() { return '/'.$this->getMonogram(); } /** * Synchronize a commit's overall audit status with the individual audit * triggers. */ public function updateAuditStatus(array $requests) { assert_instances_of($requests, 'PhabricatorRepositoryAuditRequest'); $any_concern = false; $any_accept = false; $any_need = false; foreach ($requests as $request) { switch ($request->getAuditStatus()) { case PhabricatorAuditStatusConstants::AUDIT_REQUIRED: $any_need = true; break; case PhabricatorAuditStatusConstants::ACCEPTED: $any_accept = true; break; case PhabricatorAuditStatusConstants::CONCERNED: $any_concern = true; break; } } if ($any_concern) { $status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; } else if ($any_accept) { if ($any_need) { $status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED; } else { $status = PhabricatorAuditCommitStatusConstants::FULLY_AUDITED; } } else if ($any_need) { $status = PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT; } else { $status = PhabricatorAuditCommitStatusConstants::NONE; } return $this->setAuditStatus($status); } public function getMonogram() { $repository = $this->getRepository(); $callsign = $repository->getCallsign(); $identifier = $this->getCommitIdentifier(); if ($callsign !== null) { return "r{$callsign}{$identifier}"; } else { $id = $repository->getID(); return "R{$id}:{$identifier}"; } } public function getDisplayName() { $repository = $this->getRepository(); $identifier = $this->getCommitIdentifier(); return $repository->formatCommitName($identifier); } /** * Return a local display name for use in the context of the containing * repository. * * In Git and Mercurial, this returns only a short hash, like "abcdef012345". * See @{method:getDisplayName} for a short name that always includes * repository context. * * @return string Short human-readable name for use inside a repository. */ public function getLocalName() { $repository = $this->getRepository(); $identifier = $this->getCommitIdentifier(); return $repository->formatCommitName($identifier, $local = true); } public function renderAuthorLink($handles) { $author_phid = $this->getAuthorPHID(); if ($author_phid && isset($handles[$author_phid])) { return $handles[$author_phid]->renderLink(); } return $this->renderAuthorShortName($handles); } public function renderAuthorShortName($handles) { $author_phid = $this->getAuthorPHID(); if ($author_phid && isset($handles[$author_phid])) { return $handles[$author_phid]->getName(); } $data = $this->getCommitData(); $name = $data->getAuthorName(); $parsed = new PhutilEmailAddress($name); return nonempty($parsed->getDisplayName(), $parsed->getAddress()); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getRepository()->getPolicy($capability); case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_USER; } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getRepository()->hasAutomaticCapability($capability, $viewer); } public function describeAutomaticCapability($capability) { return pht( 'Commits inherit the policies of the repository they belong to.'); } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getAuthorPHID(), ); } /* -( Stuff for serialization )---------------------------------------------- */ /** * NOTE: this is not a complete serialization; only the 'protected' fields are * involved. This is due to ease of (ab)using the Lisk abstraction to get this * done, as well as complexity of the other fields. */ public function toDictionary() { return array( 'repositoryID' => $this->getRepositoryID(), 'phid' => $this->getPHID(), 'commitIdentifier' => $this->getCommitIdentifier(), 'epoch' => $this->getEpoch(), 'mailKey' => $this->getMailKey(), 'authorPHID' => $this->getAuthorPHID(), 'auditStatus' => $this->getAuditStatus(), 'summary' => $this->getSummary(), 'importStatus' => $this->getImportStatus(), ); } public static function newFromDictionary(array $dict) { return id(new PhabricatorRepositoryCommit()) ->loadFromArray($dict); } /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getHarbormasterBuildablePHID(); + } + public function getHarbormasterBuildablePHID() { return $this->getPHID(); } public function getHarbormasterContainerPHID() { return $this->getRepository()->getPHID(); } public function getBuildVariables() { $results = array(); $results['buildable.commit'] = $this->getCommitIdentifier(); $repo = $this->getRepository(); $results['repository.callsign'] = $repo->getCallsign(); $results['repository.phid'] = $repo->getPHID(); $results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.uri'] = $repo->getPublicCloneURI(); return $results; } public function getAvailableBuildVariables() { return array( 'buildable.commit' => pht('The commit identifier, if applicable.'), 'repository.callsign' => pht('The callsign of the repository in Phabricator.'), 'repository.phid' => pht('The PHID of the repository in Phabricator.'), 'repository.vcs' => pht('The version control system, either "svn", "hg" or "git".'), 'repository.uri' => pht('The URI to clone or checkout the repository from.'), ); } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return PhabricatorEnv::getEnvConfig('diffusion.fields'); } public function getCustomFieldBaseClass() { return 'PhabricatorCommitCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } /* -( PhabricatorSubscribableInterface )----------------------------------- */ public function isAutomaticallySubscribed($phid) { // TODO: This should also list auditors, but handling that is a bit messy // right now because we are not guaranteed to have the data. return ($phid == $this->getAuthorPHID()); } public function shouldShowSubscribersProperty() { return true; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorAuditEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhabricatorAuditTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { $xactions = $timeline->getTransactions(); $path_ids = array(); foreach ($xactions as $xaction) { if ($xaction->hasComment()) { $path_id = $xaction->getComment()->getPathID(); if ($path_id) { $path_ids[] = $path_id; } } } $path_map = array(); if ($path_ids) { $path_map = id(new DiffusionPathQuery()) ->withPathIDs($path_ids) ->execute(); $path_map = ipull($path_map, 'path', 'id'); } return $timeline->setPathMap($path_map); } /* -( PhabricatorFulltextInterface )--------------------------------------- */ public function newFulltextEngine() { return new DiffusionCommitFulltextEngine(); } }