Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2891144
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
30 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
index 0eb735439a..ca4ffc9088 100644
--- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
@@ -1,442 +1,478 @@
<?php
final class DrydockWorkingCopyBlueprintImplementation
extends DrydockBlueprintImplementation {
+ const PHASE_SQUASHMERGE = 'squashmerge';
+
public function isEnabled() {
return true;
}
public function getBlueprintName() {
return pht('Working Copy');
}
public function getDescription() {
return pht('Allows Drydock to check out working copies of repositories.');
}
public function canAnyBlueprintEverAllocateResourceForLease(
DrydockLease $lease) {
return true;
}
public function canEverAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
return true;
}
public function canAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$viewer = $this->getViewer();
if ($this->shouldLimitAllocatingPoolSize($blueprint)) {
return false;
}
// TODO: If we have a pending resource which is compatible with the
// configuration for this lease, prevent a new allocation? Otherwise the
// queue can fill up with copies of requests from the same lease. But
// maybe we can deal with this with "pre-leasing"?
return true;
}
public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// Don't hand out leases on working copies which have not activated, since
// it may take an arbitrarily long time for them to acquire a host.
if (!$resource->isActive()) {
return false;
}
$need_map = $lease->getAttribute('repositories.map');
if (!is_array($need_map)) {
return false;
}
$have_map = $resource->getAttribute('repositories.map');
if (!is_array($have_map)) {
return false;
}
$have_as = ipull($have_map, 'phid');
$need_as = ipull($need_map, 'phid');
foreach ($need_as as $need_directory => $need_phid) {
if (empty($have_as[$need_directory])) {
// This resource is missing a required working copy.
return false;
}
if ($have_as[$need_directory] != $need_phid) {
// This resource has a required working copy, but it contains
// the wrong repository.
return false;
}
unset($have_as[$need_directory]);
}
if ($have_as && $lease->getAttribute('repositories.strict')) {
// This resource has extra repositories, but the lease is strict about
// which repositories are allowed to exist.
return false;
}
if (!DrydockSlotLock::isLockFree($this->getLeaseSlotLock($resource))) {
return false;
}
return true;
}
public function acquireLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$lease
->needSlotLock($this->getLeaseSlotLock($resource))
->acquireOnResource($resource);
}
private function getLeaseSlotLock(DrydockResource $resource) {
$resource_phid = $resource->getPHID();
return "workingcopy.lease({$resource_phid})";
}
public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$resource = $this->newResourceTemplate($blueprint);
$resource_phid = $resource->getPHID();
$blueprint_phids = $blueprint->getFieldValue('blueprintPHIDs');
$host_lease = $this->newLease($blueprint)
->setResourceType('host')
->setOwnerPHID($resource_phid)
->setAttribute('workingcopy.resourcePHID', $resource_phid)
->setAllowedBlueprintPHIDs($blueprint_phids);
$resource->setAttribute('host.leasePHID', $host_lease->getPHID());
$map = $lease->getAttribute('repositories.map');
foreach ($map as $key => $value) {
$map[$key] = array_select_keys(
$value,
array(
'phid',
));
}
$resource->setAttribute('repositories.map', $map);
$slot_lock = $this->getConcurrentResourceLimitSlotLock($blueprint);
if ($slot_lock !== null) {
$resource->needSlotLock($slot_lock);
}
$resource->allocateResource();
$host_lease->queueForActivation();
return $resource;
}
public function activateResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
$lease = $this->loadHostLease($resource);
$this->requireActiveLease($lease);
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
// TODO: Make this configurable.
$resource_id = $resource->getID();
$root = "/var/drydock/workingcopy-{$resource_id}";
$map = $resource->getAttribute('repositories.map');
$repositories = $this->loadRepositories(ipull($map, 'phid'));
foreach ($map as $directory => $spec) {
// TODO: Validate directory isn't goofy like "/etc" or "../../lol"
// somewhere?
$repository = $repositories[$spec['phid']];
$path = "{$root}/repo/{$directory}/";
// TODO: Run these in parallel?
$interface->execx(
'git clone -- %s %s',
(string)$repository->getCloneURIObject(),
$path);
}
$resource
->setAttribute('workingcopy.root', $root)
->activateResource();
}
public function destroyResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
try {
$lease = $this->loadHostLease($resource);
} catch (Exception $ex) {
// If we can't load the lease, assume we don't need to take any actions
// to destroy it.
return;
}
// Destroy the lease on the host.
$lease->releaseOnDestruction();
if ($lease->isActive()) {
// Destroy the working copy on disk.
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
$root_key = 'workingcopy.root';
$root = $resource->getAttribute($root_key);
if (strlen($root)) {
$interface->execx('rm -rf -- %s', $root);
}
}
}
public function getResourceName(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
return pht('Working Copy');
}
public function activateLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$host_lease = $this->loadHostLease($resource);
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $host_lease->getInterface($command_type);
$map = $lease->getAttribute('repositories.map');
$root = $resource->getAttribute('workingcopy.root');
$default = null;
foreach ($map as $directory => $spec) {
$cmd = array();
$arg = array();
$interface->pushWorkingDirectory("{$root}/repo/{$directory}/");
$cmd[] = 'git clean -d --force';
$cmd[] = 'git fetch';
$commit = idx($spec, 'commit');
$branch = idx($spec, 'branch');
$ref = idx($spec, 'ref');
if ($commit !== null) {
$cmd[] = 'git reset --hard %s';
$arg[] = $commit;
} else if ($branch !== null) {
$cmd[] = 'git checkout %s';
$arg[] = $branch;
$cmd[] = 'git reset --hard origin/%s';
$arg[] = $branch;
} else if ($ref) {
$ref_uri = $ref['uri'];
$ref_ref = $ref['ref'];
$cmd[] = 'git fetch --no-tags -- %s +%s:%s';
$arg[] = $ref_uri;
$arg[] = $ref_ref;
$arg[] = $ref_ref;
$cmd[] = 'git checkout %s';
$arg[] = $ref_ref;
$cmd[] = 'git reset --hard %s';
$arg[] = $ref_ref;
} else {
$cmd[] = 'git reset --hard HEAD';
}
$cmd = implode(' && ', $cmd);
$argv = array_merge(array($cmd), $arg);
$result = call_user_func_array(
array($interface, 'execx'),
$argv);
if (idx($spec, 'default')) {
$default = $directory;
}
$merges = idx($spec, 'merges');
if ($merges) {
foreach ($merges as $merge) {
- $this->applyMerge($interface, $merge);
+ $this->applyMerge($lease, $interface, $merge);
}
}
$interface->popWorkingDirectory();
}
if ($default === null) {
$default = head_key($map);
}
// TODO: Use working storage?
$lease->setAttribute('workingcopy.default', "{$root}/repo/{$default}/");
$lease->activateOnResource($resource);
}
public function didReleaseLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// We leave working copies around even if there are no leases on them,
// since the cost to maintain them is nearly zero but rebuilding them is
// moderately expensive and it's likely that they'll be reused.
return;
}
public function destroyLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// When we activate a lease we just reset the working copy state and do
// not create any new state, so we don't need to do anything special when
// destroying a lease.
return;
}
public function getType() {
return 'working-copy';
}
public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
$type) {
switch ($type) {
case DrydockCommandInterface::INTERFACE_TYPE:
$host_lease = $this->loadHostLease($resource);
$command_interface = $host_lease->getInterface($type);
$path = $lease->getAttribute('workingcopy.default');
$command_interface->pushWorkingDirectory($path);
return $command_interface;
}
}
private function loadRepositories(array $phids) {
$viewer = $this->getViewer();
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($phids as $phid) {
if (empty($repositories[$phid])) {
throw new Exception(
pht(
'Repository PHID "%s" does not exist.',
$phid));
}
}
foreach ($repositories as $repository) {
$repository_vcs = $repository->getVersionControlSystem();
switch ($repository_vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
throw new Exception(
pht(
'Repository ("%s") has unsupported VCS ("%s").',
$repository->getPHID(),
$repository_vcs));
}
}
return $repositories;
}
private function loadHostLease(DrydockResource $resource) {
$viewer = $this->getViewer();
$lease_phid = $resource->getAttribute('host.leasePHID');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Unable to load lease ("%s").',
$lease_phid));
}
return $lease;
}
protected function getCustomFieldSpecifications() {
return array(
'blueprintPHIDs' => array(
'name' => pht('Use Blueprints'),
'type' => 'blueprints',
'required' => true,
),
);
}
protected function shouldUseConcurrentResourceLimit() {
return true;
}
private function applyMerge(
+ DrydockLease $lease,
DrydockCommandInterface $interface,
array $merge) {
$src_uri = $merge['src.uri'];
$src_ref = $merge['src.ref'];
$interface->execx(
'git fetch --no-tags -- %s +%s:%s',
$src_uri,
$src_ref,
$src_ref);
+ $command = csprintf(
+ 'git merge --no-stat --squash --ff-only -- %R',
+ $src_ref);
+
try {
- $interface->execx(
- 'git merge --no-stat --squash --ff-only -- %s',
- $src_ref);
+ $interface->execx('%C', $command);
} catch (CommandException $ex) {
- // TODO: Specifically note this as a merge conflict.
+ $this->setWorkingCopyVCSErrorFromCommandException(
+ $lease,
+ self::PHASE_SQUASHMERGE,
+ $command,
+ $ex);
+
throw $ex;
}
}
+ protected function setWorkingCopyVCSErrorFromCommandException(
+ DrydockLease $lease,
+ $phase,
+ $command,
+ CommandException $ex) {
+
+ $error = array(
+ 'phase' => $phase,
+ 'command' => (string)$command,
+ 'raw' => (string)$ex->getCommand(),
+ 'err' => $ex->getError(),
+ 'stdout' => $ex->getStdout(),
+ 'stderr' => $ex->getStderr(),
+ );
+
+ $lease->setAttribute('workingcopy.vcs.error', $error);
+ }
+
+ public function getWorkingCopyVCSError(DrydockLease $lease) {
+ $error = $lease->getAttribute('workingcopy.vcs.error');
+ if (!$error) {
+ return null;
+ } else {
+ return $error;
+ }
+ }
}
diff --git a/src/applications/drydock/storage/DrydockRepositoryOperation.php b/src/applications/drydock/storage/DrydockRepositoryOperation.php
index 6d7151b25d..c6fc383193 100644
--- a/src/applications/drydock/storage/DrydockRepositoryOperation.php
+++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php
@@ -1,204 +1,212 @@
<?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;
private $implementation = 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 attachImplementation(DrydockRepositoryOperationType $impl) {
$this->implementation = $impl;
return $this;
}
public function getImplementation() {
return $this->implementation;
}
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,
));
}
public function applyOperation(DrydockInterface $interface) {
return $this->getImplementation()->applyOperation(
$this,
$interface);
}
public function getOperationDescription(PhabricatorUser $viewer) {
return $this->getImplementation()->getOperationDescription(
$this,
$viewer);
}
public function getOperationCurrentStatus(PhabricatorUser $viewer) {
return $this->getImplementation()->getOperationCurrentStatus(
$this,
$viewer);
}
public function isUnderway() {
switch ($this->getOperationState()) {
case self::STATE_WAIT:
case self::STATE_WORK:
return true;
}
return false;
}
public function isDone() {
return ($this->getOperationState() === self::STATE_DONE);
}
public function getWorkingCopyMerges() {
return $this->getImplementation()->getWorkingCopyMerges(
$this);
}
public function setWorkingCopyLeasePHID($lease_phid) {
return $this->setProperty('exec.leasePHID', $lease_phid);
}
public function getWorkingCopyLeasePHID() {
return $this->getProperty('exec.leasePHID');
}
+ public function setWorkingCopyVCSError(array $error) {
+ return $this->setProperty('exec.workingcopy.error', $error);
+ }
+
+ public function getWorkingCopyVCSError() {
+ return $this->getProperty('exec.workingcopy.error');
+ }
+
/* -( 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.');
}
}
diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php
index 3bdb529c20..2638926816 100644
--- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php
+++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php
@@ -1,84 +1,176 @@
<?php
final class DrydockRepositoryOperationStatusView
extends AphrontView {
private $operation;
private $boxView;
public function setOperation(DrydockRepositoryOperation $operation) {
$this->operation = $operation;
return $this;
}
public function getOperation() {
return $this->operation;
}
public function setBoxView(PHUIObjectBoxView $box_view) {
$this->boxView = $box_view;
return $this;
}
public function getBoxView() {
return $this->boxView;
}
public function render() {
$viewer = $this->getUser();
$operation = $this->getOperation();
$list = $this->renderUnderwayState();
// If the operation is currently underway, refresh the status view.
if ($operation->isUnderway()) {
$status_id = celerity_generate_unique_node_id();
$id = $operation->getID();
$list->setID($status_id);
Javelin::initBehavior(
'drydock-live-operation-status',
array(
'statusID' => $status_id,
'updateURI' => "/drydock/operation/{$id}/status/",
));
}
$box_view = $this->getBoxView();
if (!$box_view) {
$box_view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Operation Status'));
}
$box_view->setObjectList($list);
return $box_view;
}
public function renderUnderwayState() {
$viewer = $this->getUser();
$operation = $this->getOperation();
$id = $operation->getID();
$state = $operation->getOperationState();
$icon = DrydockRepositoryOperation::getOperationStateIcon($state);
$name = DrydockRepositoryOperation::getOperationStateName($state);
$item = id(new PHUIObjectItemView())
->setHref("/drydock/operation/{$id}/")
->setHeader($operation->getOperationDescription($viewer))
->setStatusIcon($icon, $name);
if ($state != DrydockRepositoryOperation::STATE_FAIL) {
$item->addAttribute($operation->getOperationCurrentStatus($viewer));
} else {
- // TODO: Make this more useful.
- $item->addAttribute(pht('Operation encountered an error.'));
+ $vcs_error = $operation->getWorkingCopyVCSError();
+ if ($vcs_error) {
+ switch ($vcs_error['phase']) {
+ case DrydockWorkingCopyBlueprintImplementation::PHASE_SQUASHMERGE:
+ $message = pht(
+ 'This change did not merge cleanly. This usually indicates '.
+ 'that the change is out of date and needs to be updated.');
+ break;
+ default:
+ $message = pht(
+ 'Operation encountered an error while performing repository '.
+ 'operations.');
+ break;
+ }
+
+ $item->addAttribute($message);
+
+ $table = $this->renderVCSErrorTable($vcs_error);
+ list($links, $info) = $this->renderDetailToggles($table);
+
+ $item->addAttribute($links);
+ $item->appendChild($info);
+ } else {
+ $item->addAttribute(pht('Operation encountered an error.'));
+ }
}
return id(new PHUIObjectItemListView())
->addItem($item);
}
+ private function renderVCSErrorTable(array $vcs_error) {
+ $rows = array();
+ $rows[] = array(pht('Command'), $vcs_error['command']);
+ $rows[] = array(pht('Error'), $vcs_error['err']);
+ $rows[] = array(pht('Stdout'), $vcs_error['stdout']);
+ $rows[] = array(pht('Stderr'), $vcs_error['stderr']);
+
+ $table = id(new AphrontTableView($rows))
+ ->setColumnClasses(
+ array(
+ 'header',
+ 'wide',
+ ));
+
+ return $table;
+ }
+
+ private function renderDetailToggles(AphrontTableView $table) {
+ $show_id = celerity_generate_unique_node_id();
+ $hide_id = celerity_generate_unique_node_id();
+ $info_id = celerity_generate_unique_node_id();
+
+ Javelin::initBehavior('phabricator-reveal-content');
+
+ $show_details = javelin_tag(
+ 'a',
+ array(
+ 'id' => $show_id,
+ 'href' => '#',
+ 'sigil' => 'reveal-content',
+ 'mustcapture' => true,
+ 'meta' => array(
+ 'hideIDs' => array($show_id),
+ 'showIDs' => array($hide_id, $info_id),
+ ),
+ ),
+ pht('Show Details'));
+
+ $hide_details = javelin_tag(
+ 'a',
+ array(
+ 'id' => $hide_id,
+ 'href' => '#',
+ 'sigil' => 'reveal-content',
+ 'mustcapture' => true,
+ 'style' => 'display: none',
+ 'meta' => array(
+ 'hideIDs' => array($hide_id, $info_id),
+ 'showIDs' => array($show_id),
+ ),
+ ),
+ pht('Hide Details'));
+
+ $info = javelin_tag(
+ 'div',
+ array(
+ 'id' => $info_id,
+ 'style' => 'display: none',
+ ),
+ $table);
+
+ $links = array(
+ $show_details,
+ $hide_details,
+ );
+
+ return array($links, $info);
+ }
+
}
diff --git a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php
index a0dc1e5ebd..f6e8b55e74 100644
--- a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php
+++ b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php
@@ -1,179 +1,186 @@
<?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) {
$viewer = $this->getViewer();
$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 {
$operation->getImplementation()
->setViewer($viewer);
$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);
$operation->applyOperation($interface);
} 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?
+ $working_copy = new DrydockWorkingCopyBlueprintImplementation();
+ $working_copy_type = $working_copy->getType();
+
$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
->setWorkingCopyLeasePHID($lease->getPHID())
->save();
$lease->queueForActivation();
}
if ($lease->isActivating()) {
throw new PhabricatorWorkerYieldException(15);
}
if (!$lease->isActive()) {
+ $vcs_error = $working_copy->getWorkingCopyVCSError($lease);
+ if ($vcs_error) {
+ $operation
+ ->setWorkingCopyVCSError($vcs_error)
+ ->save();
+ }
+
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));
}
$spec['merges'] = $operation->getWorkingCopyMerges();
$map = array();
$map[$repository->getCloneName()] = array(
'phid' => $repository->getPHID(),
'default' => true,
) + $spec;
return $map;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 14:42 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125613
Default Alt Text
(30 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment