src/land/UberArcanistStackGitLandEngine.php (113 lines of code) (raw):
<?php
final class UberArcanistStackGitLandEngine
extends ArcanistGitLandEngine {
private $revisionIdsInStackOrder;
private $mergedRef;
/**
* @return mixed
*/
public function getRevisionIdsInStackOrder() {
return $this->revisionIdsInStackOrder;
}
/**
* @param mixed $revisionIdsInStackOrder
*/
public function setRevisionIdsInStackOrder($revisions) {
$this->revisionIdsInStackOrder = $revisions;
return $this;
}
/**
* Helper method to allow running child workflow
* @param $workflow Workflow Name
* @param $params Arguments for workflow
* @param $err_title Error Title to be displayed
* @param $err_msg Error Message to be displayed
* @throws Exception
*/
private function runChildWorkflow($workflow, $params, $err_title, $err_msg) {
try {
$flow = $this->getWorkflow()->buildChildWorkflow($workflow, $params);
$err = $flow->run();
if ($err) {
$this->writeInfo($err_title, $err_msg.$err);
throw new ArcanistUserAbortException();
}
} catch (Exception $exp) {
echo pht("Failed executing workflow %s with args (%s).\n", $workflow,
implode(',', $params));
throw $exp;
}
}
/**
* Create a branch with base-revision corresponding to the passed argument
* @param $base_revision
* @return null|string
*/
private function createBranch($base_revision) {
$repository_api = $this->getRepositoryAPI();
$repository_api->reloadWorkingCopy();
$branch_name = $this->getBranchName();
if ($base_revision) {
$base_revision = $repository_api
->getCanonicalRevisionName($base_revision);
$repository_api
->execxLocal('checkout -b %s %s', $branch_name, $base_revision);
} else {
$repository_api->execxLocal('checkout -b %s', $branch_name);
}
$repository_api->reloadWorkingCopy();
return $branch_name;
}
/**
* Create a temporary branch name
* @return null|string
* @throws Exception
*/
private function getBranchName() {
$base_name = 'arcstack';
$repository_api = $this->getRepositoryAPI();
// Try 100 different branch names before giving up.
for ($i = 0; $i < 100; $i++) {
$proposed_name = $base_name.$i;
list($err) = $repository_api->execManualLocal(
'rev-parse --verify %s',
$proposed_name);
// no error means git rev-parse found a branch
if ($err) {
return $proposed_name;
}
}
throw new Exception(
pht(
'Arc was unable to automagically make a name for this patch. '.
'Please clean up your working copy and try again.'));
}
protected function updateWorkingCopy() {
$api = $this->getRepositoryAPI();
$tempBranch = $this->createBranch($this->getTargetOnto());
// apply changes from phabricator
foreach ($this->revisionIdsInStackOrder as $revision) {
$patch_args = array(
'--revision',
$revision,
'--nobranch',
'--skip-dependencies',
);
$this->runChildWorkflow(
'patch',
$patch_args,
pht('Patching revision D%s', $revision), 'Unable to patch revision');
}
try {
// if repo is ok with squashing then make simple merge
$api->execxLocal(
'merge --no-stat --no-commit --ff -- %s',
$tempBranch);
} catch (Exception $ex) {
$api->execManualLocal('merge --abort');
$api->execManualLocal('reset --hard HEAD --');
throw new Exception(
pht(
'Local "%s" does not merge cleanly into "%s". Merge or rebase '.
'local changes so they can merge cleanly.',
$tempBranch,
$this->getTargetFullRef()));
}
$this->getWorkflow()->didCommitMerge();
list($stdout) = $api->execxLocal(
'rev-parse --verify %s',
'HEAD');
$this->mergedRef = trim($stdout);
// delete temporary stack if merge and everything is fin
$api->execxLocal('checkout %s', $this->getSourceRef());
$api->execxLocal('branch -D %s', $tempBranch);
}
protected function pushChange() {
$api = $this->getRepositoryAPI();
$this->writeInfo(
pht('PUSHING'),
pht('Pushing changes to "%s".', $this->getTargetFullRef()));
$err = $api->execPassthru(
'push -- %s %s:%s',
$this->getTargetRemote(),
$this->mergedRef,
$this->getTargetOnto());
if ($err) {
throw new ArcanistUsageException(
pht(
'Push failed! Fix the error and run "%s" again.',
'arc land'));
}
}
}