mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-21 22:32:41 +01:00
Refactor how Mercurial runs commands that require extensions
Summary: Currently there are a number of `--config extensions.blah=` sprinkled throughout the Mercurial APIs which aren't pleasant to look at. Additionally it turns out the `rebase` command requires the `rebase` extension which is not turned on by default. Uses of `rebase` should also be including this flag to enable the extension when it's in use. This adds `ArcanistMercurialAPI::buildMercurialExtensionCommand()` which allows running a Mercurial command that requires an extension just by naming the extension instead of providing the full `--config..` argument. It can be used e.g. ```lang=php $api->execxLocalWithExtension( 'strip', 'strip --rev %s --', $rev); $api->execxLocalWithExtension( 'arc-hg', 'arc-amend --logfile %s', $tmp_file); ``` Which produces ```lang=console $ hg --encoding utf-8 --config 'extensions.strip=' strip --rev 82567759e2d703e1e0497f5f41363de3a1500188 -- $ hg --encoding utf-8 --config 'extensions.arg-hg=/Users/cspeckrun/Source/phacility/arcanist/support/hg/arc-hg.py' arc-amend --logfile /private/var/folders/cg/364w44254v5767ydf_x1tg_80000gn/T/6bwck66ccx0kwskw/89989-5F8JaL ``` Refs T13659 Test Plan: I ran through several scenarios of running `arc diff` and `arc land` with uncommitted changes on non-head commits, while not having the `evolve` extension enabled. Using the `--trace` argument I verified that `rebase`, `strip`, `arc-amend`, and `arc-ls-markers` were all invoked and the corresponding `arc diff` and `arc land` commands succeeded. I also ran `arc land --onto-remote default` while `arc-hg.py` raised an exception for the "remote" case and verified that the error was caught by Arcanist and displayed the error and prevented further execution. I removed the error from `arc-hg.py` and re-ran the command and verified it succeeded. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T13659 Differential Revision: https://secure.phabricator.com/D21697
This commit is contained in:
parent
35c1b9bf02
commit
ac365c3ee5
4 changed files with 165 additions and 63 deletions
|
@ -1160,8 +1160,9 @@ final class ArcanistMercurialLandEngine
|
|||
'prune --rev %s',
|
||||
$rev_set);
|
||||
} else {
|
||||
$api->execxLocal(
|
||||
'--config extensions.strip= strip --rev %s',
|
||||
$api->execxLocalWithExtension(
|
||||
'strip',
|
||||
'strip --rev %s',
|
||||
$rev_set);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
*/
|
||||
final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||
|
||||
/**
|
||||
* Mercurial deceptively indicates that the default encoding is UTF-8 however
|
||||
* however the actual default appears to be "something else", at least on
|
||||
* Windows systems. Force all mercurial commands to use UTF-8 encoding.
|
||||
*/
|
||||
const ROOT_HG_COMMAND = 'hg --encoding utf-8 ';
|
||||
|
||||
private $branch;
|
||||
private $localCommitInfo;
|
||||
private $rawDiffCache = array();
|
||||
|
@ -13,28 +20,24 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
private $featureFutures = array();
|
||||
|
||||
protected function buildLocalFuture(array $argv) {
|
||||
$env = $this->getMercurialEnvironmentVariables();
|
||||
$argv[0] = self::ROOT_HG_COMMAND.$argv[0];
|
||||
|
||||
// Mercurial deceptively indicates that the default encoding is UTF-8
|
||||
// however the actual default appears to be "something else", at least on
|
||||
// Windows systems. Force all mercurial commands to use UTF-8 encoding.
|
||||
$argv[0] = 'hg --encoding utf-8 '.$argv[0];
|
||||
|
||||
$future = newv('ExecFuture', $argv)
|
||||
->setEnv($env)
|
||||
->setCWD($this->getPath());
|
||||
|
||||
return $future;
|
||||
return $this->newConfiguredFuture(newv('ExecFuture', $argv));
|
||||
}
|
||||
|
||||
public function newPassthru($pattern /* , ... */) {
|
||||
$args = func_get_args();
|
||||
$args[0] = self::ROOT_HG_COMMAND.$args[0];
|
||||
|
||||
return $this->newConfiguredFuture(newv('PhutilExecPassthru', $args));
|
||||
}
|
||||
|
||||
private function newConfiguredFuture(PhutilExecutableFuture $future) {
|
||||
$args = func_get_args();
|
||||
|
||||
$env = $this->getMercurialEnvironmentVariables();
|
||||
|
||||
$args[0] = 'hg '.$args[0];
|
||||
|
||||
return newv('PhutilExecPassthru', $args)
|
||||
return $future
|
||||
->setEnv($env)
|
||||
->setCWD($this->getPath());
|
||||
}
|
||||
|
@ -730,14 +733,10 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
'log --template %s --rev . --',
|
||||
'{node}');
|
||||
|
||||
$argv = array();
|
||||
foreach ($this->getMercurialExtensionArguments() as $arg) {
|
||||
$argv[] = $arg;
|
||||
}
|
||||
$argv[] = 'arc-amend';
|
||||
$argv[] = '--logfile';
|
||||
$argv[] = $tmp_file;
|
||||
$this->execxLocal('%Ls', $argv);
|
||||
$this->execxLocalWithExtension(
|
||||
'arc-hg',
|
||||
'arc-amend --logfile %s',
|
||||
$tmp_file);
|
||||
|
||||
list($new_commit) = $this->execxLocal(
|
||||
'log --rev tip --template %s --',
|
||||
|
@ -753,13 +752,20 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
$rebase_args[] = $child;
|
||||
}
|
||||
|
||||
$this->execxLocal('rebase %Ls --', $rebase_args);
|
||||
$this->execxLocalWithExtension(
|
||||
'rebase',
|
||||
'rebase %Ls --',
|
||||
$rebase_args);
|
||||
} catch (CommandException $ex) {
|
||||
$this->execxLocal('rebase --abort --');
|
||||
$this->execxLocalWithExtension(
|
||||
'rebase',
|
||||
'rebase --abort --');
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$this->execxLocal('--config extensions.strip= strip --rev %s --',
|
||||
$this->execxLocalWithExtension(
|
||||
'strip',
|
||||
'strip --rev %s --',
|
||||
$current);
|
||||
}
|
||||
|
||||
|
@ -1034,6 +1040,115 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
return $this->executeMercurialFeatureTest($feature, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the necessary flag for using a Mercurial extension. This will
|
||||
* enable Mercurial built-in extensions and the "arc-hg" extension that is
|
||||
* included with Arcanist. This will not enable other extensions, e.g.
|
||||
* "evolve".
|
||||
*
|
||||
* @param string The name of the extension to enable.
|
||||
* @return string A new command pattern that includes the necessary flags to
|
||||
* enable the specified extension.
|
||||
*/
|
||||
private function getMercurialExtensionFlag($extension) {
|
||||
switch ($extension) {
|
||||
case 'arc-hg':
|
||||
$path = phutil_get_library_root('arcanist');
|
||||
$path = dirname($path);
|
||||
$path = $path.'/support/hg/arc-hg.py';
|
||||
$ext_config = 'extensions.arg-hg='.$path;
|
||||
break;
|
||||
case 'rebase':
|
||||
$ext_config = 'extensions.rebase=';
|
||||
break;
|
||||
case 'shelve':
|
||||
$ext_config = 'extensions.shelve=';
|
||||
break;
|
||||
case 'strip':
|
||||
$ext_config = 'extensions.strip=';
|
||||
break;
|
||||
default:
|
||||
throw new Exception(
|
||||
pht('Unknown Mercurial Extension: "%s".', $extension));
|
||||
}
|
||||
|
||||
return csprintf('--config %s', $ext_config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the arguments that should be passed to Mercurial command
|
||||
* execution that enables a desired extension.
|
||||
*
|
||||
* @param string The name of the extension to enable.
|
||||
* @param string The command pattern that will be run with the extension
|
||||
* enabled.
|
||||
* @param array Parameters for the command pattern argument.
|
||||
* @return array An array where the first item is a Mercurial command
|
||||
* pattern that includes the necessary flag for enabling the
|
||||
* desired extension, and all remaining items are parameters
|
||||
* to that command pattern.
|
||||
*/
|
||||
private function buildMercurialExtensionCommand(
|
||||
$extension,
|
||||
$pattern /* , ... */) {
|
||||
|
||||
$args = func_get_args();
|
||||
|
||||
$pattern_args = array_slice($args, 2);
|
||||
|
||||
$ext_flag = $this->getMercurialExtensionFlag($extension);
|
||||
|
||||
$full_cmd = $ext_flag.' '.$pattern;
|
||||
|
||||
$args = array_merge(
|
||||
array($full_cmd),
|
||||
$pattern_args);
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
public function execxLocalWithExtension(
|
||||
$extension,
|
||||
$pattern /* , ... */) {
|
||||
|
||||
$args = func_get_args();
|
||||
$extended_args = call_user_func_array(
|
||||
array($this, 'buildMercurialExtensionCommand'),
|
||||
$args);
|
||||
|
||||
return call_user_func_array(
|
||||
array($this, 'execxLocal'),
|
||||
$extended_args);
|
||||
}
|
||||
|
||||
public function execFutureLocalWithExtension(
|
||||
$extension,
|
||||
$pattern /* , ... */) {
|
||||
|
||||
$args = func_get_args();
|
||||
$extended_args = call_user_func_array(
|
||||
array($this, 'buildMercurialExtensionCommand'),
|
||||
$args);
|
||||
|
||||
return call_user_func_array(
|
||||
array($this, 'execFutureLocal'),
|
||||
$extended_args);
|
||||
}
|
||||
|
||||
public function execPassthruWithExtension(
|
||||
$extension,
|
||||
$pattern /* , ... */) {
|
||||
|
||||
$args = func_get_args();
|
||||
$extended_args = call_user_func_array(
|
||||
array($this, 'buildMercurialExtensionCommand'),
|
||||
$args);
|
||||
|
||||
return call_user_func_array(
|
||||
array($this, 'execPassthru'),
|
||||
$extended_args);
|
||||
}
|
||||
|
||||
private function executeMercurialFeatureTest($feature, $resolve) {
|
||||
if (array_key_exists($feature, $this->featureResults)) {
|
||||
return $this->featureResults[$feature];
|
||||
|
@ -1059,8 +1174,9 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
private function newMercurialFeatureFuture($feature) {
|
||||
switch ($feature) {
|
||||
case 'shelve':
|
||||
return $this->execFutureLocal(
|
||||
'--config extensions.shelve= shelve --help --');
|
||||
return $this->execFutureLocalWithExtension(
|
||||
'shelve',
|
||||
'shelve --help --');
|
||||
case 'evolve':
|
||||
return $this->execFutureLocal('prune --help --');
|
||||
default:
|
||||
|
@ -1094,17 +1210,6 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
return new ArcanistMercurialRepositoryRemoteQuery();
|
||||
}
|
||||
|
||||
public function getMercurialExtensionArguments() {
|
||||
$path = phutil_get_library_root('arcanist');
|
||||
$path = dirname($path);
|
||||
$path = $path.'/support/hg/arc-hg.py';
|
||||
|
||||
return array(
|
||||
'--config',
|
||||
'extensions.arc-hg='.$path,
|
||||
);
|
||||
}
|
||||
|
||||
protected function newNormalizedURI($uri) {
|
||||
return new ArcanistRepositoryURINormalizer(
|
||||
ArcanistRepositoryURINormalizer::TYPE_MERCURIAL,
|
||||
|
|
|
@ -19,12 +19,6 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
// to provide a command which works like "git for-each-ref" locally and
|
||||
// "git ls-remote" when given a remote.
|
||||
|
||||
$argv = array();
|
||||
foreach ($api->getMercurialExtensionArguments() as $arg) {
|
||||
$argv[] = $arg;
|
||||
}
|
||||
$argv[] = 'arc-ls-markers';
|
||||
|
||||
// NOTE: In remote mode, we're using passthru and a tempfile on this
|
||||
// because it's a remote command and may prompt the user to provide
|
||||
// credentials interactively. In local mode, we can just read stdout.
|
||||
|
@ -33,20 +27,17 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
$tmpfile = new TempFile();
|
||||
Filesystem::remove($tmpfile);
|
||||
|
||||
$argv = array();
|
||||
$argv[] = '--output';
|
||||
$argv[] = phutil_string_cast($tmpfile);
|
||||
}
|
||||
|
||||
$argv[] = '--';
|
||||
|
||||
if ($remote !== null) {
|
||||
$argv[] = '--';
|
||||
$argv[] = $remote->getRemoteName();
|
||||
}
|
||||
|
||||
if ($remote !== null) {
|
||||
$passthru = $api->newPassthru('%Ls', $argv);
|
||||
$err = $api->execPassthruWithExtension(
|
||||
'arc-hg',
|
||||
'arc-ls-markers %Ls',
|
||||
$argv);
|
||||
|
||||
$err = $passthru->execute();
|
||||
if ($err) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -57,8 +48,10 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
$raw_data = Filesystem::readFile($tmpfile);
|
||||
unset($tmpfile);
|
||||
} else {
|
||||
$future = $api->newFuture('%Ls', $argv);
|
||||
list($raw_data) = $future->resolve();
|
||||
$future = $api->execFutureLocalWithExtension(
|
||||
'arc-hg',
|
||||
'arc-ls-markers --');
|
||||
list($err, $raw_data) = $future->resolve();
|
||||
}
|
||||
|
||||
$items = phutil_json_decode($raw_data);
|
||||
|
|
|
@ -160,8 +160,9 @@ final class ArcanistMercurialLocalState
|
|||
'arc-%s',
|
||||
Filesystem::readRandomCharacters(12));
|
||||
|
||||
$api->execxLocal(
|
||||
'--config extensions.shelve= shelve --unknown --name %s --',
|
||||
$api->execxLocalWithExtension(
|
||||
'shelve',
|
||||
'shelve --unknown --name %s --',
|
||||
$stash_ref);
|
||||
|
||||
$log->writeStatus(
|
||||
|
@ -179,16 +180,18 @@ final class ArcanistMercurialLocalState
|
|||
pht('UNSHELVE'),
|
||||
pht('Restoring uncommitted changes to working copy.'));
|
||||
|
||||
$api->execxLocal(
|
||||
'--config extensions.shelve= unshelve --keep --name %s --',
|
||||
$api->execxLocalWithExtension(
|
||||
'shelve',
|
||||
'unshelve --keep --name %s --',
|
||||
$stash_ref);
|
||||
}
|
||||
|
||||
protected function discardStash($stash_ref) {
|
||||
$api = $this->getRepositoryAPI();
|
||||
|
||||
$api->execxLocal(
|
||||
'--config extensions.shelve= shelve --delete %s --',
|
||||
$api->execxLocalWithExtension(
|
||||
'shelve',
|
||||
'shelve --delete %s --',
|
||||
$stash_ref);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue