diff --git a/scripts/arcanist.php b/scripts/arcanist.php index 18109e85..d9715153 100755 --- a/scripts/arcanist.php +++ b/scripts/arcanist.php @@ -189,13 +189,17 @@ try { $need_auth = $workflow->requiresAuthentication(); $need_repository_api = $workflow->requiresRepositoryAPI(); + $want_repository_api = $workflow->desiresRepositoryAPI(); + $want_working_copy = $workflow->desiresWorkingCopy() || + $want_repository_api; + $need_conduit = $need_conduit || $need_auth; $need_working_copy = $need_working_copy || $need_repository_api; - if ($need_working_copy) { - if (!$working_copy->getProjectRoot()) { + if ($need_working_copy || $want_working_copy) { + if ($need_working_copy && !$working_copy->getProjectRoot()) { throw new ArcanistUsageException( "This command must be run in a Git, Mercurial or Subversion working ". "copy."); @@ -267,7 +271,7 @@ try { $workflow->authenticateConduit(); } - if ($need_repository_api) { + if ($need_repository_api || ($want_repository_api && $working_copy)) { $repository_api = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity( $working_copy); $workflow->setRepositoryAPI($repository_api); diff --git a/src/workflow/ArcanistBaseWorkflow.php b/src/workflow/ArcanistBaseWorkflow.php index 18392d7d..403ad124 100644 --- a/src/workflow/ArcanistBaseWorkflow.php +++ b/src/workflow/ArcanistBaseWorkflow.php @@ -465,11 +465,18 @@ abstract class ArcanistBaseWorkflow { return false; } + public function desiresWorkingCopy() { + return false; + } public function requiresRepositoryAPI() { return false; } + public function desiresRepositoryAPI() { + return false; + } + public function setCommand($command) { $this->command = $command; return $this; @@ -1020,6 +1027,25 @@ abstract class ArcanistBaseWorkflow { self::writeUserConfigurationFile($config); } + public function readLocalArcConfig() { + $local = array(); + $file = $this->readScratchFile('config'); + if ($file) { + $local = json_decode($file, true); + } + + return $local; + } + + public function writeLocalArcConfig(array $config) { + $json_encoder = new PhutilJSON(); + $json = $json_encoder->encodeFormatted($config); + + $this->writeScratchFile('config', $json); + + return $this; + } + /** * Write a message to stderr so that '--json' flags or stdout which is meant diff --git a/src/workflow/ArcanistGetConfigWorkflow.php b/src/workflow/ArcanistGetConfigWorkflow.php index 8ba82ea2..3a889232 100644 --- a/src/workflow/ArcanistGetConfigWorkflow.php +++ b/src/workflow/ArcanistGetConfigWorkflow.php @@ -25,7 +25,7 @@ final class ArcanistGetConfigWorkflow extends ArcanistBaseWorkflow { public function getCommandSynopses() { return phutil_console_format(<<getArgument('argv'); - $config = self::readGlobalArcConfig(); + $configs = array( + 'global' => self::readGlobalArcConfig(), + 'local' => $this->readLocalArcConfig(), + ); if ($argv) { $keys = $argv; } else { - $keys = array_keys($config); + $keys = array_mergev(array_map('array_keys', $configs)); + $keys = array_unique($keys); sort($keys); } foreach ($keys as $key) { - $val = self::formatConfigValueForDisplay(idx($config, $key)); - echo "{$key} = {$val}\n"; + foreach ($configs as $name => $config) { + if ($name == 'global' || isset($config[$key])) { + $val = self::formatConfigValueForDisplay(idx($config, $key)); + echo "({$name}) {$key} = {$val}\n"; + } + } } return 0; diff --git a/src/workflow/ArcanistSetConfigWorkflow.php b/src/workflow/ArcanistSetConfigWorkflow.php index 6c415bad..fcc7cee8 100644 --- a/src/workflow/ArcanistSetConfigWorkflow.php +++ b/src/workflow/ArcanistSetConfigWorkflow.php @@ -25,7 +25,7 @@ final class ArcanistSetConfigWorkflow extends ArcanistBaseWorkflow { public function getCommandSynopses() { return phutil_console_format(<< array( 'help' => 'Show available configuration values.', ), + 'local' => array( + 'help' => 'Set a local config value instead of a global one', + ), '*' => 'argv', ); } + public function requiresRepositoryAPI() { + return $this->getArgument('local'); + } + public function run() { if ($this->getArgument('show')) { return $this->show(); @@ -64,7 +77,15 @@ EOTEXT "Specify a key and a value, or --show."); } - $config = self::readGlobalArcConfig(); + $is_local = $this->getArgument('local'); + + if ($is_local) { + $config = $this->readLocalArcConfig(); + $which = 'local'; + } else { + $config = self::readGlobalArcConfig(); + $which = 'global'; + } $key = $argv[0]; $val = $argv[1]; @@ -76,26 +97,34 @@ EOTEXT if (!strlen($val)) { unset($config[$key]); - self::writeGlobalArcConfig($config); + if ($is_local) { + $this->writeLocalArcConfig($config); + } else { + self::writeGlobalArcConfig($config); + } if ($old === null) { - echo "Deleted key '{$key}'.\n"; + echo "Deleted key '{$key}' from {$which} config.\n"; } else { - echo "Deleted key '{$key}' (was '{$old}').\n"; + echo "Deleted key '{$key}' from {$which} config (was '{$old}').\n"; } } else { $val = $this->parse($key, $val); $config[$key] = $val; - self::writeGlobalArcConfig($config); + if ($is_local) { + $this->writeLocalArcConfig($config); + } else { + self::writeGlobalArcConfig($config); + } $val = self::formatConfigValueForDisplay($val); $old = self::formatConfigValueForDisplay($old); if ($old === null) { - echo "Set key '{$key}' = '{$val}'.\n"; + echo "Set key '{$key}' = '{$val}' in {$which} config.\n"; } else { - echo "Set key '{$key}' = '{$val}' (was '{$old}').\n"; + echo "Set key '{$key}' = '{$val}' in {$which} config (was '{$old}').\n"; } } diff --git a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php index 5adae0c6..e862e169 100644 --- a/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php +++ b/src/workingcopyidentity/ArcanistWorkingCopyIdentity.php @@ -26,6 +26,7 @@ */ final class ArcanistWorkingCopyIdentity { + protected $localConfig; protected $projectConfig; protected $projectRoot; @@ -97,6 +98,26 @@ final class ArcanistWorkingCopyIdentity { protected function __construct($root, array $config) { $this->projectRoot = $root; $this->projectConfig = $config; + $this->localConfig = array(); + + $vc_dirs = array( + '.git', + '.hg', + '.svn', + ); + foreach ($vc_dirs as $dir) { + $local_path = Filesystem::resolvePath( + $dir.'/arc/config', + $this->projectRoot); + if (Filesystem::pathExists($local_path)) { + $file = Filesystem::readFile($local_path); + if ($file) { + $this->localConfig = json_decode($file, true); + break; + } + } + } + } public function getProjectID() { @@ -132,10 +153,25 @@ final class ArcanistWorkingCopyIdentity { } + /** + * Read a configuration directive from local configuration. This + * reads ONLY the per-working copy configuration, + * i.e. .(git|hg|svn)/arc/config, and not other configuration + * sources. See @{method:getConfigFromAnySource} to read from any + * config source or @{method:getConfig} to read permanent + * project-level config. + * + * @task config + */ + public function getLocalConfig($key, $default=null) { + return idx($this->localConfig, $key, $default); + } + /** * Read a configuration directive from any available configuration source. * In contrast to @{method:getConfig}, this will look for the directive in - * user configuration in addition to project configuration. + * local and user configuration in addition to project configuration. + * The precedence is local > project > user * * @param key Key to read. * @param wild Default value if key is not found. @@ -144,26 +180,33 @@ final class ArcanistWorkingCopyIdentity { * @task config */ public function getConfigFromAnySource($key, $default = null) { - $pval = $this->getConfig($key); - if ($pval !== null) { - return $pval; + + // try local config first + $pval = $this->getLocalConfig($key); + + // then per-project config + if ($pval === null) { + $pval = $this->getConfig($key); } - // Test for older names. - + // Test for older names in the per-project config only, since + // they've only been used there static $deprecated_names = array( 'lint.engine' => 'lint_engine', 'unit.engine' => 'unit_engine', ); - if (isset($deprecated_names[$key])) { + if ($pval === null && isset($deprecated_names[$key])) { $pval = $this->getConfig($deprecated_names[$key]); - if ($pval !== null) { - return $pval; - } } - $global_config = ArcanistBaseWorkflow::readGlobalArcConfig(); - return idx($global_config, $key, $default); + // lastly, try global (i.e. user-level) config + if ($pval === null) { + $global_config = ArcanistBaseWorkflow::readGlobalArcConfig(); + $pval = idx($global_config, $key, $default); + } + + return $pval; + } }