mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-13 16:21:07 +01:00
Rough cut of DrydockRepositoryOperation
Summary: Ref T182. This doesn't do anything interesting yet and is mostly scaffolding, but here's roughly the workflow. From previous revision, you can configure "Repository Automation" for a repository: {F875741} If it's configured, a new "Land Revision" button shows up: {F875743} Once you click it you get a big warning dialog that it won't work, and then this shows up at the top of the revision (completely temporary/placeholder UI, some day a nice progress bar or whatever): {F875747} If you're lucky, the operation eventually sort of works: {F875750} It only runs `git show` right now, doesn't actually do any writes or anything. Test Plan: - Clicked "Land Revision". - Watched `phd debug task`. - Saw it log `git show` to output. - Verified operation success in UI (by fiddling URL, no way to get there normally yet). Reviewers: chad Reviewed By: chad Subscribers: revi Maniphest Tasks: T182 Differential Revision: https://secure.phabricator.com/D14266
This commit is contained in:
parent
df5a031b54
commit
b4af57ec51
16 changed files with 845 additions and 0 deletions
16
resources/sql/autopatches/20151013.drydock.op.1.sql
Normal file
16
resources/sql/autopatches/20151013.drydock.op.1.sql
Normal file
|
@ -0,0 +1,16 @@
|
|||
CREATE TABLE {$NAMESPACE}_drydock.drydock_repositoryoperation (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
repositoryPHID VARBINARY(64) NOT NULL,
|
||||
repositoryTarget LONGBLOB NOT NULL,
|
||||
operationType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
operationState VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (phid),
|
||||
KEY `key_object` (objectPHID),
|
||||
KEY `key_repository` (repositoryPHID, operationState)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -467,6 +467,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php',
|
||||
'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php',
|
||||
'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php',
|
||||
'DifferentialRevisionOperationController' => 'applications/differential/controller/DifferentialRevisionOperationController.php',
|
||||
'DifferentialRevisionPHIDType' => 'applications/differential/phid/DifferentialRevisionPHIDType.php',
|
||||
'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php',
|
||||
'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php',
|
||||
|
@ -839,6 +840,7 @@ phutil_register_library_map(array(
|
|||
'DrydockDefaultViewCapability' => 'applications/drydock/capability/DrydockDefaultViewCapability.php',
|
||||
'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php',
|
||||
'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
|
||||
'DrydockLandRepositoryOperation' => 'applications/drydock/operation/DrydockLandRepositoryOperation.php',
|
||||
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
|
||||
'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php',
|
||||
'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php',
|
||||
|
@ -878,6 +880,12 @@ phutil_register_library_map(array(
|
|||
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
|
||||
'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php',
|
||||
'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php',
|
||||
'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php',
|
||||
'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php',
|
||||
'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php',
|
||||
'DrydockRepositoryOperationType' => 'applications/drydock/operation/DrydockRepositoryOperationType.php',
|
||||
'DrydockRepositoryOperationUpdateWorker' => 'applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php',
|
||||
'DrydockRepositoryOperationViewController' => 'applications/drydock/controller/DrydockRepositoryOperationViewController.php',
|
||||
'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
|
||||
'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php',
|
||||
'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php',
|
||||
|
@ -4205,6 +4213,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionListController' => 'DifferentialController',
|
||||
'DifferentialRevisionListView' => 'AphrontView',
|
||||
'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver',
|
||||
'DifferentialRevisionOperationController' => 'DifferentialController',
|
||||
'DifferentialRevisionPHIDType' => 'PhabricatorPHIDType',
|
||||
'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField',
|
||||
|
@ -4605,6 +4614,7 @@ phutil_register_library_map(array(
|
|||
'DrydockDefaultViewCapability' => 'PhabricatorPolicyCapability',
|
||||
'DrydockFilesystemInterface' => 'DrydockInterface',
|
||||
'DrydockInterface' => 'Phobject',
|
||||
'DrydockLandRepositoryOperation' => 'DrydockRepositoryOperationType',
|
||||
'DrydockLease' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
|
@ -4650,6 +4660,15 @@ phutil_register_library_map(array(
|
|||
'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'DrydockObjectAuthorizationView' => 'AphrontView',
|
||||
'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'DrydockRepositoryOperation' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType',
|
||||
'DrydockRepositoryOperationQuery' => 'DrydockQuery',
|
||||
'DrydockRepositoryOperationType' => 'Phobject',
|
||||
'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker',
|
||||
'DrydockRepositoryOperationViewController' => 'DrydockController',
|
||||
'DrydockResource' => array(
|
||||
'DrydockDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
|
|
|
@ -75,6 +75,8 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
|
|||
=> 'DifferentialRevisionCloseDetailsController',
|
||||
'update/(?P<revisionID>[1-9]\d*)/'
|
||||
=> 'DifferentialDiffCreateController',
|
||||
'operation/(?P<id>[1-9]\d*)/'
|
||||
=> 'DifferentialRevisionOperationController',
|
||||
),
|
||||
'comment/' => array(
|
||||
'preview/(?P<id>[1-9]\d*)/' => 'DifferentialCommentPreviewController',
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRevisionOperationController
|
||||
extends DifferentialController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->withIDs(array($id))
|
||||
->setViewer($viewer)
|
||||
->executeOne();
|
||||
if (!$revision) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$detail_uri = "/D{$id}";
|
||||
|
||||
$repository = $revision->getRepository();
|
||||
if (!$repository) {
|
||||
return $this->rejectOperation(
|
||||
$revision,
|
||||
pht('No Repository'),
|
||||
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 $this->rejectOperation(
|
||||
$revision,
|
||||
pht('No Repository Automation'),
|
||||
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()));
|
||||
}
|
||||
|
||||
// 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 $this->rejectOperation(
|
||||
$revision,
|
||||
pht('Unable to Push'),
|
||||
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()));
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$op = new DrydockLandRepositoryOperation();
|
||||
|
||||
$operation = DrydockRepositoryOperation::initializeNewOperation($op)
|
||||
->setAuthorPHID($viewer->getPHID())
|
||||
->setObjectPHID($revision->getPHID())
|
||||
->setRepositoryPHID($repository->getPHID())
|
||||
->setRepositoryTarget('branch:master');
|
||||
|
||||
$operation->save();
|
||||
$operation->scheduleUpdate();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($detail_uri);
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Land Revision'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'In theory, this will do approximately what `arc land` would do. '.
|
||||
'In practice, that is almost certainly not what it will actually '.
|
||||
'do.'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'THIS FEATURE IS EXPERIMENTAL AND DANGEROUS! USE IT AT YOUR '.
|
||||
'OWN RISK!'))
|
||||
->addCancelButton($detail_uri)
|
||||
->addSubmitButton(pht('Mutate Repository Unpredictably'));
|
||||
}
|
||||
|
||||
private function rejectOperation(
|
||||
DifferentialRevision $revision,
|
||||
$title,
|
||||
$body) {
|
||||
|
||||
$id = $revision->getID();
|
||||
$detail_uri = "/D{$id}";
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->appendParagraph($body)
|
||||
->addCancelButton($detail_uri);
|
||||
}
|
||||
|
||||
}
|
|
@ -463,7 +463,10 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
|
||||
$object_id = 'D'.$revision->getID();
|
||||
|
||||
$operations_box = $this->buildOperationsBox($revision);
|
||||
|
||||
$content = array(
|
||||
$operations_box,
|
||||
$revision_detail_box,
|
||||
$diff_detail_box,
|
||||
$page_pane,
|
||||
|
@ -1032,4 +1035,55 @@ final class DifferentialRevisionViewController extends DifferentialController {
|
|||
return $view;
|
||||
}
|
||||
|
||||
private function buildOperationsBox(DifferentialRevision $revision) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// Save a query if we can't possibly have pending operations.
|
||||
$repository = $revision->getRepository();
|
||||
if (!$repository || !$repository->canPerformAutomation()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$operations = id(new DrydockRepositoryOperationQuery())
|
||||
->setViewer($viewer)
|
||||
->withObjectPHIDs(array($revision->getPHID()))
|
||||
->withOperationStates(
|
||||
array(
|
||||
DrydockRepositoryOperation::STATE_WAIT,
|
||||
DrydockRepositoryOperation::STATE_WORK,
|
||||
DrydockRepositoryOperation::STATE_FAIL,
|
||||
))
|
||||
->execute();
|
||||
if (!$operations) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$operation = head(msort($operations, 'getID'));
|
||||
|
||||
// TODO: This is completely made up for now, give it useful information and
|
||||
// a sweet progress bar.
|
||||
|
||||
switch ($operation->getOperationState()) {
|
||||
case DrydockRepositoryOperation::STATE_WAIT:
|
||||
case DrydockRepositoryOperation::STATE_WORK:
|
||||
$severity = PHUIInfoView::SEVERITY_NOTICE;
|
||||
$text = pht(
|
||||
'Some sort of repository operation is currently running.');
|
||||
break;
|
||||
default:
|
||||
$severity = PHUIInfoView::SEVERITY_ERROR;
|
||||
$text = pht(
|
||||
'Some sort of repository operation failed.');
|
||||
break;
|
||||
}
|
||||
|
||||
$info_view = id(new PHUIInfoView())
|
||||
->setSeverity($severity)
|
||||
->appendChild($text);
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Active Operations (EXPERIMENTAL!)'))
|
||||
->setInfoView($info_view);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,18 @@ final class DifferentialLandingActionMenuEventListener
|
|||
return null;
|
||||
}
|
||||
|
||||
if ($repository->canPerformAutomation()) {
|
||||
$revision_id = $revision->getID();
|
||||
|
||||
$action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setName(pht('Land Revision'))
|
||||
->setIcon('fa-fighter-jet')
|
||||
->setHref("/differential/revision/operation/{$revision_id}/");
|
||||
|
||||
$this->addActionMenuItems($event, $action);
|
||||
}
|
||||
|
||||
$strategies = id(new PhutilClassMapQuery())
|
||||
->setAncestorClass('DifferentialLandingStrategy')
|
||||
->execute();
|
||||
|
|
|
@ -90,6 +90,11 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
|
|||
'DrydockAuthorizationAuthorizeController',
|
||||
),
|
||||
),
|
||||
'(?P<type>operation)/' => array(
|
||||
'(?P<id>[1-9]\d*)/' => array(
|
||||
'' => 'DrydockRepositoryOperationViewController',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationViewController
|
||||
extends DrydockController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$operation = id(new DrydockRepositoryOperationQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$operation) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$id = $operation->getID();
|
||||
$title = pht('Repository Operation %d', $id);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title)
|
||||
->setUser($viewer)
|
||||
->setPolicyObject($operation);
|
||||
|
||||
$state = $operation->getOperationState();
|
||||
$icon = DrydockRepositoryOperation::getOperationStateIcon($state);
|
||||
$name = DrydockRepositoryOperation::getOperationStateName($state);
|
||||
$header->setStatus($icon, null, $name);
|
||||
|
||||
$actions = $this->buildActionListView($operation);
|
||||
$properties = $this->buildPropertyListView($operation);
|
||||
$properties->setActionList($actions);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb($title);
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($properties);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
private function buildActionListView(DrydockRepositoryOperation $operation) {
|
||||
$viewer = $this->getViewer();
|
||||
$id = $operation->getID();
|
||||
|
||||
$view = id(new PhabricatorActionListView())
|
||||
->setUser($viewer)
|
||||
->setObjectURI($this->getRequest()->getRequestURI())
|
||||
->setObject($operation);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildPropertyListView(
|
||||
DrydockRepositoryOperation $operation) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$view = new PHUIPropertyListView();
|
||||
$view->addProperty(
|
||||
pht('Repository'),
|
||||
$viewer->renderHandle($operation->getRepositoryPHID()));
|
||||
|
||||
$view->addProperty(
|
||||
pht('Object'),
|
||||
$viewer->renderHandle($operation->getObjectPHID()));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
public function buildSideNavView() {
|
||||
// TODO: Get rid of this, but it's currently required by DrydockController.
|
||||
return null;
|
||||
}
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
// TODO: As above.
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
final class DrydockLandRepositoryOperation
|
||||
extends DrydockRepositoryOperationType {
|
||||
|
||||
const OPCONST = 'land';
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
abstract class DrydockRepositoryOperationType extends Phobject {
|
||||
|
||||
final public function getOperationConstant() {
|
||||
return $this->getPhobjectClassConstant('OPCONST', 32);
|
||||
}
|
||||
|
||||
final public static function getAllOperationTypes() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getOperationConstant')
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationPHIDType extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'DRYO';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Drydock Repository Operation');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new DrydockRepositoryOperation();
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new DrydockRepositoryOperationQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$operation = $objects[$phid];
|
||||
$id = $operation->getID();
|
||||
|
||||
$handle->setName(pht('Drydock Repository Operation %d', $id));
|
||||
$handle->setURI("/drydock/operation/{$id}/");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationQuery extends DrydockQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $objectPHIDs;
|
||||
private $repositoryPHIDs;
|
||||
private $operationStates;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withObjectPHIDs(array $object_phids) {
|
||||
$this->objectPHIDs = $object_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withRepositoryPHIDs(array $repository_phids) {
|
||||
$this->repositoryPHIDs = $repository_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withOperationStates(array $states) {
|
||||
$this->operationStates = $states;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new DrydockRepositoryOperation();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $operations) {
|
||||
$repository_phids = mpull($operations, 'getRepositoryPHID');
|
||||
if ($repository_phids) {
|
||||
$repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setParentQuery($this)
|
||||
->withPHIDs($repository_phids)
|
||||
->execute();
|
||||
$repositories = mpull($repositories, null, 'getPHID');
|
||||
} else {
|
||||
$repositories = array();
|
||||
}
|
||||
|
||||
foreach ($operations as $key => $operation) {
|
||||
$repository = idx($repositories, $operation->getRepositoryPHID());
|
||||
if (!$repository) {
|
||||
$this->didRejectResult($operation);
|
||||
unset($operations[$key]);
|
||||
continue;
|
||||
}
|
||||
$operation->attachRepository($repository);
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $operations) {
|
||||
$object_phids = mpull($operations, 'getObjectPHID');
|
||||
if ($object_phids) {
|
||||
$objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->setParentQuery($this)
|
||||
->withPHIDs($object_phids)
|
||||
->execute();
|
||||
$objects = mpull($objects, 'getPHID');
|
||||
} else {
|
||||
$objects = array();
|
||||
}
|
||||
|
||||
foreach ($operations as $key => $operation) {
|
||||
$object = idx($objects, $operation->getObjectPHID());
|
||||
$operation->attachObject($object);
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->objectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'objectPHID IN (%Ls)',
|
||||
$this->objectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->repositoryPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'repositoryPHID IN (%Ls)',
|
||||
$this->repositoryPHIDs);
|
||||
}
|
||||
|
||||
if ($this->operationStates !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'operationState IN (%Ls)',
|
||||
$this->operationStates);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
}
|
148
src/applications/drydock/storage/DrydockRepositoryOperation.php
Normal file
148
src/applications/drydock/storage/DrydockRepositoryOperation.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Represents a request to perform a repository operation like a merge or
|
||||
* cherry-pick.
|
||||
*/
|
||||
final class DrydockRepositoryOperation extends DrydockDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface {
|
||||
|
||||
const STATE_WAIT = 'wait';
|
||||
const STATE_WORK = 'work';
|
||||
const STATE_DONE = 'done';
|
||||
const STATE_FAIL = 'fail';
|
||||
|
||||
protected $authorPHID;
|
||||
protected $objectPHID;
|
||||
protected $repositoryPHID;
|
||||
protected $repositoryTarget;
|
||||
protected $operationType;
|
||||
protected $operationState;
|
||||
protected $properties = array();
|
||||
|
||||
private $repository = self::ATTACHABLE;
|
||||
private $object = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewOperation(
|
||||
DrydockRepositoryOperationType $op) {
|
||||
|
||||
return id(new DrydockRepositoryOperation())
|
||||
->setOperationState(self::STATE_WAIT)
|
||||
->setOperationType($op->getOperationConstant());
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'properties' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'repositoryTarget' => 'bytes',
|
||||
'operationType' => 'text32',
|
||||
'operationState' => 'text32',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_object' => array(
|
||||
'columns' => array('objectPHID'),
|
||||
),
|
||||
'key_repository' => array(
|
||||
'columns' => array('repositoryPHID', 'operationState'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
DrydockRepositoryOperationPHIDType::TYPECONST);
|
||||
}
|
||||
|
||||
public function attachRepository(PhabricatorRepository $repository) {
|
||||
$this->repository = $repository;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRepository() {
|
||||
return $this->assertAttached($this->repository);
|
||||
}
|
||||
|
||||
public function attachObject($object) {
|
||||
$this->object = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
return $this->assertAttached($this->object);
|
||||
}
|
||||
|
||||
public function getProperty($key, $default = null) {
|
||||
return idx($this->properties, $key, $default);
|
||||
}
|
||||
|
||||
public function setProperty($key, $value) {
|
||||
$this->properties[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function getOperationStateIcon($state) {
|
||||
$map = array(
|
||||
self::STATE_WAIT => 'fa-clock-o',
|
||||
self::STATE_WORK => 'fa-refresh blue',
|
||||
self::STATE_DONE => 'fa-check green',
|
||||
self::STATE_FAIL => 'fa-times red',
|
||||
);
|
||||
|
||||
return idx($map, $state, null);
|
||||
}
|
||||
|
||||
public static function getOperationStateName($state) {
|
||||
$map = array(
|
||||
self::STATE_WAIT => pht('Waiting'),
|
||||
self::STATE_WORK => pht('Working'),
|
||||
self::STATE_DONE => pht('Done'),
|
||||
self::STATE_FAIL => pht('Failed'),
|
||||
);
|
||||
|
||||
return idx($map, $state, pht('<Unknown: %s>', $state));
|
||||
}
|
||||
|
||||
public function scheduleUpdate() {
|
||||
PhabricatorWorker::scheduleTask(
|
||||
'DrydockRepositoryOperationUpdateWorker',
|
||||
array(
|
||||
'operationPHID' => $this->getPHID(),
|
||||
),
|
||||
array(
|
||||
'objectPHID' => $this->getPHID(),
|
||||
'priority' => PhabricatorWorker::PRIORITY_ALERTS,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return $this->getRepository()->getPolicy($capability);
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return $this->getRepository()->hasAutomaticCapability($capability, $viewer);
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return pht(
|
||||
'A repository operation inherits the policies of the repository it '.
|
||||
'affects.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
final class DrydockRepositoryOperationUpdateWorker
|
||||
extends DrydockWorker {
|
||||
|
||||
protected function doWork() {
|
||||
$operation_phid = $this->getTaskDataValue('operationPHID');
|
||||
|
||||
$hash = PhabricatorHash::digestForIndex($operation_phid);
|
||||
$lock_key = 'drydock.operation:'.$hash;
|
||||
|
||||
$lock = PhabricatorGlobalLock::newLock($lock_key)
|
||||
->lock(1);
|
||||
|
||||
try {
|
||||
$operation = $this->loadOperation($operation_phid);
|
||||
$this->handleUpdate($operation);
|
||||
} catch (Exception $ex) {
|
||||
$lock->unlock();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$lock->unlock();
|
||||
}
|
||||
|
||||
|
||||
private function handleUpdate(DrydockRepositoryOperation $operation) {
|
||||
$operation_state = $operation->getOperationState();
|
||||
|
||||
switch ($operation_state) {
|
||||
case DrydockRepositoryOperation::STATE_WAIT:
|
||||
$operation
|
||||
->setOperationState(DrydockRepositoryOperation::STATE_WORK)
|
||||
->save();
|
||||
break;
|
||||
case DrydockRepositoryOperation::STATE_WORK:
|
||||
break;
|
||||
case DrydockRepositoryOperation::STATE_DONE:
|
||||
case DrydockRepositoryOperation::STATE_FAIL:
|
||||
// No more processing for these requests.
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: We should probably check for other running operations with lower
|
||||
// IDs and the same repository target and yield to them here? That is,
|
||||
// enforce sequential evaluation of operations against the same target so
|
||||
// that if you land "A" and then land "B", we always finish "A" first.
|
||||
// For now, just let stuff happen in any order. We can't lease until
|
||||
// we know we're good to move forward because we might deadlock if we do:
|
||||
// we're waiting for another operation to complete, and that operation is
|
||||
// waiting for a lease we're holding.
|
||||
|
||||
try {
|
||||
$lease = $this->loadWorkingCopyLease($operation);
|
||||
|
||||
$interface = $lease->getInterface(
|
||||
DrydockCommandInterface::INTERFACE_TYPE);
|
||||
|
||||
// No matter what happens here, destroy the lease away once we're done.
|
||||
$lease->releaseOnDestruction(true);
|
||||
|
||||
// TODO: Some day, do useful things instead of running `git show`.
|
||||
list($stdout) = $interface->execx('git show');
|
||||
phlog($stdout);
|
||||
} catch (PhabricatorWorkerYieldException $ex) {
|
||||
throw $ex;
|
||||
} catch (Exception $ex) {
|
||||
$operation
|
||||
->setOperationState(DrydockRepositoryOperation::STATE_FAIL)
|
||||
->save();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$operation
|
||||
->setOperationState(DrydockRepositoryOperation::STATE_DONE)
|
||||
->save();
|
||||
|
||||
// TODO: Once we have sequencing, we could awaken the next operation
|
||||
// against this target after finishing or failing.
|
||||
}
|
||||
|
||||
private function loadWorkingCopyLease(
|
||||
DrydockRepositoryOperation $operation) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// TODO: This is very similar to leasing in Harbormaster, maybe we can
|
||||
// share some of the logic?
|
||||
|
||||
$lease_phid = $operation->getProperty('exec.leasePHID');
|
||||
if ($lease_phid) {
|
||||
$lease = id(new DrydockLeaseQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($lease_phid))
|
||||
->executeOne();
|
||||
if (!$lease) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Lease "%s" could not be loaded.',
|
||||
$lease_phid));
|
||||
}
|
||||
} else {
|
||||
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
|
||||
->getType();
|
||||
|
||||
$repository = $operation->getRepository();
|
||||
|
||||
$allowed_phids = $repository->getAutomationBlueprintPHIDs();
|
||||
$authorizing_phid = $repository->getPHID();
|
||||
|
||||
$lease = DrydockLease::initializeNewLease()
|
||||
->setResourceType($working_copy_type)
|
||||
->setOwnerPHID($operation->getPHID())
|
||||
->setAuthorizingPHID($authorizing_phid)
|
||||
->setAllowedBlueprintPHIDs($allowed_phids);
|
||||
|
||||
$map = $this->buildRepositoryMap($operation);
|
||||
|
||||
$lease->setAttribute('repositories.map', $map);
|
||||
|
||||
$task_id = $this->getCurrentWorkerTaskID();
|
||||
if ($task_id) {
|
||||
$lease->setAwakenTaskIDs(array($task_id));
|
||||
}
|
||||
|
||||
$operation
|
||||
->setProperty('exec.leasePHID', $lease->getPHID())
|
||||
->save();
|
||||
|
||||
$lease->queueForActivation();
|
||||
}
|
||||
|
||||
if ($lease->isActivating()) {
|
||||
throw new PhabricatorWorkerYieldException(15);
|
||||
}
|
||||
|
||||
if (!$lease->isActive()) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Lease "%s" never activated.',
|
||||
$lease->getPHID()));
|
||||
}
|
||||
|
||||
return $lease;
|
||||
}
|
||||
|
||||
private function buildRepositoryMap(DrydockRepositoryOperation $operation) {
|
||||
$repository = $operation->getRepository();
|
||||
|
||||
$target = $operation->getRepositoryTarget();
|
||||
list($type, $name) = explode(':', $target, 2);
|
||||
switch ($type) {
|
||||
case 'branch':
|
||||
$spec = array(
|
||||
'branch' => $name,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unknown repository operation target type "%s" (in target "%s").',
|
||||
$type,
|
||||
$target));
|
||||
}
|
||||
|
||||
$map = array();
|
||||
$map[$repository->getCloneName()] = array(
|
||||
'phid' => $repository->getPHID(),
|
||||
'default' => true,
|
||||
) + $spec;
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,21 @@ abstract class DrydockWorker extends PhabricatorWorker {
|
|||
return $resource;
|
||||
}
|
||||
|
||||
protected function loadOperation($operation_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$operation = id(new DrydockRepositoryOperationQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($operation_phid))
|
||||
->executeOne();
|
||||
if (!$operation) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('No such operation "%s"!', $operation_phid));
|
||||
}
|
||||
|
||||
return $operation;
|
||||
}
|
||||
|
||||
protected function loadCommands($target_phid) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
|
|
|
@ -1822,6 +1822,17 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $this->isGit();
|
||||
}
|
||||
|
||||
public function canPerformAutomation() {
|
||||
if (!$this->supportsAutomation()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->getAutomationBlueprintPHIDs()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getAutomationBlueprintPHIDs() {
|
||||
if (!$this->supportsAutomation()) {
|
||||
|
|
Loading…
Reference in a new issue