public function getBarrierToLanding(PhabricatorUser $viewer, DifferentialRevision $revision) { $repository = $revision->getRepository(); if (!$repository) { return array('title' => pht('No Repository'), 'body' => pht('This revision is not associated with a known repository. Only ' . 'revisions associated with a tracked repository can be landed ' . 'automatically.')); } if (!$repository->canPerformAutomation()) { return array('title' => pht('No Repository Automation'), 'body' => pht('The repository this revision is associated with ("%s") is not ' . 'configured to support automation. Configure automation for the ' . 'repository to enable revisions to be landed automatically.', $repository->getMonogram())); } // Check if this diff was pushed to a staging area. $diff = id(new DifferentialDiffQuery())->setViewer($viewer)->withIDs(array($revision->getActiveDiff()->getID()))->needProperties(true)->executeOne(); // Older diffs won't have this property. They may still have been pushed. // At least for now, assume staging changes are present if the property // is missing. This should smooth the transition to the more formal // approach. $has_staging = $diff->hasDiffProperty('arc.staging'); if ($has_staging) { $staging = $diff->getProperty('arc.staging'); if (!is_array($staging)) { $staging = array(); } $status = idx($staging, 'status'); if ($status != ArcanistDiffWorkflow::STAGING_PUSHED) { return $this->getBarrierToLandingFromStagingStatus($status); } } // TODO: At some point we should allow installs to give "land reviewed // code" permission to more users than "push any commit", because it is // a much less powerful operation. For now, just require push so this // doesn't do anything users can't do on their own. $can_push = PhabricatorPolicyFilter::hasCapability($viewer, $repository, DiffusionPushCapability::CAPABILITY); if (!$can_push) { return array('title' => pht('Unable to Push'), 'body' => pht('You do not have permission to push to the repository this ' . 'revision is associated with ("%s"), so you can not land it.', $repository->getMonogram())); } $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; if ($revision->getStatus() != $status_accepted) { switch ($revision->getStatus()) { case ArcanistDifferentialRevisionStatus::CLOSED: return array('title' => pht('Revision Closed'), 'body' => pht('This revision has already been closed. Only open, accepted ' . 'revisions may land.')); case ArcanistDifferentialRevisionStatus::ABANDONED: return array('title' => pht('Revision Abandoned'), 'body' => pht('This revision has been abandoned. Only accepted revisions ' . 'may land.')); default: return array('title' => pht('Revision Not Accepted'), 'body' => pht('This revision is still under review. Only revisions which ' . 'have been accepted may land.')); } } // Check for other operations. Eventually this should probably be more // general (e.g., it's OK to land to multiple different branches // simultaneously) but just put this in as a sanity check for now. $other_operations = id(new DrydockRepositoryOperationQuery())->setViewer($viewer)->withObjectPHIDs(array($revision->getPHID()))->withOperationTypes(array($this->getOperationConstant()))->withOperationStates(array(DrydockRepositoryOperation::STATE_WAIT, DrydockRepositoryOperation::STATE_WORK, DrydockRepositoryOperation::STATE_DONE))->execute(); if ($other_operations) { $any_done = false; foreach ($other_operations as $operation) { if ($operation->isDone()) { $any_done = true; break; } } if ($any_done) { return array('title' => pht('Already Complete'), 'body' => pht('This revision has already landed.')); } else { return array('title' => pht('Already In Flight'), 'body' => pht('This revision is already landing.')); } } return null; }
public function updateRevisionWithCommit(DifferentialRevision $revision, PhabricatorRepositoryCommit $commit, array $more_xactions, PhabricatorContentSource $content_source) { $viewer = $this->getViewer(); $result_data = array(); $new_diff = $this->newDiffFromCommit($commit); $old_diff = $revision->getActiveDiff(); $changed_uri = null; if ($old_diff) { $old_diff = id(new DifferentialDiffQuery())->setViewer($viewer)->withIDs(array($old_diff->getID()))->needChangesets(true)->executeOne(); if ($old_diff) { $has_changed = $this->isDiffChangedBeforeCommit($commit, $old_diff, $new_diff); if ($has_changed) { $result_data['vsDiff'] = $old_diff->getID(); $revision_monogram = $revision->getMonogram(); $old_id = $old_diff->getID(); $new_id = $new_diff->getID(); $changed_uri = "/{$revision_monogram}?vs={$old_id}&id={$new_id}#toc"; $changed_uri = PhabricatorEnv::getProductionURI($changed_uri); } } } $xactions = array(); $xactions[] = id(new DifferentialTransaction())->setTransactionType(DifferentialTransaction::TYPE_UPDATE)->setIgnoreOnNoEffect(true)->setNewValue($new_diff->getPHID())->setMetadataValue('isCommitUpdate', true); foreach ($more_xactions as $more_xaction) { $xactions[] = $more_xaction; } $editor = id(new DifferentialTransactionEditor())->setActor($viewer)->setContinueOnMissingFields(true)->setContentSource($content_source)->setChangedPriorToCommitURI($changed_uri)->setIsCloseByCommit(true); $author_phid = $this->getAuthorPHID(); if ($author_phid !== null) { $editor->setActingAsPHID($author_phid); } try { $editor->applyTransactions($revision, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { // NOTE: We've marked transactions other than the CLOSE transaction // as ignored when they don't have an effect, so this means that we // lost a race to close the revision. That's perfectly fine, we can // just continue normally. } return $result_data; }