mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-23 22:10:55 +01:00
Make WorkingCopyBlueprint responsible for performing merges
Summary: Ref T182. Currently, the "RepositoryLand" operation is responsible for performing merges when landing a revision. However, we'd like to be able to perform these merges in a larger set of cases in the future. For example: - After Releeph is revamped, when someone says "I want to merge bug fix X into stable branch Y", it would probably be nice to make that a Buildable and let tests run against it without requring that it actually be pushed anywhere. - Same deal if we want a merge-from-Diffusion or cherry-pick-from-Diffusion operation. - Similar deal if we want a "random web UI edits from Diffusion". Move the merging part into WorkingCopy so more applications can share/use it in the future. A big chunk of this is me making stuff up for now (the ol' undocumented dictionary full of arbitrary magic keys), but I anticipate formalizing it as we move along. Test Plan: Pushed rGITTEST0d58eef3ce0fa5a10732d2efefc56aec126bc219 up from my local install via "Land Revision". Reviewers: chad Reviewed By: chad Maniphest Tasks: T182 Differential Revision: https://secure.phabricator.com/D14337
This commit is contained in:
parent
c059149eb9
commit
9c39493796
6 changed files with 138 additions and 65 deletions
|
@ -237,8 +237,7 @@ final class DrydockWorkingCopyBlueprintImplementation
|
|||
$cmd = array();
|
||||
$arg = array();
|
||||
|
||||
$cmd[] = 'cd %s';
|
||||
$arg[] = "{$root}/repo/{$directory}/";
|
||||
$interface->pushWorkingDirectory("{$root}/repo/{$directory}/");
|
||||
|
||||
$cmd[] = 'git clean -d --force';
|
||||
$cmd[] = 'git fetch';
|
||||
|
@ -285,6 +284,15 @@ final class DrydockWorkingCopyBlueprintImplementation
|
|||
if (idx($spec, 'default')) {
|
||||
$default = $directory;
|
||||
}
|
||||
|
||||
$merges = idx($spec, 'merges');
|
||||
if ($merges) {
|
||||
foreach ($merges as $merge) {
|
||||
$this->applyMerge($interface, $merge);
|
||||
}
|
||||
}
|
||||
|
||||
$interface->popWorkingDirectory();
|
||||
}
|
||||
|
||||
if ($default === null) {
|
||||
|
@ -333,7 +341,7 @@ final class DrydockWorkingCopyBlueprintImplementation
|
|||
$command_interface = $host_lease->getInterface($type);
|
||||
|
||||
$path = $lease->getAttribute('workingcopy.default');
|
||||
$command_interface->setWorkingDirectory($path);
|
||||
$command_interface->pushWorkingDirectory($path);
|
||||
|
||||
return $command_interface;
|
||||
}
|
||||
|
@ -407,5 +415,28 @@ final class DrydockWorkingCopyBlueprintImplementation
|
|||
return true;
|
||||
}
|
||||
|
||||
private function applyMerge(
|
||||
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);
|
||||
|
||||
try {
|
||||
$interface->execx(
|
||||
'git merge --no-stat --squash --ff-only -- %s',
|
||||
$src_ref);
|
||||
} catch (CommandException $ex) {
|
||||
// TODO: Specifically note this as a merge conflict.
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -4,15 +4,27 @@ abstract class DrydockCommandInterface extends DrydockInterface {
|
|||
|
||||
const INTERFACE_TYPE = 'command';
|
||||
|
||||
private $workingDirectory;
|
||||
private $workingDirectoryStack = array();
|
||||
|
||||
public function setWorkingDirectory($working_directory) {
|
||||
$this->workingDirectory = $working_directory;
|
||||
public function pushWorkingDirectory($working_directory) {
|
||||
$this->workingDirectoryStack[] = $working_directory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWorkingDirectory() {
|
||||
return $this->workingDirectory;
|
||||
public function popWorkingDirectory() {
|
||||
if (!$this->workingDirectoryStack) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to pop working directory, directory stack is empty.'));
|
||||
}
|
||||
return array_pop($this->workingDirectoryStack);
|
||||
}
|
||||
|
||||
public function peekWorkingDirectory() {
|
||||
if ($this->workingDirectoryStack) {
|
||||
return last($this->workingDirectoryStack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final public function getInterfaceType() {
|
||||
|
@ -38,12 +50,14 @@ abstract class DrydockCommandInterface extends DrydockInterface {
|
|||
abstract public function getExecFuture($command);
|
||||
|
||||
protected function applyWorkingDirectoryToArgv(array $argv) {
|
||||
if ($this->getWorkingDirectory() !== null) {
|
||||
$directory = $this->peekWorkingDirectory();
|
||||
|
||||
if ($directory !== null) {
|
||||
$cmd = $argv[0];
|
||||
$cmd = "(cd %s && {$cmd})";
|
||||
$argv = array_merge(
|
||||
array($cmd),
|
||||
array($this->getWorkingDirectory()),
|
||||
array($directory),
|
||||
array_slice($argv, 1));
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,28 @@ final class DrydockLandRepositoryOperation
|
|||
}
|
||||
}
|
||||
|
||||
public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) {
|
||||
$repository = $operation->getRepository();
|
||||
$merges = array();
|
||||
|
||||
$object = $operation->getObject();
|
||||
if ($object instanceof DifferentialRevision) {
|
||||
$diff = $this->loadDiff($operation);
|
||||
$merges[] = array(
|
||||
'src.uri' => $repository->getStagingURI(),
|
||||
'src.ref' => $diff->getStagingRef(),
|
||||
);
|
||||
} else {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Invalid or unknown object ("%s") for land operation, expected '.
|
||||
'Differential Revision.',
|
||||
$operation->getObjectPHID()));
|
||||
}
|
||||
|
||||
return $merges;
|
||||
}
|
||||
|
||||
public function applyOperation(
|
||||
DrydockRepositoryOperation $operation,
|
||||
DrydockInterface $interface) {
|
||||
|
@ -48,36 +70,7 @@ final class DrydockLandRepositoryOperation
|
|||
if ($object instanceof DifferentialRevision) {
|
||||
$revision = $object;
|
||||
|
||||
$diff_phid = $operation->getProperty('differential.diffPHID');
|
||||
|
||||
$diff = id(new DifferentialDiffQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($diff_phid))
|
||||
->executeOne();
|
||||
if (!$diff) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to load diff "%s".',
|
||||
$diff_phid));
|
||||
}
|
||||
|
||||
$diff_revid = $diff->getRevisionID();
|
||||
$revision_id = $revision->getID();
|
||||
if ($diff_revid != $revision_id) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Diff ("%s") has wrong revision ID ("%s", expected "%s").',
|
||||
$diff_phid,
|
||||
$diff_revid,
|
||||
$revision_id));
|
||||
}
|
||||
|
||||
$cmd[] = 'git fetch --no-tags -- %s +%s:%s';
|
||||
$arg[] = $repository->getStagingURI();
|
||||
$arg[] = $diff->getStagingRef();
|
||||
$arg[] = $diff->getStagingRef();
|
||||
|
||||
$merge_src = $diff->getStagingRef();
|
||||
$diff = $this->loadDiff($operation);
|
||||
|
||||
$dict = $diff->getDiffAuthorshipDict();
|
||||
$author_name = idx($dict, 'authorName');
|
||||
|
@ -104,7 +97,6 @@ final class DrydockLandRepositoryOperation
|
|||
switch ($type) {
|
||||
case 'branch':
|
||||
$push_dst = 'refs/heads/'.$name;
|
||||
$merge_dst = 'refs/remotes/origin/'.$name;
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
|
@ -116,30 +108,24 @@ final class DrydockLandRepositoryOperation
|
|||
|
||||
$committer_info = $this->getCommitterInfo($operation);
|
||||
|
||||
$cmd[] = 'git checkout %s';
|
||||
$arg[] = $merge_dst;
|
||||
// NOTE: We're doing this commit with "-F -" so we don't run into trouble
|
||||
// with enormous commit messages which might otherwise exceed the maximum
|
||||
// size of a command.
|
||||
|
||||
$cmd[] = 'git merge --no-stat --squash --ff-only -- %s';
|
||||
$arg[] = $merge_src;
|
||||
$future = $interface->getExecFuture(
|
||||
'git -c user.name=%s -c user.email=%s commit --author %s -F - --',
|
||||
$committer_info['name'],
|
||||
$committer_info['email'],
|
||||
"{$author_name} <{$author_email}>");
|
||||
|
||||
$cmd[] = 'git -c user.name=%s -c user.email=%s commit --author %s -m %s';
|
||||
$future
|
||||
->write($commit_message)
|
||||
->resolvex();
|
||||
|
||||
$arg[] = $committer_info['name'];
|
||||
$arg[] = $committer_info['email'];
|
||||
|
||||
$arg[] = "{$author_name} <{$author_email}>";
|
||||
$arg[] = $commit_message;
|
||||
|
||||
$cmd[] = 'git push origin -- %s:%s';
|
||||
$arg[] = 'HEAD';
|
||||
$arg[] = $push_dst;
|
||||
|
||||
$cmd = implode(' && ', $cmd);
|
||||
$argv = array_merge(array($cmd), $arg);
|
||||
|
||||
$result = call_user_func_array(
|
||||
array($interface, 'execx'),
|
||||
$argv);
|
||||
$interface->execx(
|
||||
'git push origin -- %s:%s',
|
||||
'HEAD',
|
||||
$push_dst);
|
||||
}
|
||||
|
||||
private function getCommitterInfo(DrydockRepositoryOperation $operation) {
|
||||
|
@ -172,4 +158,35 @@ final class DrydockLandRepositoryOperation
|
|||
);
|
||||
}
|
||||
|
||||
private function loadDiff(DrydockRepositoryOperation $operation) {
|
||||
$viewer = $this->getViewer();
|
||||
$revision = $operation->getObject();
|
||||
|
||||
$diff_phid = $operation->getProperty('differential.diffPHID');
|
||||
|
||||
$diff = id(new DifferentialDiffQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($diff_phid))
|
||||
->executeOne();
|
||||
if (!$diff) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to load diff "%s".',
|
||||
$diff_phid));
|
||||
}
|
||||
|
||||
$diff_revid = $diff->getRevisionID();
|
||||
$revision_id = $revision->getID();
|
||||
if ($diff_revid != $revision_id) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Diff ("%s") has wrong revision ID ("%s", expected "%s").',
|
||||
$diff_phid,
|
||||
$diff_revid,
|
||||
$revision_id));
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ abstract class DrydockRepositoryOperationType extends Phobject {
|
|||
DrydockRepositoryOperation $operation,
|
||||
PhabricatorUser $viewer);
|
||||
|
||||
public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) {
|
||||
return array();
|
||||
}
|
||||
|
||||
final public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
|
|
|
@ -158,6 +158,11 @@ final class DrydockRepositoryOperation extends DrydockDAO
|
|||
return false;
|
||||
}
|
||||
|
||||
public function getWorkingCopyMerges() {
|
||||
return $this->getImplementation()->getWorkingCopyMerges(
|
||||
$this);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
|
|
@ -53,6 +53,9 @@ final class DrydockRepositoryOperationUpdateWorker
|
|||
// waiting for a lease we're holding.
|
||||
|
||||
try {
|
||||
$operation->getImplementation()
|
||||
->setViewer($viewer);
|
||||
|
||||
$lease = $this->loadWorkingCopyLease($operation);
|
||||
|
||||
$interface = $lease->getInterface(
|
||||
|
@ -61,9 +64,6 @@ final class DrydockRepositoryOperationUpdateWorker
|
|||
// No matter what happens here, destroy the lease away once we're done.
|
||||
$lease->releaseOnDestruction(true);
|
||||
|
||||
$operation->getImplementation()
|
||||
->setViewer($viewer);
|
||||
|
||||
$operation->applyOperation($interface);
|
||||
|
||||
} catch (PhabricatorWorkerYieldException $ex) {
|
||||
|
@ -166,6 +166,8 @@ final class DrydockRepositoryOperationUpdateWorker
|
|||
$target));
|
||||
}
|
||||
|
||||
$spec['merges'] = $operation->getWorkingCopyMerges();
|
||||
|
||||
$map = array();
|
||||
$map[$repository->getCloneName()] = array(
|
||||
'phid' => $repository->getPHID(),
|
||||
|
|
Loading…
Reference in a new issue