1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 04:20: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:
epriestley 2015-10-26 12:40:16 -07:00
parent c059149eb9
commit 9c39493796
6 changed files with 138 additions and 65 deletions

View file

@ -237,8 +237,7 @@ final class DrydockWorkingCopyBlueprintImplementation
$cmd = array(); $cmd = array();
$arg = array(); $arg = array();
$cmd[] = 'cd %s'; $interface->pushWorkingDirectory("{$root}/repo/{$directory}/");
$arg[] = "{$root}/repo/{$directory}/";
$cmd[] = 'git clean -d --force'; $cmd[] = 'git clean -d --force';
$cmd[] = 'git fetch'; $cmd[] = 'git fetch';
@ -285,6 +284,15 @@ final class DrydockWorkingCopyBlueprintImplementation
if (idx($spec, 'default')) { if (idx($spec, 'default')) {
$default = $directory; $default = $directory;
} }
$merges = idx($spec, 'merges');
if ($merges) {
foreach ($merges as $merge) {
$this->applyMerge($interface, $merge);
}
}
$interface->popWorkingDirectory();
} }
if ($default === null) { if ($default === null) {
@ -333,7 +341,7 @@ final class DrydockWorkingCopyBlueprintImplementation
$command_interface = $host_lease->getInterface($type); $command_interface = $host_lease->getInterface($type);
$path = $lease->getAttribute('workingcopy.default'); $path = $lease->getAttribute('workingcopy.default');
$command_interface->setWorkingDirectory($path); $command_interface->pushWorkingDirectory($path);
return $command_interface; return $command_interface;
} }
@ -407,5 +415,28 @@ final class DrydockWorkingCopyBlueprintImplementation
return true; 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;
}
}
} }

View file

@ -4,15 +4,27 @@ abstract class DrydockCommandInterface extends DrydockInterface {
const INTERFACE_TYPE = 'command'; const INTERFACE_TYPE = 'command';
private $workingDirectory; private $workingDirectoryStack = array();
public function setWorkingDirectory($working_directory) { public function pushWorkingDirectory($working_directory) {
$this->workingDirectory = $working_directory; $this->workingDirectoryStack[] = $working_directory;
return $this; return $this;
} }
public function getWorkingDirectory() { public function popWorkingDirectory() {
return $this->workingDirectory; 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() { final public function getInterfaceType() {
@ -38,12 +50,14 @@ abstract class DrydockCommandInterface extends DrydockInterface {
abstract public function getExecFuture($command); abstract public function getExecFuture($command);
protected function applyWorkingDirectoryToArgv(array $argv) { protected function applyWorkingDirectoryToArgv(array $argv) {
if ($this->getWorkingDirectory() !== null) { $directory = $this->peekWorkingDirectory();
if ($directory !== null) {
$cmd = $argv[0]; $cmd = $argv[0];
$cmd = "(cd %s && {$cmd})"; $cmd = "(cd %s && {$cmd})";
$argv = array_merge( $argv = array_merge(
array($cmd), array($cmd),
array($this->getWorkingDirectory()), array($directory),
array_slice($argv, 1)); array_slice($argv, 1));
} }

View file

@ -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( public function applyOperation(
DrydockRepositoryOperation $operation, DrydockRepositoryOperation $operation,
DrydockInterface $interface) { DrydockInterface $interface) {
@ -48,36 +70,7 @@ final class DrydockLandRepositoryOperation
if ($object instanceof DifferentialRevision) { if ($object instanceof DifferentialRevision) {
$revision = $object; $revision = $object;
$diff_phid = $operation->getProperty('differential.diffPHID'); $diff = $this->loadDiff($operation);
$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();
$dict = $diff->getDiffAuthorshipDict(); $dict = $diff->getDiffAuthorshipDict();
$author_name = idx($dict, 'authorName'); $author_name = idx($dict, 'authorName');
@ -104,7 +97,6 @@ final class DrydockLandRepositoryOperation
switch ($type) { switch ($type) {
case 'branch': case 'branch':
$push_dst = 'refs/heads/'.$name; $push_dst = 'refs/heads/'.$name;
$merge_dst = 'refs/remotes/origin/'.$name;
break; break;
default: default:
throw new Exception( throw new Exception(
@ -116,30 +108,24 @@ final class DrydockLandRepositoryOperation
$committer_info = $this->getCommitterInfo($operation); $committer_info = $this->getCommitterInfo($operation);
$cmd[] = 'git checkout %s'; // NOTE: We're doing this commit with "-F -" so we don't run into trouble
$arg[] = $merge_dst; // with enormous commit messages which might otherwise exceed the maximum
// size of a command.
$cmd[] = 'git merge --no-stat --squash --ff-only -- %s'; $future = $interface->getExecFuture(
$arg[] = $merge_src; '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']; $interface->execx(
$arg[] = $committer_info['email']; 'git push origin -- %s:%s',
'HEAD',
$arg[] = "{$author_name} <{$author_email}>"; $push_dst);
$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);
} }
private function getCommitterInfo(DrydockRepositoryOperation $operation) { 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;
}
} }

View file

@ -16,6 +16,10 @@ abstract class DrydockRepositoryOperationType extends Phobject {
DrydockRepositoryOperation $operation, DrydockRepositoryOperation $operation,
PhabricatorUser $viewer); PhabricatorUser $viewer);
public function getWorkingCopyMerges(DrydockRepositoryOperation $operation) {
return array();
}
final public function setViewer(PhabricatorUser $viewer) { final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer; $this->viewer = $viewer;
return $this; return $this;

View file

@ -158,6 +158,11 @@ final class DrydockRepositoryOperation extends DrydockDAO
return false; return false;
} }
public function getWorkingCopyMerges() {
return $this->getImplementation()->getWorkingCopyMerges(
$this);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -53,6 +53,9 @@ final class DrydockRepositoryOperationUpdateWorker
// waiting for a lease we're holding. // waiting for a lease we're holding.
try { try {
$operation->getImplementation()
->setViewer($viewer);
$lease = $this->loadWorkingCopyLease($operation); $lease = $this->loadWorkingCopyLease($operation);
$interface = $lease->getInterface( $interface = $lease->getInterface(
@ -61,9 +64,6 @@ final class DrydockRepositoryOperationUpdateWorker
// No matter what happens here, destroy the lease away once we're done. // No matter what happens here, destroy the lease away once we're done.
$lease->releaseOnDestruction(true); $lease->releaseOnDestruction(true);
$operation->getImplementation()
->setViewer($viewer);
$operation->applyOperation($interface); $operation->applyOperation($interface);
} catch (PhabricatorWorkerYieldException $ex) { } catch (PhabricatorWorkerYieldException $ex) {
@ -166,6 +166,8 @@ final class DrydockRepositoryOperationUpdateWorker
$target)); $target));
} }
$spec['merges'] = $operation->getWorkingCopyMerges();
$map = array(); $map = array();
$map[$repository->getCloneName()] = array( $map[$repository->getCloneName()] = array(
'phid' => $repository->getPHID(), 'phid' => $repository->getPHID(),