diff --git a/src/land/engine/ArcanistMercurialLandEngine.php b/src/land/engine/ArcanistMercurialLandEngine.php --- a/src/land/engine/ArcanistMercurialLandEngine.php +++ b/src/land/engine/ArcanistMercurialLandEngine.php @@ -1160,8 +1160,9 @@ 'prune --rev %s', $rev_set); } else { - $api->execxLocal( - '--config extensions.strip= strip --rev %s', + $api->execxLocalWithExtension( + 'strip', + 'strip --rev %s', $rev_set); } } diff --git a/src/repository/api/ArcanistMercurialAPI.php b/src/repository/api/ArcanistMercurialAPI.php --- a/src/repository/api/ArcanistMercurialAPI.php +++ b/src/repository/api/ArcanistMercurialAPI.php @@ -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 @@ 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]; + return $this->newConfiguredFuture(newv('ExecFuture', $argv)); + } - $future = newv('ExecFuture', $argv) - ->setEnv($env) - ->setCWD($this->getPath()); + public function newPassthru($pattern /* , ... */) { + $args = func_get_args(); + $args[0] = self::ROOT_HG_COMMAND.$args[0]; - return $future; + return $this->newConfiguredFuture(newv('PhutilExecPassthru', $args)); } - public function newPassthru($pattern /* , ... */) { + 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 @@ '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 @@ $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 @@ 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 @@ 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 @@ 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, diff --git a/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php b/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php --- a/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php +++ b/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php @@ -19,12 +19,6 @@ // 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 @@ $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 @@ $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); diff --git a/src/repository/state/ArcanistMercurialLocalState.php b/src/repository/state/ArcanistMercurialLocalState.php --- a/src/repository/state/ArcanistMercurialLocalState.php +++ b/src/repository/state/ArcanistMercurialLocalState.php @@ -160,8 +160,9 @@ '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 @@ 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); }