public static function newAPIFromWorkingCopyIdentity(ArcanistWorkingCopyIdentity $working_copy) { $root = $working_copy->getProjectRoot(); if (!$root) { throw new ArcanistUsageException("There is no readable '.arcconfig' file in the working directory or " . "any parent directory. Create an '.arcconfig' file to configure arc."); } // check if we're in an svn working copy list($err) = exec_manual('svn info'); if (!$err) { $api = newv('ArcanistSubversionAPI', array($root)); $api->workingCopyIdentity = $working_copy; return $api; } if (Filesystem::pathExists($root . '/.hg')) { $api = newv('ArcanistMercurialAPI', array($root)); $api->workingCopyIdentity = $working_copy; return $api; } $git_root = self::discoverGitBaseDirectory($root); if ($git_root) { if (!Filesystem::pathsAreEquivalent($root, $git_root)) { throw new ArcanistUsageException("'.arcconfig' file is located at '{$root}', but working copy root " . "is '{$git_root}'. Move '.arcconfig' file to the working copy root."); } $api = newv('ArcanistGitAPI', array($root)); $api->workingCopyIdentity = $working_copy; return $api; } throw new ArcanistUsageException("The current working directory is not part of a working copy for a " . "supported version control system (svn, git or mercurial)."); }
public function run() { $roots = array(); $roots['libphutil'] = dirname(phutil_get_library_root('phutil')); $roots['arcanist'] = dirname(phutil_get_library_root('arcanist')); foreach ($roots as $lib => $root) { echo "Upgrading {$lib}...\n"; if (!Filesystem::pathExists($root . '/.git')) { throw new ArcanistUsageException("{$lib} must be in its git working copy to be automatically " . "upgraded. This copy of {$lib} (in '{$root}') is not in a git " . "working copy."); } $working_copy = ArcanistWorkingCopyIdentity::newFromPath($root); $configuration_manager = clone $this->getConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $repository_api = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); $this->setRepositoryAPI($repository_api); // Require no local changes. $this->requireCleanWorkingCopy(); // Require the library be on master. $branch_name = $repository_api->getBranchName(); if ($branch_name != 'master') { throw new ArcanistUsageException("{$lib} must be on branch 'master' to be automatically upgraded. " . "This copy of {$lib} (in '{$root}') is on branch '{$branch_name}'."); } chdir($root); try { phutil_passthru('git pull --rebase'); } catch (Exception $ex) { phutil_passthru('git rebase --abort'); throw $ex; } } echo phutil_console_wrap(phutil_console_format("**Updated!** Your copy of arc is now up to date.\n")); return 0; }
public function run() { $roots = array('libphutil' => dirname(phutil_get_library_root('phutil')), 'arcanist' => dirname(phutil_get_library_root('arcanist'))); foreach ($roots as $lib => $root) { echo phutil_console_format("%s\n", pht('Upgrading %s...', $lib)); $working_copy = ArcanistWorkingCopyIdentity::newFromPath($root); $configuration_manager = clone $this->getConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $repository = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); if (!Filesystem::pathExists($repository->getMetadataPath())) { throw new ArcanistUsageException(pht("%s must be in its git working copy to be automatically upgraded. " . "This copy of %s (in '%s') is not in a git working copy.", $lib, $lib, $root)); } $this->setRepositoryAPI($repository); // Require no local changes. $this->requireCleanWorkingCopy(); // Require the library be on master. $branch_name = $repository->getBranchName(); if ($branch_name != 'master') { throw new ArcanistUsageException(pht("%s must be on branch '%s' to be automatically upgraded. " . "This copy of %s (in '%s') is on branch '%s'.", $lib, 'master', $lib, $root, $branch_name)); } chdir($root); try { phutil_passthru('git pull --rebase'); } catch (Exception $ex) { phutil_passthru('git rebase --abort'); throw $ex; } } echo phutil_console_format("**%s** %s\n", pht('Updated!'), pht('Your copy of arc is now up to date.')); return 0; }
public function testXHPASTLint() { $linter = new ArcanistXHPASTLinter(); $linter->setCustomSeverityMap(array(ArcanistXHPASTLinter::LINT_RAGGED_CLASSTREE_EDGE => ArcanistLintSeverity::SEVERITY_WARNING)); $working_copy = ArcanistWorkingCopyIdentity::newFromPath(__FILE__); return $this->executeTestsInDirectory(dirname(__FILE__) . '/xhpast/', $linter, $working_copy); }
public function run() { $roots = array(); $roots['libphutil'] = dirname(phutil_get_library_root('phutil')); $roots['arcanist'] = dirname(phutil_get_library_root('arcanist')); foreach ($roots as $lib => $root) { echo "Upgrading {$lib}...\n"; if (!Filesystem::pathExists($root . '/.git')) { throw new ArcanistUsageException("{$lib} must be in its git working copy to be automatically " . "upgraded. This copy of {$lib} (in '{$root}') is not in a git " . "working copy."); } $working_copy = ArcanistWorkingCopyIdentity::newFromPath($root); $repository_api = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity($working_copy); // Force the range to HEAD^..HEAD, which is meaningless but keeps us // from triggering "base" rules or other commit range resolution rules // that might prompt the user when we pull the working copy status. $repository_api->setRelativeCommit('HEAD^'); $this->setRepositoryAPI($repository_api); // Require no local changes. $this->requireCleanWorkingCopy(); // Require the library be on master. $branch_name = $repository_api->getBranchName(); if ($branch_name != 'master') { throw new ArcanistUsageException("{$lib} must be on branch 'master' to be automatically upgraded. " . "This copy of {$lib} (in '{$root}') is on branch '{$branch_name}'."); } chdir($root); try { phutil_passthru('git pull --rebase'); } catch (Exception $ex) { phutil_passthru('git rebase --abort'); throw $ex; } } echo phutil_console_wrap(phutil_console_format("**Updated!** Your copy of arc is now up to date.\n")); return 0; }
public function testPhutilXHPASTLint() { $linter = new ArcanistPhutilXHPASTLinter(); $linter->setXHPASTLinter(new ArcanistXHPASTLinter()); $linter->setDeprecatedFunctions(array('deprecated_function' => 'This function is most likely deprecated.')); $working_copy = ArcanistWorkingCopyIdentity::newFromPath(__FILE__); return $this->executeTestsInDirectory(dirname(__FILE__) . '/phlxhp/', $linter, $working_copy); }
public function testSpellingLint() { $linter = new ArcanistSpellingLinter(); $linter->removeLintRule('acc' . 'out'); $linter->addPartialWordRule('supermn', 'superman'); $linter->addWholeWordRule('batmn', 'batman'); $working_copy = ArcanistWorkingCopyIdentity::newFromPath(__FILE__); return $this->executeTestsInDirectory(dirname(__FILE__) . '/spelling/', $linter, $working_copy); }
private function buildParser() { // TODO: This is a little hacky beacuse we're using the Arcanist repository // itself to execute tests with, but it should be OK until we get proper // isolation for repository-oriented test cases. $root = dirname(phutil_get_library_root('arcanist')); $copy = ArcanistWorkingCopyIdentity::newFromPath($root); $repo = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity($copy); return new ArcanistBaseCommitParser($repo); }
public function testStateParsing() { $dir = dirname(__FILE__) . '/state/'; $tests = Filesystem::listDirectory($dir, $include_hidden = false); foreach ($tests as $test) { $fixture = PhutilDirectoryFixture::newFromArchive($dir . '/' . $test); $fixture_path = $fixture->getPath(); $working_copy = ArcanistWorkingCopyIdentity::newFromPath($fixture_path); $api = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity($working_copy); $api->setBaseCommitArgumentRules('arc:this'); $this->assertCorrectState($test, $api); } }
public function run() { // We might not be in a working directory, so we don't want to require a // working copy identity here. $working_copy = ArcanistWorkingCopyIdentity::newFromPath(getcwd()); $aliases = self::getAliases($working_copy); $argv = $this->getArgument('argv'); if (count($argv) == 0) { if ($aliases) { foreach ($aliases as $alias => $binding) { echo phutil_console_format("**%s** %s\n", $alias, implode(' ', $binding)); } } else { echo "You haven't defined any aliases yet.\n"; } } else { if (count($argv) == 1) { if (empty($aliases[$argv[0]])) { echo "No alias '{$argv[0]}' to remove.\n"; } else { echo phutil_console_format("'**arc %s**' is currently aliased to '**arc %s**'.", $argv[0], implode(' ', $aliases[$argv[0]])); $ok = phutil_console_confirm('Delete this alias?'); if ($ok) { $was = implode(' ', $aliases[$argv[0]]); unset($aliases[$argv[0]]); $this->writeAliases($aliases); echo "Unaliased '{$argv[0]}' (was '{$was}').\n"; } else { throw new ArcanistUserAbortException(); } } } else { $arc_config = $this->getArcanistConfiguration(); if ($arc_config->buildWorkflow($argv[0])) { throw new ArcanistUsageException("You can not create an alias for '{$argv[0]}' because it is a " . "builtin command. 'arc alias' can only create new commands."); } $aliases[$argv[0]] = array_slice($argv, 1); echo phutil_console_format("Aliased '**arc %s**' to '**arc %s**'.\n", $argv[0], implode(' ', $aliases[$argv[0]])); $this->writeAliases($aliases); } } return 0; }
public function run() { $console = PhutilConsole::getConsole(); if (!Filesystem::binaryExists('git')) { throw new ArcanistUsageException(pht('Cannot display current version without having `%s` installed.', 'git')); } $roots = array('arcanist' => dirname(phutil_get_library_root('arcanist')), 'libphutil' => dirname(phutil_get_library_root('phutil'))); foreach ($roots as $lib => $root) { $working_copy = ArcanistWorkingCopyIdentity::newFromPath($root); $configuration_manager = clone $this->getConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $repository = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); if (!Filesystem::pathExists($repository->getMetadataPath())) { throw new ArcanistUsageException(pht('%s is not a git working copy.', $lib)); } list($stdout) = $repository->execxLocal('log -1 --format=%s', '%H %ct'); list($commit, $timestamp) = explode(' ', $stdout); $console->writeOut("%s %s (%s)\n", $lib, $commit, date('j M Y', (int) $timestamp)); } }
private function parseState($test) { $dir = dirname(__FILE__) . '/state/'; $fixture = PhutilDirectoryFixture::newFromArchive($dir . '/' . $test); $fixture_path = $fixture->getPath(); $working_copy = ArcanistWorkingCopyIdentity::newFromPath($fixture_path); $configuration_manager = new ArcanistConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $api = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); $api->setBaseCommitArgumentRules('arc:this'); if ($api instanceof ArcanistSubversionAPI) { // Upgrade the repository so that the test will still pass if the local // `svn` is newer than the `svn` which created the repository. // NOTE: Some versions of Subversion (1.7.x?) exit with an error code on // a no-op upgrade, although newer versions do not. We just ignore the // error here; if it's because of an actual problem we'll hit an error // shortly anyway. $api->execManualLocal('upgrade'); } $this->assertCorrectState($test, $api); }
public function run() { $roots = array('libphutil' => dirname(phutil_get_library_root('phutil')), 'arcanist' => dirname(phutil_get_library_root('arcanist'))); foreach ($roots as $lib => $root) { echo phutil_console_format("%s\n", pht('Upgrading %s...', $lib)); $working_copy = ArcanistWorkingCopyIdentity::newFromPath($root); $configuration_manager = clone $this->getConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $repository = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); if (!Filesystem::pathExists($repository->getMetadataPath())) { throw new ArcanistUsageException(pht("%s must be in its git working copy to be automatically upgraded. " . "This copy of %s (in '%s') is not in a git working copy.", $lib, $lib, $root)); } $this->setRepositoryAPI($repository); // NOTE: Don't use requireCleanWorkingCopy() here because it tries to // amend changes and generally move the workflow forward. We just want to // abort if there are local changes and make the user sort things out. $uncommitted = $repository->getUncommittedStatus(); if ($uncommitted) { $message = pht('You have uncommitted changes in the working copy for this ' . 'library:'); $list = id(new PhutilConsoleList())->setWrap(false)->addItems(array_keys($uncommitted)); id(new PhutilConsoleBlock())->addParagraph($message)->addList($list)->draw(); throw new ArcanistUsageException(pht('`arc upgrade` can only upgrade clean working copies.')); } $branch_name = $repository->getBranchName(); if ($branch_name != 'master' && $branch_name != 'stable') { throw new ArcanistUsageException(pht("%s must be on either branch '%s' or '%s' to be automatically " . "upgraded. " . "This copy of %s (in '%s') is on branch '%s'.", $lib, 'master', 'stable', $lib, $root, $branch_name)); } chdir($root); try { execx('git pull --rebase'); } catch (Exception $ex) { // If we failed, try to go back to the old state, then throw the // original exception. exec_manual('git rebase --abort'); throw $ex; } } echo phutil_console_format("**%s** %s\n", pht('Updated!'), pht('Your copy of arc is now up to date.')); return 0; }
/** * Locate all the information we need about a directory which we presume * to be a working copy. Particularly, we want to discover: * * - Is the directory inside a working copy (hg, git, svn)? * - If so, what is the root of the working copy? * - Is there a `.arcconfig` file? * * This is complicated, mostly because Subversion has special rules. In * particular: * * - Until 1.7, Subversion put a `.svn/` directory inside //every// * directory in a working copy. After 1.7, it //only// puts one at the * root. * - We allow `.arcconfig` to appear anywhere in a Subversion working copy, * and use the one closest to the directory. * - Although we may use a `.arcconfig` from a subdirectory, we store * metadata in the root's `.svn/`, because it's the only one guaranteed * to exist. * * Users also do these kinds of things in the wild: * * - Put working copies inside other working copies. * - Put working copies inside `.git/` directories. * - Create `.arcconfig` files at `/.arcconfig`, `/home/.arcconfig`, etc. * * This method attempts to be robust against all sorts of possible * misconfiguration. * * @param string Path to load information for, usually the current working * directory (unless running unit tests). * @param map|null Pass `null` to locate and load a `.arcconfig` file if one * exists. Pass a map to use it to set configuration. * @return ArcanistWorkingCopyIdentity Constructed working copy identity. */ private static function newFromPathWithConfig($path, $config) { $project_root = null; $vcs_root = null; $vcs_type = null; // First, find the outermost directory which is a Git, Mercurial or // Subversion repository, if one exists. We go from the top because this // makes it easier to identify the root of old SVN working copies (which // have a ".svn/" directory inside every directory in the working copy) and // gives us the right result if you have a Git repository inside a // Subversion repository or something equally ridiculous. $paths = Filesystem::walkToRoot($path); $config_paths = array(); $paths = array_reverse($paths); foreach ($paths as $path_key => $parent_path) { $try = array('git' => $parent_path . '/.git', 'hg' => $parent_path . '/.hg', 'svn' => $parent_path . '/.svn'); foreach ($try as $vcs => $try_dir) { if (!Filesystem::pathExists($try_dir)) { continue; } // NOTE: We're distinguishing between the `$project_root` and the // `$vcs_root` because they may not be the same in Subversion. Normally, // they are identical. However, in Subversion, the `$vcs_root` is the // base directory of the working copy (the directory which has the // `.svn/` directory, after SVN 1.7), while the `$project_root` might // be any subdirectory of the `$vcs_root`: it's the the directory // closest to the current directory which contains a `.arcconfig`. $project_root = $parent_path; $vcs_root = $parent_path; $vcs_type = $vcs; if ($vcs == 'svn') { // For Subversion, we'll look for a ".arcconfig" file here or in // any subdirectory, starting at the deepest subdirectory. $config_paths = array_slice($paths, $path_key); $config_paths = array_reverse($config_paths); } else { // For Git and Mercurial, we'll only look for ".arcconfig" right here. $config_paths = array($parent_path); } break; } } $console = PhutilConsole::getConsole(); $looked_in = array(); foreach ($config_paths as $config_path) { $config_file = $config_path . '/.arcconfig'; $looked_in[] = $config_file; if (Filesystem::pathExists($config_file)) { // We always need to examine the filesystem to look for `.arcconfig` // so we can set the project root correctly. We might or might not // actually read the file: if the caller passed in configuration data, // we'll ignore the actual file contents. $project_root = $config_path; if ($config === null) { $console->writeLog("%s\n", pht('Working Copy: Reading .arcconfig from "%s".', $config_file)); $config_data = Filesystem::readFile($config_file); $config = self::parseRawConfigFile($config_data, $config_file); } break; } } if ($config === null) { if ($looked_in) { $console->writeLog("%s\n", pht('Working Copy: Unable to find .arcconfig in any of these ' . 'locations: %s.', implode(', ', $looked_in))); } else { $console->writeLog("%s\n", pht('Working Copy: No candidate locations for .arcconfig from ' . 'this working directory.')); } $config = array(); } if ($project_root === null) { // We aren't in a working directory at all. This is fine if we're // running a command like "arc help". If we're running something that // requires a working directory, an exception will be raised a little // later on. $console->writeLog("%s\n", pht('Working Copy: Path "%s" is not in any working copy.', $path)); return new ArcanistWorkingCopyIdentity($path, $config); } $console->writeLog("%s\n", pht('Working Copy: Path "%s" is part of `%s` working copy "%s".', $path, $vcs_type, $vcs_root)); $console->writeLog("%s\n", pht('Working Copy: Project root is at "%s".', $project_root)); $identity = new ArcanistWorkingCopyIdentity($project_root, $config); $identity->localMetaDir = $vcs_root . '/.' . $vcs_type; $identity->localConfig = $identity->readLocalArcConfig(); $identity->vcsType = $vcs_type; $identity->vcsRoot = $vcs_root; return $identity; }
function generateCoverageResults() { // Find all executables, not just the test executables. // We need to do this because the test executables may not // include all of the possible code (linker decides to omit // it from the image) so we see a skewed representation of // the source lines. $fp = popen("find {$this->repo_root} -type f -name watchman -o " . "-name \\*.a -o -name \\*.so -o -name \\*.dylib", "r"); while (true) { $line = fgets($fp); if ($line === false) { break; } $obj_files[] = trim($line); } // Parse line information from the objects foreach ($obj_files as $object) { DwarfLineInfo::loadObject($object); } $CG = new CallgrindFile((string) $this->cg_file); $CG->parse(); $source_files = array(); foreach ($CG->getSourceFiles() as $filename) { if (Filesystem::isDescendant($filename, $this->repo_root) && !preg_match("/(thirdparty|tests)/", $filename)) { $source_files[$filename] = $filename; } } $cov = array(); foreach ($source_files as $filename) { $relsrc = substr($filename, strlen($this->repo_root) + 1); $cov[$relsrc] = CallgrindFile::mergeSourceLineData($filename, array($CG)); } $res = new ArcanistUnitTestResult(); $res->setName('coverage'); $res->setUserData("Collected"); $res->setResult(ArcanistUnitTestResult::RESULT_PASS); $res->setCoverage($cov); // Stash it for review with our `arc cov` command $wc = ArcanistWorkingCopyIdentity::newFromPath($this->repo_root); $api = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity($wc); $api->writeScratchFile('wman-cov.json', json_encode($cov)); return array($res); }
/** * NOTE: SPOOKY BLACK MAGIC * * When arc is run in a copy of arcanist other than itself, or a copy of * libphutil other than the one we loaded, reenter the script and force it * to use the current working directory instead of the default. * * In the case of execution inside arcanist/, we force execution of the local * arc binary. * * In the case of execution inside libphutil/, we force the local copy to load * instead of the one selected by default rules. * * @param PhutilConsole Console. * @param ArcanistWorkingCopyIdentity The current working copy. * @param array Original arc arguments. * @return void */ function reenter_if_this_is_arcanist_or_libphutil(PhutilConsole $console, ArcanistWorkingCopyIdentity $working_copy, array $original_argv) { $project_id = $working_copy->getProjectID(); if ($project_id != 'arcanist' && $project_id != 'libphutil') { // We're not in a copy of arcanist or libphutil. return; } $library_names = array('arcanist' => 'arcanist', 'libphutil' => 'phutil'); $library_root = phutil_get_library_root($library_names[$project_id]); $project_root = $working_copy->getProjectRoot(); if (Filesystem::isDescendant($library_root, $project_root)) { // We're in a copy of arcanist or libphutil, but already loaded the correct // copy. Continue execution normally. return; } if ($project_id == 'libphutil') { $console->writeLog("This is libphutil! Forcing this copy to load...\n"); $original_argv[0] = dirname(phutil_get_library_root('arcanist')) . '/bin/arc'; $libphutil_path = $project_root; } else { $console->writeLog("This is arcanist! Forcing this copy to run...\n"); $original_argv[0] = $project_root . '/bin/arc'; $libphutil_path = dirname(phutil_get_library_root('phutil')); } if (phutil_is_windows()) { $err = phutil_passthru('set ARC_PHUTIL_PATH=%s & %Ls', $libphutil_path, $original_argv); } else { $err = phutil_passthru('ARC_PHUTIL_PATH=%s %Ls', $libphutil_path, $original_argv); } exit($err); }
public function run() { $pos = $this->getArgument('current'); $argv = $this->getArgument('argv', array()); $argc = count($argv); if ($pos === null) { $pos = $argc - 1; } if ($pos > $argc) { throw new ArcanistUsageException(pht('Specified position is greater than the number of ' . 'arguments provided.')); } // Determine which revision control system the working copy uses, so we // can filter out commands and flags which aren't supported. If we can't // figure it out, just return all flags/commands. $vcs = null; // We have to build our own because if we requiresWorkingCopy() we'll throw // if we aren't in a .arcconfig directory. We probably still can't do much, // but commands can raise more detailed errors. $configuration_manager = $this->getConfigurationManager(); $working_copy = ArcanistWorkingCopyIdentity::newFromPath(getcwd()); if ($working_copy->getVCSType()) { $configuration_manager->setWorkingCopyIdentity($working_copy); $repository_api = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); $vcs = $repository_api->getSourceControlSystemName(); } $arc_config = $this->getArcanistConfiguration(); if ($pos <= 1) { $workflows = $arc_config->buildAllWorkflows(); $complete = array(); foreach ($workflows as $name => $workflow) { if (!$workflow->shouldShellComplete()) { continue; } $workflow->setArcanistConfiguration($this->getArcanistConfiguration()); $workflow->setConfigurationManager($this->getConfigurationManager()); if ($vcs || $workflow->requiresWorkingCopy()) { $supported_vcs = $workflow->getSupportedRevisionControlSystems(); if (!in_array($vcs, $supported_vcs)) { continue; } } $complete[] = $name; } // Also permit autocompletion of "arc alias" commands. $aliases = ArcanistAliasWorkflow::getAliases($configuration_manager); foreach ($aliases as $key => $value) { $complete[] = $key; } echo implode(' ', $complete) . "\n"; return 0; } else { $workflow = $arc_config->buildWorkflow($argv[1]); if (!$workflow) { list($new_command, $new_args) = ArcanistAliasWorkflow::resolveAliases($argv[1], $arc_config, array_slice($argv, 2), $configuration_manager); if ($new_command) { $workflow = $arc_config->buildWorkflow($new_command); } if (!$workflow) { return 1; } else { $argv = array_merge(array($argv[0]), array($new_command), $new_args); } } $arguments = $workflow->getArguments(); $prev = idx($argv, $pos - 1, null); if (!strncmp($prev, '--', 2)) { $prev = substr($prev, 2); } else { $prev = null; } if ($prev !== null && isset($arguments[$prev]) && isset($arguments[$prev]['param'])) { $type = idx($arguments[$prev], 'paramtype'); switch ($type) { case 'file': echo "FILE\n"; break; case 'complete': echo implode(' ', $workflow->getShellCompletions($argv)) . "\n"; break; default: echo "ARGUMENT\n"; break; } return 0; } else { $output = array(); foreach ($arguments as $argument => $spec) { if ($argument == '*') { continue; } if ($vcs && isset($spec['supports']) && !in_array($vcs, $spec['supports'])) { continue; } $output[] = '--' . $argument; } $cur = idx($argv, $pos, ''); $any_match = false; if (strlen($cur)) { foreach ($output as $possible) { if (!strncmp($possible, $cur, strlen($cur))) { $any_match = true; } } } if (!$any_match && isset($arguments['*'])) { // TODO: This is mega hacktown but something else probably breaks // if we use a rich argument specification; fix it when we move to // PhutilArgumentParser since everything will need to be tested then // anyway. if ($arguments['*'] == 'branch' && isset($repository_api)) { $branches = $repository_api->getAllBranches(); $branches = ipull($branches, 'name'); $output = $branches; } else { $output = array('FILE'); } } echo implode(' ', $output) . "\n"; return 0; } } }
public function run() { $pos = $this->getArgument('current'); $argv = $this->getArgument('argv', array()); $argc = count($argv); if ($pos === null) { $pos = $argc - 1; } // Determine which revision control system the working copy uses, so we // can filter out commands and flags which aren't supported. If we can't // figure it out, just return all flags/commands. $vcs = null; // We have to build our own because if we requiresWorkingCopy() we'll throw // if we aren't in a .arcconfig directory. We probably still can't do much, // but commands can raise more detailed errors. $working_copy = ArcanistWorkingCopyIdentity::newFromPath(getcwd()); if ($working_copy->getProjectRoot()) { $repository_api = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity($working_copy); $vcs = $repository_api->getSourceControlSystemName(); } $arc_config = $this->getArcanistConfiguration(); if ($pos == 1) { $workflows = $arc_config->buildAllWorkflows(); $complete = array(); foreach ($workflows as $name => $workflow) { if (!$workflow->shouldShellComplete()) { continue; } $supported = $workflow->getSupportedRevisionControlSystems(); $ok = in_array('any', $supported) || in_array($vcs, $supported); if (!$ok) { continue; } $complete[] = $name; } // Also permit autocompletion of "arc alias" commands. foreach (ArcanistAliasWorkflow::getAliases($working_copy) as $key => $value) { $complete[] = $key; } echo implode(' ', $complete) . "\n"; return 0; } else { $workflow = $arc_config->buildWorkflow($argv[1]); if (!$workflow) { list($new_command, $new_args) = ArcanistAliasWorkflow::resolveAliases($argv[1], $arc_config, array_slice($argv, 2), $working_copy); if ($new_command) { $workflow = $arc_config->buildWorkflow($new_command); } if (!$workflow) { return 1; } else { $argv = array_merge(array($argv[0]), array($new_command), $new_args); } } $arguments = $workflow->getArguments(); $prev = idx($argv, $pos - 1, null); if (!strncmp($prev, '--', 2)) { $prev = substr($prev, 2); } else { $prev = null; } if ($prev !== null && isset($arguments[$prev]) && isset($arguments[$prev]['param'])) { $type = idx($arguments[$prev], 'paramtype'); switch ($type) { case 'file': echo "FILE\n"; break; case 'complete': echo implode(' ', $workflow->getShellCompletions($argv)) . "\n"; break; default: echo "ARGUMENT\n"; break; } return 0; } else { $output = array(); foreach ($arguments as $argument => $spec) { if ($argument == '*') { continue; } if ($vcs && isset($spec['supports']) && !in_array($vcs, $spec['supports'])) { continue; } $output[] = '--' . $argument; } $cur = idx($argv, $pos, ''); $any_match = false; foreach ($output as $possible) { if (!strncmp($possible, $cur, strlen($cur))) { $any_match = true; } } if (!$any_match && isset($arguments['*'])) { // TODO: the '*' specifier should probably have more details about // whether or not it is a list of files. Since it almost always is in // practice, assume FILE for now. echo "FILE\n"; } else { echo implode(' ', $output) . "\n"; } return 0; } } }
public function testCpplintLint() { $linter = new ArcanistCpplintLinter(); $working_copy = ArcanistWorkingCopyIdentity::newFromPath(__FILE__); return $this->executeTestsInDirectory(dirname(__FILE__) . '/cpp/', $linter, $working_copy); }
function arcanist_load_libraries($load, $must_load, $lib_source, ArcanistWorkingCopyIdentity $working_copy, $config_trace_mode) { if (!$load) { return; } if (!is_array($load)) { $error = "Libraries specified by {$lib_source} are invalid; expected " . "a list. Check your configuration."; $console = PhutilConsole::getConsole(); $console->writeErr("WARNING: %s\n", $error); return; } foreach ($load as $location) { // Try to resolve the library location. We look in several places, in // order: // // 1. Inside the working copy. This is for phutil libraries within the // project. For instance "library/src" will resolve to // "./library/src" if it exists. // 2. In the same directory as the working copy. This allows you to // check out a library alongside a working copy and reference it. // If we haven't resolved yet, "library/src" will try to resolve to // "../library/src" if it exists. // 3. Using normal libphutil resolution rules. Generally, this means // that it checks for libraries next to libphutil, then libraries // in the PHP include_path. // // Note that absolute paths will just resolve absolutely through rule (1). $resolved = false; // Check inside the working copy. This also checks absolute paths, since // they'll resolve absolute and just ignore the project root. $resolved_location = Filesystem::resolvePath($location, $working_copy->getProjectRoot()); if (Filesystem::pathExists($resolved_location)) { $location = $resolved_location; $resolved = true; } // If we didn't find anything, check alongside the working copy. if (!$resolved) { $resolved_location = Filesystem::resolvePath($location, dirname($working_copy->getProjectRoot())); if (Filesystem::pathExists($resolved_location)) { $location = $resolved_location; $resolved = true; } } if ($config_trace_mode) { echo "Loading phutil library from '{$location}'...\n"; } $error = null; try { phutil_load_library($location); } catch (PhutilBootloaderException $ex) { $error = "Failed to load phutil library at location '{$location}'. " . "This library is specified by {$lib_source}. Check that the " . "setting is correct and the library is located in the right " . "place."; if ($must_load) { throw new ArcanistUsageException($error); } else { file_put_contents('php://stderr', phutil_console_wrap('WARNING: ' . $error . "\n\n")); } } catch (PhutilLibraryConflictException $ex) { if ($ex->getLibrary() != 'arcanist') { throw $ex; } $arc_dir = dirname(dirname(__FILE__)); $error = "You are trying to run one copy of Arcanist on another copy of " . "Arcanist. This operation is not supported. To execute Arcanist " . "operations against this working copy, run './bin/arc' (from the " . "current working copy) not some other copy of 'arc' (you ran one " . "from '{$arc_dir}')."; throw new ArcanistUsageException($error); } } }
public function run($dir) { $working_copy = ArcanistWorkingCopyIdentity::newFromPath($dir); $configuration_manager = new ArcanistConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $api = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); $this->svnRoot = id(new PhutilURI($api->getSourceControlPath()))->getPath(); if ($api instanceof ArcanistGitAPI) { $svn_fetch = $api->getGitConfig('svn-remote.svn.fetch'); list($this->svnRoot) = explode(':', $svn_fetch); if ($this->svnRoot != '') { $this->svnRoot = '/' . $this->svnRoot; } } $project_id = $working_copy->getProjectID(); $project = id(new PhabricatorRepositoryArcanistProject())->loadOneWhere('name = %s', $project_id); if (!$project || !$project->getRepositoryID()) { throw new Exception("Couldn't find repository for {$project_id}."); } $branch_name = $api->getBranchName(); $this->branch = PhabricatorRepositoryBranch::loadOrCreateBranch($project->getRepositoryID(), $branch_name); $this->conn = $this->branch->establishConnection('w'); $this->lintCommit = null; if (!$this->all) { $this->lintCommit = $this->branch->getLintCommit(); } if ($this->lintCommit) { try { $commit = $this->lintCommit; if ($this->svnRoot) { $commit = $api->getCanonicalRevisionName('@' . $commit); } $all_files = $api->getChangedFiles($commit); } catch (ArcanistCapabilityNotSupportedException $ex) { $this->lintCommit = null; } } if (!$this->lintCommit) { $where = $this->svnRoot ? qsprintf($this->conn, 'AND path LIKE %>', $this->svnRoot . '/') : ''; queryfx($this->conn, 'DELETE FROM %T WHERE branchID = %d %Q', PhabricatorRepository::TABLE_LINTMESSAGE, $this->branch->getID(), $where); $all_files = $api->getAllFiles(); } $count = 0; $files = array(); foreach ($all_files as $file => $val) { $count++; if (!$this->lintCommit) { $file = $val; } else { $this->deletes[] = $this->svnRoot . '/' . $file; if ($val & ArcanistRepositoryAPI::FLAG_DELETED) { continue; } } $files[$file] = $file; if (count($files) >= $this->chunkSize) { $this->runArcLint($files); $files = array(); } } $this->runArcLint($files); $this->saveLintMessages(); $this->lintCommit = $api->getUnderlyingWorkingCopyRevision(); $this->branch->setLintCommit($this->lintCommit); $this->branch->save(); if ($this->blame) { $this->blameAuthors(); $this->blame = array(); } return $count; }
private function liberateBuildLintEngine($path, array $changed) { $lint_map = array(); foreach ($changed as $module) { $module_path = $path . '/' . $module; $files = Filesystem::listDirectory($module_path); $lint_map[$module] = $files; } $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile($path, json_encode(array('project_id' => '__arcliberate__')), 'arc liberate'); $engine = new ArcanistLiberateLintEngine(); $engine->setWorkingCopy($working_copy); $lint_paths = array(); foreach ($lint_map as $module => $files) { foreach ($files as $file) { $lint_paths[] = $module . '/' . $file; } } if (!$lint_paths) { return null; } $engine->setPaths($lint_paths); $engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR); return $engine; }
public function run() { $svnargs = $this->getArgument('svnargs'); $repository = $svnargs[0]; $transaction = $svnargs[1]; list($commit_message) = execx('svnlook log --transaction %s %s', $transaction, $repository); if (strpos($commit_message, '@bypass-lint') !== false) { return 0; } // TODO: Do stuff with commit message. list($changed) = execx('svnlook changed --transaction %s %s', $transaction, $repository); $paths = array(); $changed = explode("\n", trim($changed)); foreach ($changed as $line) { $matches = null; preg_match('/^..\\s*(.*)$/', $line, $matches); $paths[$matches[1]] = strlen($matches[1]); } $resolved = array(); $failed = array(); $missing = array(); $found = array(); asort($paths); foreach ($paths as $path => $length) { foreach ($resolved as $rpath => $root) { if (!strncmp($path, $rpath, strlen($rpath))) { $resolved[$path] = $root; continue 2; } } $config = $path; if (basename($config) == '.arcconfig') { $resolved[$config] = $config; continue; } $config = rtrim($config, '/'); $last_config = $config; do { if (!empty($missing[$config])) { break; } else { if (!empty($found[$config])) { $resolved[$path] = $found[$config]; break; } } list($err) = exec_manual('svnlook cat --transaction %s %s %s', $transaction, $repository, $config ? $config . '/.arcconfig' : '.arcconfig'); if ($err) { $missing[$path] = true; } else { $resolved[$path] = $config ? $config . '/.arcconfig' : '.arcconfig'; $found[$config] = $resolved[$path]; break; } $config = dirname($config); if ($config == '.') { $config = ''; } if ($config == $last_config) { break; } $last_config = $config; } while (true); if (empty($resolved[$path])) { $failed[] = $path; } } if ($failed && $resolved) { $failed_paths = ' ' . implode("\n ", $failed); $resolved_paths = ' ' . implode("\n ", array_keys($resolved)); throw new ArcanistUsageException("This commit includes a mixture of files in Arcanist projects and " . "outside of Arcanist projects. A commit which affects an Arcanist " . "project must affect only that project.\n\n" . "Files in projects:\n\n" . $resolved_paths . "\n\n" . "Files not in projects:\n\n" . $failed_paths); } if (!$resolved) { // None of the affected paths are beneath a .arcconfig file. return 0; } $groups = array(); foreach ($resolved as $path => $project) { $groups[$project][] = $path; } if (count($groups) > 1) { $message = array(); foreach ($groups as $project => $group) { $message[] = "Files underneath '{$project}':\n\n"; $message[] = " " . implode("\n ", $group) . "\n\n"; } $message = implode('', $message); throw new ArcanistUsageException("This commit includes a mixture of files from different Arcanist " . "projects. A commit which affects an Arcanist project must affect " . "only that project.\n\n" . $message); } $config_file = key($groups); $project_root = dirname($config_file); $paths = reset($groups); list($config) = execx('svnlook cat --transaction %s %s %s', $transaction, $repository, $config_file); $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile($project_root, $config, $config_file . " (svnlook: {$transaction} {$repository})"); $repository_api = new ArcanistSubversionHookAPI($project_root, $transaction, $repository); $lint_engine = $working_copy->getConfig('lint_engine'); if (!$lint_engine) { return 0; } $engine = newv($lint_engine, array()); $engine->setWorkingCopy($working_copy); $engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR); $engine->setPaths($paths); $engine->setCommitHookMode(true); $engine->setHookAPI($repository_api); try { $results = $engine->run(); } catch (ArcanistNoEffectException $no_effect) { // Nothing to do, bail out. return 0; } $failures = array(); foreach ($results as $result) { if (!$result->getMessages()) { continue; } $failures[] = $result; } if ($failures) { $at = "@"; $msg = phutil_console_format("\n**LINT ERRORS**\n\n" . "This changeset has lint errors. You must fix all lint errors before " . "you can commit.\n\n" . "You can add '{$at}bypass-lint' to your commit message to disable " . "lint checks for this commit, or '{$at}nolint' to the file with " . "errors to disable lint for that file.\n\n"); echo phutil_console_wrap($msg); $renderer = new ArcanistLintConsoleRenderer(); foreach ($failures as $result) { echo $renderer->renderLintResult($result); } return 1; } return 0; }
private function lintFile($file, ArcanistLinter $linter) { $linter = clone $linter; $contents = Filesystem::readFile($file); $contents = preg_split('/^~{4,}\\n/m', $contents); if (count($contents) < 2) { throw new Exception(pht("Expected '%s' separating test case and results.", '~~~~~~~~~~')); } list($data, $expect, $xform, $config) = array_merge($contents, array(null, null)); $basename = basename($file); if ($config) { $config = phutil_json_decode($config); } else { $config = array(); } PhutilTypeSpec::checkMap($config, array('config' => 'optional map<string, wild>', 'path' => 'optional string', 'mode' => 'optional string', 'stopped' => 'optional bool')); $exception = null; $after_lint = null; $messages = null; $exception_message = false; $caught_exception = false; try { $tmp = new TempFile($basename); Filesystem::writeFile($tmp, $data); $full_path = (string) $tmp; $mode = idx($config, 'mode'); if ($mode) { Filesystem::changePermissions($tmp, octdec($mode)); } $dir = dirname($full_path); $path = basename($full_path); $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile($dir, null, pht('Unit Test')); $configuration_manager = new ArcanistConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $engine = new ArcanistUnitTestableLintEngine(); $engine->setWorkingCopy($working_copy); $engine->setConfigurationManager($configuration_manager); $path_name = idx($config, 'path', $path); $engine->setPaths(array($path_name)); $linter->addPath($path_name); $linter->addData($path_name, $data); foreach (idx($config, 'config', array()) as $key => $value) { $linter->setLinterConfigurationValue($key, $value); } $engine->addLinter($linter); $engine->addFileData($path_name, $data); $results = $engine->run(); $this->assertEqual(1, count($results), pht('Expect one result returned by linter.')); $assert_stopped = idx($config, 'stopped'); if ($assert_stopped !== null) { $this->assertEqual($assert_stopped, $linter->didStopAllLinters(), $assert_stopped ? pht('Expect linter to be stopped.') : pht('Expect linter to not be stopped.')); } $result = reset($results); $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result); $after_lint = $patcher->getModifiedFileContent(); } catch (PhutilTestTerminatedException $ex) { throw $ex; } catch (Exception $exception) { $caught_exception = true; if ($exception instanceof PhutilAggregateException) { $caught_exception = false; foreach ($exception->getExceptions() as $ex) { if ($ex instanceof ArcanistUsageException || $ex instanceof ArcanistMissingLinterException) { $this->assertSkipped($ex->getMessage()); } else { $caught_exception = true; } } } else { if ($exception instanceof ArcanistUsageException || $exception instanceof ArcanistMissingLinterException) { $this->assertSkipped($exception->getMessage()); } } $exception_message = $exception->getMessage() . "\n\n" . $exception->getTraceAsString(); } $this->assertEqual(false, $caught_exception, $exception_message); $this->compareLint($basename, $expect, $result); $this->compareTransform($xform, $after_lint); }
public function run($dir) { $working_copy = ArcanistWorkingCopyIdentity::newFromPath($dir); $configuration_manager = new ArcanistConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $api = ArcanistRepositoryAPI::newAPIFromConfigurationManager($configuration_manager); $this->svnRoot = id(new PhutilURI($api->getSourceControlPath()))->getPath(); if ($api instanceof ArcanistGitAPI) { $svn_fetch = $api->getGitConfig('svn-remote.svn.fetch'); list($this->svnRoot) = explode(':', $svn_fetch); if ($this->svnRoot != '') { $this->svnRoot = '/' . $this->svnRoot; } } $callsign = $configuration_manager->getConfigFromAnySource('repository.callsign'); $uuid = $api->getRepositoryUUID(); $remote_uri = $api->getRemoteURI(); $repository_query = id(new PhabricatorRepositoryQuery())->setViewer(PhabricatorUser::getOmnipotentUser()); if ($callsign) { $repository_query->withCallsigns(array($callsign)); } else { if ($uuid) { $repository_query->withUUIDs(array($uuid)); } else { if ($remote_uri) { $repository_query->withRemoteURIs(array($remote_uri)); } } } $repository = $repository_query->executeOne(); $branch_name = $api->getBranchName(); if (!$repository) { throw new Exception(pht('No repository was found.')); } $this->branch = PhabricatorRepositoryBranch::loadOrCreateBranch($repository->getID(), $branch_name); $this->conn = $this->branch->establishConnection('w'); $this->lintCommit = null; if (!$this->all) { $this->lintCommit = $this->branch->getLintCommit(); } if ($this->lintCommit) { try { $commit = $this->lintCommit; if ($this->svnRoot) { $commit = $api->getCanonicalRevisionName('@' . $commit); } $all_files = $api->getChangedFiles($commit); } catch (ArcanistCapabilityNotSupportedException $ex) { $this->lintCommit = null; } } if (!$this->lintCommit) { $where = $this->svnRoot ? qsprintf($this->conn, 'AND path LIKE %>', $this->svnRoot . '/') : ''; queryfx($this->conn, 'DELETE FROM %T WHERE branchID = %d %Q', PhabricatorRepository::TABLE_LINTMESSAGE, $this->branch->getID(), $where); $all_files = $api->getAllFiles(); } $count = 0; $files = array(); foreach ($all_files as $file => $val) { $count++; if (!$this->lintCommit) { $file = $val; } else { $this->deletes[] = $this->svnRoot . '/' . $file; if ($val & ArcanistRepositoryAPI::FLAG_DELETED) { continue; } } $files[$file] = $file; if (count($files) >= $this->chunkSize) { $this->runArcLint($files); $files = array(); } } $this->runArcLint($files); $this->saveLintMessages(); $this->lintCommit = $api->getUnderlyingWorkingCopyRevision(); $this->branch->setLintCommit($this->lintCommit); $this->branch->save(); if ($this->blame) { $this->blameAuthors(); $this->blame = array(); } return $count; }
$load[] = $matches[1]; } else { if (preg_match('/^--conduit-uri=(.*)$/', $arg, $matches)) { unset($args[$key]); $force_conduit = $matches[1]; } } } } $args = array_values($args); $working_directory = getcwd(); try { if (!$args) { throw new ArcanistUsageException("No command provided. Try 'arc help'."); } $working_copy = ArcanistWorkingCopyIdentity::newFromPath($working_directory); if ($load) { $libs = $load; } else { $libs = $working_copy->getConfig('phutil_libraries'); } if ($libs) { foreach ($libs as $name => $location) { // Try to resolve the library location. We look in several places, in // order: // // 1. Inside the working copy. This is for phutil libraries within the // project. For instance "library/src" will resolve to // "./library/src" if it exists. // 2. In the same directory as the working copy. This allows you to // check out a library alongside a working copy and reference it.