diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -3,6 +3,8 @@ final class DrydockWorkingCopyBlueprintImplementation extends DrydockBlueprintImplementation { + const PHASE_SQUASHMERGE = 'squashmerge'; + public function isEnabled() { return true; } @@ -288,7 +290,7 @@ $merges = idx($spec, 'merges'); if ($merges) { foreach ($merges as $merge) { - $this->applyMerge($interface, $merge); + $this->applyMerge($lease, $interface, $merge); } } @@ -416,6 +418,7 @@ } private function applyMerge( + DrydockLease $lease, DrydockCommandInterface $interface, array $merge) { @@ -428,15 +431,48 @@ $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 --- a/src/applications/drydock/storage/DrydockRepositoryOperation.php +++ b/src/applications/drydock/storage/DrydockRepositoryOperation.php @@ -175,6 +175,14 @@ 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 )----------------------------------------- */ diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -73,12 +73,104 @@ 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 --- a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php @@ -90,6 +90,9 @@ // 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()) @@ -103,9 +106,6 @@ $lease_phid)); } } else { - $working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation()) - ->getType(); - $repository = $operation->getRepository(); $allowed_phids = $repository->getAutomationBlueprintPHIDs(); @@ -138,6 +138,13 @@ } 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.',