1
0
Fork 0
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:
epriestley 2015-10-13 15:46:12 -07:00
parent df5a031b54
commit b4af57ec51
16 changed files with 845 additions and 0 deletions

View 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};

View file

@ -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',

View file

@ -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',

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();

View file

@ -90,6 +90,11 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
'DrydockAuthorizationAuthorizeController',
),
),
'(?P<type>operation)/' => array(
'(?P<id>[1-9]\d*)/' => array(
'' => 'DrydockRepositoryOperationViewController',
),
),
),
);
}

View file

@ -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;
}
}

View file

@ -0,0 +1,8 @@
<?php
final class DrydockLandRepositoryOperation
extends DrydockRepositoryOperationType {
const OPCONST = 'land';
}

View file

@ -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();
}
}

View file

@ -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}/");
}
}
}

View file

@ -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;
}
}

View 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.');
}
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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()) {