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(); @@ -15,10 +22,7 @@ protected function buildLocalFuture(array $argv) { $env = $this->getMercurialEnvironmentVariables(); - // 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]; + $argv[0] = self::ROOT_HG_COMMAND.$argv[0]; $future = newv('ExecFuture', $argv) ->setEnv($env) @@ -32,7 +36,7 @@ $env = $this->getMercurialEnvironmentVariables(); - $args[0] = 'hg '.$args[0]; + $args[0] = self::ROOT_HG_COMMAND.$args[0]; return newv('PhutilExecPassthru', $args) ->setEnv($env) @@ -730,14 +734,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 +753,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 +1041,59 @@ return $this->executeMercurialFeatureTest($feature, true); } + public function buildMercurialExtensionCommand($pattern /* , ... */) { + $args = func_get_args(); + + $extension = $args[0]; + $ext_pattern = $args[1]; + $ext_pattern_args = array_slice($args, 2); + + 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)); + } + + // Escape potential spaces in path to arg-hg.py + $config_arg = csprintf('--config %s', $ext_config); + + // Surely this is safe + $hg_cmd = vcsprintf($ext_pattern, $ext_pattern_args); + + return $config_arg.' '.$hg_cmd; + } + + public function execxLocalWithExtension($pattern /* , ... */) { + $raw_cmd = call_user_func_array( + array($this, 'buildMercurialExtensionCommand'), + func_get_args()); + + return $this->execxLocal($raw_cmd); + } + + public function execFutureLocalWithExtension($pattern /* , ... */) { + $raw_cmd = call_user_func_array( + array($this, 'buildMercurialExtensionCommand'), + func_get_args()); + + return $this->execFutureLocal($raw_cmd); + } + private function executeMercurialFeatureTest($feature, $resolve) { if (array_key_exists($feature, $this->featureResults)) { return $this->featureResults[$feature]; @@ -1059,8 +1119,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 +1155,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,18 +27,19 @@ $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); + list($config_pattern, $ext_config, $hg_command) = + ArcanistMercurialAPI::buildMercurialExtensionCommand( + 'arc-hg', + 'arc-ls-markers %Ls', + $argv); + + $passthru = $api->newPassthru($config_pattern, $ext_config, $hg_command); $err = $passthru->execute(); if ($err) { @@ -57,8 +52,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); }