diff --git a/src/applications/releeph/conduit/ConduitAPI_releeph_queryrequests_Method.php b/src/applications/releeph/conduit/ConduitAPI_releeph_queryrequests_Method.php index 7180166efb..9683215576 100644 --- a/src/applications/releeph/conduit/ConduitAPI_releeph_queryrequests_Method.php +++ b/src/applications/releeph/conduit/ConduitAPI_releeph_queryrequests_Method.php @@ -1,73 +1,71 @@ 'optional list', 'requestedCommitPHIDs' => 'optional list' ); } public function defineReturnType() { return 'dict'; } public function defineErrorTypes() { return array(); } protected function execute(ConduitAPIRequest $conduit_request) { $revision_phids = $conduit_request->getValue('revisionPHIDs'); $requested_commit_phids = $conduit_request->getValue('requestedCommitPHIDs'); $result = array(); if (!$revision_phids && !$requested_commit_phids) { return $result; } $query = new ReleephRequestQuery(); $query->setViewer($conduit_request->getUser()); if ($revision_phids) { $query->withRevisionPHIDs($revision_phids); } else if ($requested_commit_phids) { $query->withRequestedCommitPHIDs($requested_commit_phids); } $releephRequests = $query->execute(); foreach ($releephRequests as $releephRequest) { - $branch = $releephRequest->loadReleephBranch(); - if (!$branch) { - continue; - } + $branch = $releephRequest->getBranch(); + $request_commit_phid = $releephRequest->getRequestCommitPHID(); $revisionPHID = $query->getRevisionPHID($request_commit_phid); $status = $releephRequest->getStatus(); $statusName = ReleephRequestStatus::getStatusDescriptionFor($status); $url = PhabricatorEnv::getProductionURI('/RQ'.$releephRequest->getID()); $result[] = array( 'branchBasename' => $branch->getBasename(), 'branchSymbolic' => $branch->getSymbolicName(), 'requestID' => $releephRequest->getID(), 'revisionPHID' => $revisionPHID, 'status' => $status, 'statusName' => $statusName, 'url' => $url, ); } return $result; } } diff --git a/src/applications/releeph/conduit/ConduitAPI_releeph_request_Method.php b/src/applications/releeph/conduit/ConduitAPI_releeph_request_Method.php index bd9d0be36f..762b4a4c9f 100644 --- a/src/applications/releeph/conduit/ConduitAPI_releeph_request_Method.php +++ b/src/applications/releeph/conduit/ConduitAPI_releeph_request_Method.php @@ -1,157 +1,159 @@ 'required string', - 'things' => 'required string', + 'things' => 'required list', 'fields' => 'dict', ); } public function defineReturnType() { return 'dict'; } public function defineErrorTypes() { return array( "ERR_BRANCH" => 'Unknown Releeph branch.', "ERR_FIELD_PARSE" => 'Unable to parse a Releeph field.', ); } protected function execute(ConduitAPIRequest $request) { $user = $request->getUser(); $viewer_handle = id(new PhabricatorHandleQuery()) ->setViewer($user) ->withPHIDs(array($user->getPHID())) ->executeOne(); $branch_phid = $request->getValue('branchPHID'); - $releeph_branch = id(new ReleephBranch()) - ->loadOneWhere('phid = %s', $branch_phid); + $releeph_branch = id(new ReleephBranchQuery()) + ->setViewer($user) + ->withPHIDs(array($branch_phid)) + ->executeOne(); if (!$releeph_branch) { throw id(new ConduitException("ERR_BRANCH"))->setErrorDescription( "No ReleephBranch found with PHID {$branch_phid}!"); } - $releeph_project = $releeph_branch->loadReleephProject(); + $releeph_project = $releeph_branch->getProduct(); // Find the requested commit identifiers $requested_commits = array(); $things = $request->getValue('things'); $finder = id(new ReleephCommitFinder()) ->setUser($user) ->setReleephProject($releeph_project); foreach ($things as $thing) { try { $requested_commits[$thing] = $finder->fromPartial($thing); } catch (ReleephCommitFinderException $ex) { throw id(new ConduitException('ERR_NO_MATCHES')) ->setErrorDescription($ex->getMessage()); } } $requested_commit_phids = mpull($requested_commits, 'getPHID'); // Find any existing requests that clash on the commit id, for this branch $existing_releeph_requests = id(new ReleephRequest())->loadAllWhere( 'requestCommitPHID IN (%Ls) AND branchID = %d', $requested_commit_phids, $releeph_branch->getID()); $existing_releeph_requests = mpull( $existing_releeph_requests, null, 'getRequestCommitPHID'); $selector = $releeph_project->getReleephFieldSelector(); $fields = $selector->getFieldSpecifications(); foreach ($fields as $field) { $field ->setReleephProject($releeph_project) ->setReleephBranch($releeph_branch); } $results = array(); $handles = id(new PhabricatorHandleQuery()) ->setViewer($user) ->withPHIDs($requested_commit_phids) ->execute(); foreach ($requested_commits as $thing => $commit) { $phid = $commit->getPHID(); $name = id($handles[$phid])->getName(); $releeph_request = null; $existing_releeph_request = idx($existing_releeph_requests, $phid); if ($existing_releeph_request) { $releeph_request = $existing_releeph_request; } else { $releeph_request = id(new ReleephRequest()) ->setRequestUserPHID($user->getPHID()) ->setBranchID($releeph_branch->getID()) ->setInBranch(0); $xactions = array(); $xactions[] = id(new ReleephRequestTransaction()) ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST) ->setNewValue($commit->getPHID()); $xactions[] = id(new ReleephRequestTransaction()) ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT) ->setMetadataValue('userPHID', $user->getPHID()) ->setMetadataValue( 'isAuthoritative', $releeph_project->isAuthoritative($user)) ->setNewValue(ReleephRequest::INTENT_WANT); foreach ($fields as $field) { if (!$field->isEditable()) { continue; } $field->setReleephRequest($releeph_request); try { $field->setValueFromConduitAPIRequest($request); } catch (ReleephFieldParseException $ex) { throw id(new ConduitException('ERR_FIELD_PARSE')) ->setErrorDescription($ex->getMessage()); } } $editor = id(new ReleephRequestTransactionalEditor()) ->setActor($user) ->setContinueOnNoEffect(true) ->setContentSource( PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_CONDUIT, array())); $editor->applyTransactions($releeph_request, $xactions); } $url = PhabricatorEnv::getProductionURI('/Y'.$releeph_request->getID()); $results[$thing] = array( 'thing' => $thing, 'branch' => $releeph_branch->getDisplayNameWithDetail(), 'commitName' => $name, 'commitID' => $commit->getCommitIdentifier(), 'url' => $url, 'requestID' => $releeph_request->getID(), 'requestor' => $viewer_handle->getName(), 'requestTime' => $releeph_request->getDateCreated(), 'existing' => $existing_releeph_request !== null, ); } return $results; } } diff --git a/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getbranchcommitmessage_Method.php b/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getbranchcommitmessage_Method.php index 9095205466..9f5c6932c8 100644 --- a/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getbranchcommitmessage_Method.php +++ b/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getbranchcommitmessage_Method.php @@ -1,95 +1,99 @@ 'required string', ); } public function defineReturnType() { return 'nonempty string'; } public function defineErrorTypes() { return array(); } protected function execute(ConduitAPIRequest $request) { - $branch = id(new ReleephBranch()) - ->loadOneWhere('phid = %s', $request->getValue('branchPHID')); + $viewer = $request->getUser(); - $project = $branch->loadReleephProject(); + $branch = id(new ReleephBranchQuery()) + ->setViewer($viewer) + ->withPHIDs(array($request->getValue('branchPHID'))) + ->executeOne(); + + $project = $branch->getProduct(); $creator_phid = $branch->getCreatedByUserPHID(); $cut_phid = $branch->getCutPointCommitPHID(); $phids = array( $branch->getPHID(), $project->getPHID(), $creator_phid, $cut_phid, ); $handles = id(new PhabricatorHandleQuery()) ->setViewer($request->getUser()) ->withPHIDs($phids) ->execute(); $h_branch = $handles[$branch->getPHID()]; $h_project = $handles[$project->getPHID()]; // Not as customizable as a ReleephRequest's commit message. It doesn't // really need to be. $commit_message = array(); $commit_message[] = $h_branch->getFullName(); $commit_message[] = $h_branch->getURI(); $commit_message[] = "Cut Point: ".$handles[$cut_phid]->getName(); $cut_point_pr_commit = id(new PhabricatorRepositoryCommit()) ->loadOneWhere('phid = %s', $cut_phid); $cut_point_commit_date = strftime( '%Y-%m-%d %H:%M:%S%z', $cut_point_pr_commit->getEpoch()); $commit_message[] = "Cut Point Date: {$cut_point_commit_date}"; $commit_message[] = "Created By: ".$handles[$creator_phid]->getName(); $project_uri = $project->getURI(); $commit_message[] = "Project: ".$h_project->getName()." ".$project_uri; /** * Required for 090-limit_new_branch_creations.sh in * admin/scripts/git/hosting/hooks/update.d (in the E repo): * * http://fburl.com/2372545 * * The commit message must have a line saying: * * @new-branch: * */ $repo = $project->loadPhabricatorRepository(); switch ($repo->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $commit_message[] = sprintf( '@new-branch: %s', $branch->getName()); break; } return implode("\n\n", $commit_message); } } diff --git a/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getcommitmessage_Method.php b/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getcommitmessage_Method.php index 3241369579..3bdf941c8d 100644 --- a/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getcommitmessage_Method.php +++ b/src/applications/releeph/conduit/work/ConduitAPI_releephwork_getcommitmessage_Method.php @@ -1,90 +1,94 @@ 'required string', 'action' => 'required enum<"pick", "revert">', ); } public function defineReturnType() { return 'dict'; } public function defineErrorTypes() { return array(); } protected function execute(ConduitAPIRequest $request) { - $releeph_request = id(new ReleephRequest()) - ->loadOneWhere('phid = %s', $request->getValue('requestPHID')); + $viewer = $request->getUser(); + + $releeph_request = id(new ReleephRequestQuery()) + ->setViewer($viewer) + ->withPHIDs(array($request->getValue('requestPHID'))) + ->executeOne(); $action = $request->getValue('action'); $title = $releeph_request->getSummaryForDisplay(); $commit_message = array(); - $project = $releeph_request->loadReleephProject(); - $branch = $releeph_request->loadReleephBranch(); + $branch = $releeph_request->getBranch(); + $project = $branch->getProduct(); $selector = $project->getReleephFieldSelector(); $fields = $selector->getFieldSpecifications(); $fields = $selector->sortFieldsForCommitMessage($fields); foreach ($fields as $field) { $field ->setUser($request->getUser()) ->setReleephProject($project) ->setReleephBranch($branch) ->setReleephRequest($releeph_request); $label = null; $value = null; switch ($action) { case 'pick': if ($field->shouldAppearOnCommitMessage()) { $label = $field->renderLabelForCommitMessage(); $value = $field->renderValueForCommitMessage(); } break; case 'revert': if ($field->shouldAppearOnRevertMessage()) { $label = $field->renderLabelForRevertMessage(); $value = $field->renderValueForRevertMessage(); } break; } if ($label && $value) { if (strpos($value, "\n") !== false || substr($value, 0, 2) === ' ') { $commit_message[] = "{$label}:\n{$value}"; } else { $commit_message[] = "{$label}: {$value}"; } } } return array( 'title' => $title, 'body' => implode("\n\n", $commit_message), ); } } diff --git a/src/applications/releeph/conduit/work/ConduitAPI_releephwork_nextrequest_Method.php b/src/applications/releeph/conduit/work/ConduitAPI_releephwork_nextrequest_Method.php index 125d9f10da..329b3f0451 100644 --- a/src/applications/releeph/conduit/work/ConduitAPI_releephwork_nextrequest_Method.php +++ b/src/applications/releeph/conduit/work/ConduitAPI_releephwork_nextrequest_Method.php @@ -1,214 +1,216 @@ 'required phid', 'seen' => 'required map', ); } public function defineReturnType() { return ''; } public function defineErrorTypes() { return array( 'ERR-NOT-PUSHER' => 'You are not listed as a pusher for thie Releeph project!', ); } protected function execute(ConduitAPIRequest $request) { $viewer = $request->getUser(); $seen = $request->getValue('seen'); - $branch = id(new ReleephBranch()) - ->loadOneWhere('phid = %s', $request->getValue('branchPHID')); + $branch = id(new ReleephBranchQuery()) + ->setViewer($viewer) + ->withPHIDs(array($request->getValue('branchPHID'))) + ->executeOne(); - $project = $branch->loadReleephProject(); + $project = $branch->getProduct(); $needs_pick = array(); $needs_revert = array(); // Load every request ever made for this branch...?!!! $releeph_requests = id(new ReleephRequestQuery()) ->setViewer($viewer) ->withBranchIDs(array($branch->getID())) ->execute(); foreach ($releeph_requests as $candidate) { $phid = $candidate->getPHID(); if (idx($seen, $phid)) { continue; } $should = $candidate->shouldBeInBranch(); $in = $candidate->getInBranch(); if ($should && !$in) { $needs_pick[] = $candidate; } if (!$should && $in) { $needs_revert[] = $candidate; } } /** * Sort both needs_pick and needs_revert in ascending commit order, as * discovered by Phabricator (using the `id` column to perform that * ordering). * * This is easy for $needs_pick as the ordinal is stored. It is hard for * reverts, as we have to look that information up. */ $needs_pick = $this->sortPicks($needs_pick); $needs_revert = $this->sortReverts($needs_revert); /** * Do reverts first in reverse order, then the picks in original-commit * order. * * This seems like the correct thing to do, but there may be a better * algorithm for the releephwork.nextrequest Conduit call that orders * things better. * * We could also button-mash our way through everything that failed (at the * end of the run) to try failed things again. */ $releeph_request = null; $action = null; if ($needs_revert) { $releeph_request = last($needs_revert); $action = 'revert'; $commit_id = $releeph_request->getCommitIdentifier(); $commit_phid = $releeph_request->getCommitPHID(); } elseif ($needs_pick) { $releeph_request = head($needs_pick); $action = 'pick'; $commit = $releeph_request->loadPhabricatorRepositoryCommit(); $commit_id = $commit->getCommitIdentifier(); $commit_phid = $commit->getPHID(); } else { // Return early if there's nothing to do! return array(); } // Build the response $phids = array(); $phids[] = $commit_phid; $diff_phid = null; $diff_rev_id = null; $diff_rev = $releeph_request->loadDifferentialRevision(); if ($diff_rev) { $diff_phid = $diff_rev->getPHID(); $phids[] = $diff_phid; $diff_rev_id = $diff_rev->getID(); } $phids[] = $releeph_request->getPHID(); $handles = id(new PhabricatorHandleQuery()) ->setViewer($request->getUser()) ->withPHIDs($phids) ->execute(); $diff_name = null; if ($diff_rev) { $diff_name = $handles[$diff_phid]->getName(); } $new_author_phid = null; if ($diff_rev) { $new_author_phid = $diff_rev->getAuthorPHID(); } else { $pr_commit = $releeph_request->loadPhabricatorRepositoryCommit(); if ($pr_commit) { $new_author_phid = $pr_commit->getAuthorPHID(); } } return array( 'requestID' => $releeph_request->getID(), 'requestPHID' => $releeph_request->getPHID(), 'requestName' => $handles[$releeph_request->getPHID()]->getName(), 'requestorPHID' => $releeph_request->getRequestUserPHID(), 'action' => $action, 'diffRevID' => $diff_rev_id, 'diffName' => $diff_name, 'commitIdentifier' => $commit_id, 'commitPHID' => $commit_phid, 'commitName' => $handles[$commit_phid]->getName(), 'needsRevert' => mpull($needs_revert, 'getID'), 'needsPick' => mpull($needs_pick, 'getID'), 'newAuthorPHID' => $new_author_phid, ); } private function sortPicks(array $releeph_requests) { $surrogate = array(); foreach ($releeph_requests as $rq) { // TODO: it's likely that relying on the `id` column to provide // trunk-commit-order is thoroughly broken. $ordinal = (int) $rq->loadPhabricatorRepositoryCommit()->getID(); $surrogate[$ordinal] = $rq; } ksort($surrogate); return $surrogate; } /** * Sort an array of ReleephRequests, that have been picked into a branch, in * the order in which they were picked to the branch. */ private function sortReverts(array $releeph_requests) { if (!$releeph_requests) { return array(); } // ReleephRequests, keyed by $releeph_requests = mpull($releeph_requests, null, 'getCommitIdentifier'); $commits = id(new PhabricatorRepositoryCommit()) ->loadAllWhere( 'commitIdentifier IN (%Ls)', mpull($releeph_requests, 'getCommitIdentifier')); // A map of => $surrogate = mpull($commits, 'getID', 'getCommitIdentifier'); $unparsed = array(); $result = array(); foreach ($releeph_requests as $commit_id => $releeph_request) { $ordinal = idx($surrogate, $commit_id); if ($ordinal) { $result[$ordinal] = $releeph_request; } else { $unparsed[] = $releeph_request; } } // Sort $result in ascending order ksort($result); // Unparsed commits we'll just have to guess, based on time $unparsed = msort($unparsed, 'getDateModified'); return array_merge($result, $unparsed); } } diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php index 6c94f7cd17..5ae00fb4e6 100644 --- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php +++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php @@ -1,369 +1,371 @@ " headers in commits created by * arc-releeph so that RQs committed by arc-releeph have real * PhabricatorRepositoryCommits associated with them (instaed of just the SHA * of the commit, as seen by the pusher). * * 2: If requestors want to commit directly to their release branch, they can * use this header to (i) indicate on a differential revision that this * differential revision is for the release branch, and (ii) when they land * their diff on to the release branch manually, the ReleephRequest is * automatically updated (instead of having to use the "Mark Manually Picked" * button.) * */ final class DifferentialReleephRequestFieldSpecification { // TODO: This class is essentially dead right now, see T2222. const ACTION_PICKS = 'picks'; const ACTION_REVERTS = 'reverts'; private $releephAction; private $releephPHIDs = array(); public function getStorageKey() { return 'releeph:actions'; } public function getValueForStorage() { return json_encode(array( 'releephAction' => $this->releephAction, 'releephPHIDs' => $this->releephPHIDs, )); } public function setValueFromStorage($json) { if ($json) { $dict = json_decode($json, true); $this->releephAction = idx($dict, 'releephAction'); $this->releephPHIDs = idx($dict, 'releephPHIDs'); } return $this; } public function shouldAppearOnRevisionView() { return true; } public function renderLabelForRevisionView() { return 'Releeph'; } public function getRequiredHandlePHIDs() { return mpull($this->loadReleephRequests(), 'getPHID'); } public function renderValueForRevisionView() { static $tense = array( self::ACTION_PICKS => array( 'future' => 'Will pick', 'past' => 'Picked', ), self::ACTION_REVERTS => array( 'future' => 'Will revert', 'past' => 'Reverted', ), ); $releeph_requests = $this->loadReleephRequests(); if (!$releeph_requests) { return null; } $status = $this->getRevision()->getStatus(); if ($status == ArcanistDifferentialRevisionStatus::CLOSED) { $verb = $tense[$this->releephAction]['past']; } else { $verb = $tense[$this->releephAction]['future']; } $parts = hsprintf('%s...', $verb); foreach ($releeph_requests as $releeph_request) { $parts->appendHTML(phutil_tag('br')); $parts->appendHTML( $this->getHandle($releeph_request->getPHID())->renderLink()); } return $parts; } public function shouldAppearOnCommitMessage() { return true; } public function getCommitMessageKey() { return 'releephActions'; } public function setValueFromParsedCommitMessage($dict) { $this->releephAction = $dict['releephAction']; $this->releephPHIDs = $dict['releephPHIDs']; return $this; } public function renderValueForCommitMessage($is_edit) { $releeph_requests = $this->loadReleephRequests(); if (!$releeph_requests) { return null; } $parts = array($this->releephAction); foreach ($releeph_requests as $releeph_request) { $parts[] = 'RQ'.$releeph_request->getID(); } return implode(' ', $parts); } /** * Releeph fields should look like: * * Releeph: picks RQ1 RQ2, RQ3 * Releeph: reverts RQ1 */ public function parseValueFromCommitMessage($value) { /** * Releeph commit messages look like this (but with more blank lines, * omitted here): * * Make CaptainHaddock more reasonable * Releeph: picks RQ1 * Requested By: edward * Approved By: edward (requestor) * Request Reason: x * Summary: Make the Haddock implementation more reasonable. * Test Plan: none * Reviewers: user1 * * Some of these fields are recognized by Differential (e.g. "Requested * By"). They are folded up into the "Releeph" field, parsed by this * class. As such $value includes more than just the first-line: * * "picks RQ1\n\nRequested By: edward\n\nApproved By: edward (requestor)" * * To hack around this, just consider the first line of $value when * determining what Releeph actions the parsed commit is performing. */ $first_line = head(array_filter(explode("\n", $value))); $tokens = preg_split('/\s*,?\s+/', $first_line); $raw_action = array_shift($tokens); $action = strtolower($raw_action); if (!$action) { return null; } switch ($action) { case self::ACTION_REVERTS: case self::ACTION_PICKS: break; default: throw new DifferentialFieldParseException( "Commit message contains unknown Releeph action '{$raw_action}'!"); break; } $releeph_requests = array(); foreach ($tokens as $token) { $match = array(); if (!preg_match('/^(?:RQ)?(\d+)$/i', $token, $match)) { $label = $this->renderLabelForCommitMessage(); throw new DifferentialFieldParseException( "Commit message contains unparseable ". "Releeph request token '{$token}'!"); } $id = (int) $match[1]; $releeph_request = id(new ReleephRequest())->load($id); if (!$releeph_request) { throw new DifferentialFieldParseException( "Commit message references non existent releeph request: {$value}!"); } $releeph_requests[] = $releeph_request; } if (count($releeph_requests) > 1) { $rqs_seen = array(); $groups = array(); foreach ($releeph_requests as $releeph_request) { - $releeph_branch = $releeph_request->loadReleephBranch(); + $releeph_branch = $releeph_request->getBranch(); $branch_name = $releeph_branch->getName(); $rq_id = 'RQ'.$releeph_request->getID(); if (idx($rqs_seen, $rq_id)) { throw new DifferentialFieldParseException( "Commit message refers to {$rq_id} multiple times!"); } $rqs_seen[$rq_id] = true; if (!isset($groups[$branch_name])) { $groups[$branch_name] = array(); } $groups[$branch_name][] = $rq_id; } if (count($groups) > 1) { $lists = array(); foreach ($groups as $branch_name => $rq_ids) { $lists[] = implode(', ', $rq_ids).' in '.$branch_name; } throw new DifferentialFieldParseException( "Commit message references multiple Releeph requests, ". "but the requests are in different branches: ". implode('; ', $lists)); } } $phids = mpull($releeph_requests, 'getPHID'); $data = array( 'releephAction' => $action, 'releephPHIDs' => $phids, ); return $data; } public function renderLabelForCommitMessage() { return 'Releeph'; } public function shouldAppearOnCommitMessageTemplate() { return false; } public function didParseCommit(PhabricatorRepository $repo, PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data) { // NOTE: This is currently dead code. See T2222. $releeph_requests = $this->loadReleephRequests(); if (!$releeph_requests) { return; } - $releeph_branch = head($releeph_requests)->loadReleephBranch(); + $releeph_branch = head($releeph_requests)->getBranch(); if (!$this->isCommitOnBranch($repo, $commit, $releeph_branch)) { return; } foreach ($releeph_requests as $releeph_request) { if ($this->releephAction === self::ACTION_PICKS) { $action = 'pick'; } else { $action = 'revert'; } $actor_phid = coalesce( $data->getCommitDetail('committerPHID'), $data->getCommitDetail('authorPHID')); $actor = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $actor_phid); $xactions = array(); $xactions[] = id(new ReleephRequestTransaction()) ->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY) ->setMetadataValue('action', $action) ->setMetadataValue('authorPHID', $data->getCommitDetail('authorPHID')) ->setMetadataValue('committerPHID', $data->getCommitDetail('committerPHID')) ->setNewValue($commit->getPHID()); $editor = id(new ReleephRequestTransactionalEditor()) ->setActor($actor) ->setContinueOnNoEffect(true) ->setContentSource( PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_UNKNOWN, array())); $editor->applyTransactions($releeph_request, $xactions); } } private function loadReleephRequests() { if (!$this->releephPHIDs) { return array(); - } else { - return id(new ReleephRequest()) - ->loadAllWhere('phid IN (%Ls)', $this->releephPHIDs); } + + return id(new ReleephRequestQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($this->releephPHIDs) + ->execute(); } private function isCommitOnBranch(PhabricatorRepository $repo, PhabricatorRepositoryCommit $commit, ReleephBranch $releeph_branch) { switch ($repo->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: list($output) = $repo->execxLocalCommand( 'branch --all --no-color --contains %s', $commit->getCommitIdentifier()); $remote_prefix = 'remotes/origin/'; $branches = array(); foreach (array_filter(explode("\n", $output)) as $line) { $tokens = explode(' ', $line); $ref = last($tokens); if (strncmp($ref, $remote_prefix, strlen($remote_prefix)) === 0) { $branch = substr($ref, strlen($remote_prefix)); $branches[$branch] = $branch; } } return idx($branches, $releeph_branch->getName()); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest( DiffusionRequest::newFromDictionary(array( 'user' => $this->getUser(), 'repository' => $repo, 'commit' => $commit->getCommitIdentifier(), ))); $path_changes = $change_query->loadChanges(); $commit_paths = mpull($path_changes, 'getPath'); $branch_path = $releeph_branch->getName(); $in_branch = array(); $ex_branch = array(); foreach ($commit_paths as $path) { if (strncmp($path, $branch_path, strlen($branch_path)) === 0) { $in_branch[] = $path; } else { $ex_branch[] = $path; } } if ($in_branch && $ex_branch) { $error = sprintf( "CONFUSION: commit %s in %s contains %d path change(s) that were ". "part of a Releeph branch, but also has %d path change(s) not ". "part of a Releeph branch!", $commit->getCommitIdentifier(), $repo->getCallsign(), count($in_branch), count($ex_branch)); phlog($error); } return !empty($in_branch); break; } } } diff --git a/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php b/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php index 56fd690846..775766cc96 100644 --- a/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php +++ b/src/applications/releeph/editor/ReleephRequestTransactionalEditor.php @@ -1,302 +1,302 @@ getTransactionType()) { case ReleephRequestTransaction::TYPE_REQUEST: return $object->getRequestCommitPHID(); case ReleephRequestTransaction::TYPE_EDIT_FIELD: $field = newv($xaction->getMetadataValue('fieldClass'), array()); $value = $field->setReleephRequest($object)->getValue(); return $value; case ReleephRequestTransaction::TYPE_USER_INTENT: $user_phid = $xaction->getAuthorPHID(); return idx($object->getUserIntents(), $user_phid); case ReleephRequestTransaction::TYPE_PICK_STATUS: return (int)$object->getPickStatus(); break; case ReleephRequestTransaction::TYPE_COMMIT: return $object->getCommitIdentifier(); case ReleephRequestTransaction::TYPE_DISCOVERY: return $object->getCommitPHID(); case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: return $object->getInBranch(); } } public function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case ReleephRequestTransaction::TYPE_REQUEST: case ReleephRequestTransaction::TYPE_USER_INTENT: case ReleephRequestTransaction::TYPE_EDIT_FIELD: case ReleephRequestTransaction::TYPE_PICK_STATUS: case ReleephRequestTransaction::TYPE_COMMIT: case ReleephRequestTransaction::TYPE_DISCOVERY: case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: return $xaction->getNewValue(); } } public function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $new = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { case ReleephRequestTransaction::TYPE_REQUEST: $object->setRequestCommitPHID($new); break; case ReleephRequestTransaction::TYPE_USER_INTENT: $user_phid = $xaction->getAuthorPHID(); $intents = $object->getUserIntents(); $intents[$user_phid] = $new; $object->setUserIntents($intents); break; case ReleephRequestTransaction::TYPE_EDIT_FIELD: $field = newv($xaction->getMetadataValue('fieldClass'), array()); $field ->setReleephRequest($object) ->setValue($new); break; case ReleephRequestTransaction::TYPE_PICK_STATUS: $object->setPickStatus($new); break; case ReleephRequestTransaction::TYPE_COMMIT: $this->setInBranchFromAction($object, $xaction); $object->setCommitIdentifier($new); break; case ReleephRequestTransaction::TYPE_DISCOVERY: $this->setInBranchFromAction($object, $xaction); $object->setCommitPHID($new); break; case ReleephRequestTransaction::TYPE_MANUAL_IN_BRANCH: $object->setInBranch((int) $new); break; } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return; } protected function filterTransactions( PhabricatorLiskDAO $object, array $xactions) { // Remove TYPE_DISCOVERY xactions that are the result of a reparse. $previously_discovered_commits = array(); $discovery_xactions = idx( mgroup($xactions, 'getTransactionType'), ReleephRequestTransaction::TYPE_DISCOVERY); if ($discovery_xactions) { $previous_xactions = id(new ReleephRequestTransactionQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withObjectPHIDs(array($object->getPHID())) ->execute(); foreach ($previous_xactions as $xaction) { if ($xaction->getTransactionType() === ReleephRequestTransaction::TYPE_DISCOVERY) { $commit_phid = $xaction->getNewValue(); $previously_discovered_commits[$commit_phid] = true; } } } foreach ($xactions as $key => $xaction) { if ($xaction->getTransactionType() === ReleephRequestTransaction::TYPE_DISCOVERY && idx($previously_discovered_commits, $xaction->getNewValue())) { unset($xactions[$key]); } } return parent::filterTransactions($object, $xactions); } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function sendMail( PhabricatorLiskDAO $object, array $xactions) { // Avoid sending emails that only talk about commit discovery. $types = array_unique(mpull($xactions, 'getTransactionType')); if ($types === array(ReleephRequestTransaction::TYPE_DISCOVERY)) { return null; } // Don't email people when we discover that something picks or reverts OK. if ($types === array(ReleephRequestTransaction::TYPE_PICK_STATUS)) { if (!mfilter($xactions, 'isBoringPickStatus', true /* negate */)) { // If we effectively call "isInterestingPickStatus" and get nothing... return null; } } return parent::sendMail($object, $xactions); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new ReleephRequestReplyHandler()) ->setActor($this->getActor()) ->setMailReceiver($object); } protected function getMailSubjectPrefix() { return '[Releeph]'; } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $phid = $object->getPHID(); $title = $object->getSummaryForDisplay(); return id(new PhabricatorMetaMTAMail()) ->setSubject("RQ{$id}: {$title}") ->addHeader('Thread-Topic', "RQ{$id}: {$phid}"); } protected function getMailTo(PhabricatorLiskDAO $object) { $to_phids = array(); - $releeph_project = $object->loadReleephProject(); - foreach ($releeph_project->getPushers() as $phid) { + $product = $object->getBranch()->getProduct(); + foreach ($product->getPushers() as $phid) { $to_phids[] = $phid; } foreach ($object->getUserIntents() as $phid => $intent) { $to_phids[] = $phid; } return $to_phids; } protected function getMailCC(PhabricatorLiskDAO $object) { return array(); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); $rq = $object; - $releeph_branch = $rq->loadReleephBranch(); - $releeph_project = $releeph_branch->loadReleephProject(); + $releeph_branch = $rq->getBranch(); + $releeph_project = $releeph_branch->getProduct(); /** * If any of the events we are emailing about were about a pick failure * (and/or a revert failure?), include pick failure instructions. */ $has_pick_failure = false; foreach ($xactions as $xaction) { if ($xaction->getTransactionType() === ReleephRequestTransaction::TYPE_PICK_STATUS && $xaction->getNewValue() === ReleephRequest::PICK_FAILED) { $has_pick_failure = true; break; } } if ($has_pick_failure) { $instructions = $releeph_project->getDetail('pick_failure_instructions'); if ($instructions) { $body->addTextSection( pht('PICK FAILURE INSTRUCTIONS'), $instructions); } } $name = sprintf("RQ%s: %s", $rq->getID(), $rq->getSummaryForDisplay()); $body->addTextSection( pht('RELEEPH REQUEST'), $name."\n". PhabricatorEnv::getProductionURI('/RQ'.$rq->getID())); $project_and_branch = sprintf( '%s - %s', $releeph_project->getName(), $releeph_branch->getDisplayNameWithDetail()); $body->addTextSection( pht('RELEEPH BRANCH'), $project_and_branch."\n". PhabricatorEnv::getProductionURI($releeph_branch->getURI())); return $body; } private function setInBranchFromAction( ReleephRequest $rq, ReleephRequestTransaction $xaction) { $action = $xaction->getMetadataValue('action'); switch ($action) { case 'pick': $rq->setInBranch(1); break; case 'revert': $rq->setInBranch(0); break; default: $id = $rq->getID(); $type = $xaction->getTransactionType(); $new = $xaction->getNewValue(); phlog( "Unknown discovery action '{$action}' ". "for xaction of type {$type} ". "with new value {$new} ". "mentioning RQ{$id}!"); break; } } } diff --git a/src/applications/releeph/query/ReleephRequestQuery.php b/src/applications/releeph/query/ReleephRequestQuery.php index 23725de1ec..5108e6361d 100644 --- a/src/applications/releeph/query/ReleephRequestQuery.php +++ b/src/applications/releeph/query/ReleephRequestQuery.php @@ -1,255 +1,257 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withBranchIDs(array $branch_ids) { $this->branchIDs = $branch_ids; return $this; } public function getRevisionPHID($commit_phid) { if ($this->commitToRevMap) { return idx($this->commitToRevMap, $commit_phid, null); } return null; } public function withStatus($status) { $this->status = $status; return $this; } public function withRequestedCommitPHIDs(array $requested_commit_phids) { $this->requestedCommitPHIDs = $requested_commit_phids; return $this; } public function withRequestorPHIDs(array $phids) { $this->requestorPHIDs = $phids; return $this; } public function withSeverities(array $severities) { $this->severities = $severities; return $this; } public function withRevisionPHIDs(array $revision_phids) { $this->revisionPHIDs = $revision_phids; return $this; } public function loadPage() { $table = new ReleephRequest(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $requests) { - // TODO: These should be serviced by the query, but are not currently - // denormalized anywhere. For now, filter them here instead. - - $keep_status = array_fuse($this->getKeepStatusConstants()); - if ($keep_status) { - foreach ($requests as $key => $request) { - if (empty($keep_status[$request->getStatus()])) { - unset($requests[$key]); - } - } - } - if ($this->severities) { $severities = array_fuse($this->severities); foreach ($requests as $key => $request) { // NOTE: Facebook uses a custom field here. if (ReleephDefaultFieldSelector::isFacebook()) { $severity = $request->getDetail('severity'); } else { $severity = $request->getDetail('releeph:severity'); } if (empty($severities[$severity])) { unset($requests[$key]); } } } $branch_ids = array_unique(mpull($requests, 'getBranchID')); $branches = id(new ReleephBranchQuery()) ->withIDs($branch_ids) ->setViewer($this->getViewer()) ->execute(); $branches = mpull($branches, null, 'getID'); foreach ($requests as $key => $request) { $branch = idx($branches, $request->getBranchID()); if (!$branch) { unset($requests[$key]); continue; } $request->attachBranch($branch); } + // TODO: These should be serviced by the query, but are not currently + // denormalized anywhere. For now, filter them here instead. Note that + // we must perform this filtering *after* querying and attaching branches, + // because request status depends on the product. + + $keep_status = array_fuse($this->getKeepStatusConstants()); + if ($keep_status) { + foreach ($requests as $key => $request) { + if (empty($keep_status[$request->getStatus()])) { + unset($requests[$key]); + } + } + } + return $requests; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->branchIDs) { $where[] = qsprintf( $conn_r, 'branchID IN (%Ld)', $this->branchIDs); } if ($this->requestedCommitPHIDs) { $where[] = qsprintf( $conn_r, 'requestCommitPHID IN (%Ls)', $this->requestedCommitPHIDs); } if ($this->requestorPHIDs) { $where[] = qsprintf( $conn_r, 'requestUserPHID IN (%Ls)', $this->requestorPHIDs); } if ($this->revisionPHIDs) { $type = PhabricatorEdgeConfig::TYPE_DREV_HAS_COMMIT; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($this->revisionPHIDs) ->withEdgeTypes(array($type)) ->execute(); $this->commitToRevMap = array(); foreach ($edges as $revision_phid => $edge) { foreach ($edge[$type] as $commitPHID => $item) { $this->commitToRevMap[$commitPHID] = $revision_phid; } } if (!$this->commitToRevMap) { throw new PhabricatorEmptyQueryException("Malformed Revision Phids"); } $where[] = qsprintf( $conn_r, 'requestCommitPHID IN (%Ls)', array_keys($this->commitToRevMap)); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function getKeepStatusConstants() { switch ($this->status) { case self::STATUS_ALL: return array(); case self::STATUS_OPEN: return array( ReleephRequestStatus::STATUS_REQUESTED, ReleephRequestStatus::STATUS_NEEDS_PICK, ReleephRequestStatus::STATUS_NEEDS_REVERT, ); case self::STATUS_REQUESTED: return array( ReleephRequestStatus::STATUS_REQUESTED, ); case self::STATUS_NEEDS_PULL: return array( ReleephRequestStatus::STATUS_NEEDS_PICK, ); case self::STATUS_REJECTED: return array( ReleephRequestStatus::STATUS_REJECTED, ); case self::STATUS_ABANDONED: return array( ReleephRequestStatus::STATUS_ABANDONED, ); case self::STATUS_PULLED: return array( ReleephRequestStatus::STATUS_PICKED, ); case self::STATUS_NEEDS_REVERT: return array( ReleephRequestStatus::NEEDS_REVERT, ); case self::STATUS_REVERTED: return array( ReleephRequestStatus::REVERTED, ); default: throw new Exception("Unknown status '{$this->status}'!"); } } public function getQueryApplicationClass() { return 'PhabricatorApplicationReleeph'; } } diff --git a/src/applications/releeph/storage/ReleephBranch.php b/src/applications/releeph/storage/ReleephBranch.php index be6fc11428..d779af56a8 100644 --- a/src/applications/releeph/storage/ReleephBranch.php +++ b/src/applications/releeph/storage/ReleephBranch.php @@ -1,162 +1,154 @@ true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(ReleephPHIDTypeBranch::TYPECONST); } public function getDetail($key, $default = null) { return idx($this->getDetails(), $key, $default); } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function willWriteData(array &$data) { // If symbolicName is omitted, set it to the basename. // // This means that we can enforce symbolicName as a UNIQUE column in the // DB. We'll interpret symbolicName === basename as meaning "no symbolic // name". // // SYMBOLIC_NAME_NOTE if (!$data['symbolicName']) { $data['symbolicName'] = $data['basename']; } parent::willWriteData($data); } public function getSymbolicName() { // See SYMBOLIC_NAME_NOTE above for why this is needed if ($this->symbolicName == $this->getBasename()) { return ''; } return $this->symbolicName; } public function setSymbolicName($name) { if ($name) { parent::setSymbolicName($name); } else { parent::setSymbolicName($this->getBasename()); } return $this; } public function getDisplayName() { if ($sn = $this->getSymbolicName()) { return $sn; } return $this->getBasename(); } public function getDisplayNameWithDetail() { $n = $this->getBasename(); if ($sn = $this->getSymbolicName()) { return "{$sn} ({$n})"; } else { return $n; } } public function getURI($path = null) { $components = array( - '/releeph', - rawurlencode($this->loadReleephProject()->getName()), - rawurlencode($this->getBasename()), - $path + '/releeph/branch', + $this->getID(), + $path, ); return implode('/', $components); } - public function loadReleephProject() { - return $this->loadOneRelative( - new ReleephProject(), - 'id', - 'getReleephProjectID'); - } - public function isActive() { return $this->getIsActive(); } public function attachProject(ReleephProject $project) { $this->project = $project; return $this; } public function getProject() { return $this->assertAttached($this->project); } public function getProduct() { return $this->getProject(); } public function attachCutPointCommit( PhabricatorRepositoryCommit $commit = null) { $this->cutPointCommit = $commit; return $this; } public function getCutPointCommit() { return $this->assertAttached($this->cutPointCommit); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return $this->getProduct()->getCapabilities(); } public function getPolicy($capability) { return $this->getProduct()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getProduct()->hasAutomaticCapability($capability, $viewer); } public function describeAutomaticCapability($capability) { return pht( 'Release branches have the same policies as the product they are a '. 'part of.'); } } diff --git a/src/applications/releeph/storage/ReleephProject.php b/src/applications/releeph/storage/ReleephProject.php index b05dd3f50b..d6b520ac89 100644 --- a/src/applications/releeph/storage/ReleephProject.php +++ b/src/applications/releeph/storage/ReleephProject.php @@ -1,143 +1,143 @@ true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID(ReleephPHIDTypeProduct::TYPECONST); } public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } public function getURI($path = null) { $components = array( - '/releeph/project', + '/releeph/product', $this->getID(), $path ); return implode('/', $components); } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function getArcanistProject() { return $this->assertAttached($this->arcanistProject); } public function attachArcanistProject( PhabricatorRepositoryArcanistProject $arcanist_project = null) { $this->arcanistProject = $arcanist_project; return $this; } public function getPushers() { return $this->getDetail('pushers', array()); } public function isPusher(PhabricatorUser $user) { // TODO Deprecate this once `isPusher` is out of the Facebook codebase. return $this->isAuthoritative($user); } public function isAuthoritative(PhabricatorUser $user) { return $this->isAuthoritativePHID($user->getPHID()); } public function isAuthoritativePHID($phid) { $pushers = $this->getPushers(); if (!$pushers) { return true; } else { return in_array($phid, $pushers); } } public function attachRepository(PhabricatorRepository $repository) { $this->repository = $repository; return $this; } public function getRepository() { return $this->assertAttached($this->repository); } // TODO: Remove once everything uses ProjectQuery. Also, T603. public function loadPhabricatorRepository() { return $this->loadOneRelative( new PhabricatorRepository(), 'phid', 'getRepositoryPHID'); } public function getReleephFieldSelector() { return new ReleephDefaultFieldSelector(); } public function isTestFile($filename) { $test_paths = $this->getDetail('testPaths', array()); foreach ($test_paths as $test_path) { if (preg_match($test_path, $filename)) { return true; } } return false; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return PhabricatorPolicies::POLICY_USER; } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } } diff --git a/src/applications/releeph/storage/ReleephRequest.php b/src/applications/releeph/storage/ReleephRequest.php index 92f7de87a5..3c5af69850 100644 --- a/src/applications/releeph/storage/ReleephRequest.php +++ b/src/applications/releeph/storage/ReleephRequest.php @@ -1,330 +1,313 @@ getPusherIntent() == self::INTENT_WANT && /** * We use "!= pass" instead of "== want" in case the requestor intent is * not present. In other words, only revert if the requestor explicitly * passed. */ $this->getRequestorIntent() != self::INTENT_PASS; } /** * Will return INTENT_WANT if any pusher wants this request, and no pusher * passes on this request. */ public function getPusherIntent() { - $project = $this->loadReleephProject(); - if (!$project) { - return null; - } + $product = $this->getBranch()->getProduct(); - if (!$project->getPushers()) { + if (!$product->getPushers()) { return self::INTENT_WANT; } $found_pusher_want = false; foreach ($this->userIntents as $phid => $intent) { - if ($project->isAuthoritativePHID($phid)) { + if ($product->isAuthoritativePHID($phid)) { if ($intent == self::INTENT_PASS) { return self::INTENT_PASS; } $found_pusher_want = true; } } if ($found_pusher_want) { return self::INTENT_WANT; } else { return null; } } public function getRequestorIntent() { return idx($this->userIntents, $this->requestUserPHID); } public function getStatus() { return $this->calculateStatus(); } public function getMonogram() { return 'Y'.$this->getID(); } public function getBranch() { return $this->assertAttached($this->branch); } public function attachBranch(ReleephBranch $branch) { $this->branch = $branch; return $this; } private function calculateStatus() { if ($this->shouldBeInBranch()) { if ($this->getInBranch()) { return ReleephRequestStatus::STATUS_PICKED; } else { return ReleephRequestStatus::STATUS_NEEDS_PICK; } } else { if ($this->getInBranch()) { return ReleephRequestStatus::STATUS_NEEDS_REVERT; } else { $has_been_in_branch = $this->getCommitIdentifier(); // Regardless of why we reverted something, always say reverted if it // was once in the branch. if ($has_been_in_branch) { return ReleephRequestStatus::STATUS_REVERTED; } elseif ($this->getPusherIntent() === ReleephRequest::INTENT_PASS) { // Otherwise, if it has never been in the branch, explicitly say why: return ReleephRequestStatus::STATUS_REJECTED; } elseif ($this->getRequestorIntent() === ReleephRequest::INTENT_WANT) { return ReleephRequestStatus::STATUS_REQUESTED; } else { return ReleephRequestStatus::STATUS_ABANDONED; } } } } /* -( Lisk mechanics )----------------------------------------------------- */ public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, 'userIntents' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( ReleephPHIDTypeRequest::TYPECONST); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } /* -( Helpful accessors )--------------------------------------------------- */ public function getDetail($key, $default = null) { return idx($this->getDetails(), $key, $default); } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function getReason() { // Backward compatibility: reason used to be called comments $reason = $this->getDetail('reason'); if (!$reason) { return $this->getDetail('comments'); } return $reason; } /** * Allow a null summary, and fall back to the title of the commit. */ public function getSummaryForDisplay() { $summary = $this->getDetail('summary'); if (!strlen($summary)) { $commit = $this->loadPhabricatorRepositoryCommit(); if ($commit) { $summary = $commit->getSummary(); } } if (!strlen($summary)) { $summary = pht('None'); } return $summary; } public function loadRequestCommitDiffPHID() { $phids = array(); $commit = $this->loadPhabricatorRepositoryCommit(); if ($commit) { $phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $commit->getPHID(), PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV); } return head($phids); } /* -( Loading external objects )------------------------------------------- */ - public function loadReleephBranch() { - return $this->loadOneRelative( - new ReleephBranch(), - 'id', - 'getBranchID'); - } - - public function loadReleephProject() { - $branch = $this->loadReleephBranch(); - if ($branch) { - return $branch->loadReleephProject(); - } - } - public function loadPhabricatorRepositoryCommit() { return $this->loadOneRelative( new PhabricatorRepositoryCommit(), 'phid', 'getRequestCommitPHID'); } public function loadPhabricatorRepositoryCommitData() { $commit = $this->loadPhabricatorRepositoryCommit(); if ($commit) { return $commit->loadOneRelative( new PhabricatorRepositoryCommitData(), 'commitID'); } } // TODO: (T603) Get rid of all this one-off ad-hoc loading. public function loadDifferentialRevision() { $diff_phid = $this->loadRequestCommitDiffPHID(); if (!$diff_phid) { return null; } return $this->loadOneRelative( new DifferentialRevision(), 'phid', 'loadRequestCommitDiffPHID'); } /* -( State change helpers )----------------------------------------------- */ public function setUserIntent(PhabricatorUser $user, $intent) { $this->userIntents[$user->getPHID()] = $intent; return $this; } /* -( Migrating to status-less ReleephRequests )--------------------------- */ protected function didReadData() { if ($this->userIntents === null) { $this->userIntents = array(); } } public function setStatus($value) { throw new Exception('`status` is now deprecated!'); } /* -( Make magic Lisk methods private )------------------------------------ */ private function setUserIntents(array $ar) { return parent::setUserIntents($ar); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBranch()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBranch()->hasAutomaticCapability($capability, $viewer); } public function describeAutomaticCapability($capability) { return pht( 'Pull requests have the same policies as the branches they are '. 'requested against.'); } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return PhabricatorEnv::getEnvConfig('releeph.fields'); } public function getCustomFieldBaseClass() { return 'ReleephFieldSpecification'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } }