public function renderLintResult(ArcanistLintResult $result) { $messages = $result->getMessages(); $path = $result->getPath(); $lines = explode("\n", $result->getData()); $text = array(); foreach ($messages as $message) { if (!$this->showAutofixPatches && $message->isAutofix()) { continue; } if ($message->isError()) { $color = 'red'; } else { $color = 'yellow'; } $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity()); $code = $message->getCode(); $name = $message->getName(); $description = phutil_console_wrap($message->getDescription(), 4); $text[] = phutil_console_format(" **<bg:{$color}> %s </bg>** (%s) __%s__\n%s\n", $severity, $code, $name, $description); if ($message->hasFileContext()) { $text[] = $this->renderContext($message, $lines); } } if ($text) { $prefix = phutil_console_format("**>>>** Lint for __%s__:\n\n\n", $path); return $prefix . implode("\n", $text); } else { return null; } }
/** * @group console */ function phutil_console_prompt($prompt, $history = '') { echo "\n\n"; $prompt = phutil_console_wrap($prompt . ' ', 4); try { phutil_console_require_tty(); } catch (PhutilConsoleStdinNotInteractiveException $ex) { // Throw after echoing the prompt so the user has some idea what happened. echo $prompt; throw $ex; } $use_history = true; if ($history == '') { $use_history = false; } else { // Test if bash is available by seeing if it can run `true`. list($err) = exec_manual('bash -c %s', 'true'); if ($err) { $use_history = false; } } if (!$use_history) { echo $prompt; $response = fgets(STDIN); } else { // There's around 0% chance that readline() is available directly in PHP, // so we're using bash/read/history instead. $command = csprintf('bash -c %s', csprintf('history -r %s 2>/dev/null; ' . 'read -e -p %s; ' . 'echo "$REPLY"; ' . 'history -s "$REPLY" 2>/dev/null; ' . 'history -w %s 2>/dev/null', $history, $prompt, $history)); // execx() doesn't work with input, phutil_passthru() doesn't return output. $response = shell_exec($command); } return rtrim($response, "\r\n"); }
public function execute(PhutilArgumentParser $args) { $is_dry = $args->getArg('dryrun'); $is_force = $args->getArg('force'); if (!$is_dry && !$is_force) { echo phutil_console_wrap("Are you completely sure you really want to permanently destroy all " . "storage for Phabricator data? This operation can not be undone and " . "your data will not be recoverable if you proceed."); if (!phutil_console_confirm('Permanently destroy all data?')) { echo "Cancelled.\n"; exit(1); } if (!phutil_console_confirm('Really destroy all data forever?')) { echo "Cancelled.\n"; exit(1); } } $api = $this->getAPI(); $patches = $this->getPatches(); $databases = $api->getDatabaseList($patches); $databases[] = $api->getDatabaseName('meta_data'); foreach ($databases as $database) { if ($is_dry) { echo "DRYRUN: Would drop database '{$database}'.\n"; } else { echo "Dropping database '{$database}'...\n"; queryfx($api->getConn('meta_data', $select_database = false), 'DROP DATABASE IF EXISTS %T', $database); } } if (!$is_dry) { echo "Storage was destroyed.\n"; } return 0; }
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 execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $public_keyfile = $args->getArg('public'); if (!strlen($public_keyfile)) { throw new PhutilArgumentUsageException(pht('You must specify the path to a public keyfile with %s.', '--public')); } if (!Filesystem::pathExists($public_keyfile)) { throw new PhutilArgumentUsageException(pht('Specified public keyfile "%s" does not exist!', $public_keyfile)); } $public_key = Filesystem::readFile($public_keyfile); $pkcs8_keyfile = $args->getArg('pkcs8'); if (!strlen($pkcs8_keyfile)) { throw new PhutilArgumentUsageException(pht('You must specify the path to a pkcs8 keyfile with %s.', '--pkc8s')); } if (!Filesystem::pathExists($pkcs8_keyfile)) { throw new PhutilArgumentUsageException(pht('Specified pkcs8 keyfile "%s" does not exist!', $pkcs8_keyfile)); } $pkcs8_key = Filesystem::readFile($pkcs8_keyfile); $warning = pht('Adding a PKCS8 keyfile to the cache can be very dangerous. If the ' . 'PKCS8 file really encodes a different public key than the one ' . 'specified, an attacker could use it to gain unauthorized access.' . "\n\n" . 'Generally, you should use this option only in a development ' . 'environment where ssh-keygen is broken and it is inconvenient to ' . 'fix it, and only if you are certain you understand the risks. You ' . 'should never cache a PKCS8 file you did not generate yourself.'); $console->writeOut("%s\n", phutil_console_wrap($warning)); $prompt = pht('Really trust this PKCS8 keyfile?'); if (!phutil_console_confirm($prompt)) { throw new PhutilArgumentUsageException(pht('Aborted workflow.')); } $key = PhabricatorAuthSSHPublicKey::newFromRawKey($public_key); $key->forcePopulatePKCS8Cache($pkcs8_key); $console->writeOut("%s\n", pht('Cached PKCS8 key for public key.')); return 0; }
public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $id = $args->getArg('id'); if (!$id) { throw new PhutilArgumentUsageException(pht('Specify a public key to trust with --id.')); } $key = id(new PhabricatorAuthSSHKeyQuery())->setViewer($this->getViewer())->withIDs(array($id))->executeOne(); if (!$key) { throw new PhutilArgumentUsageException(pht('No public key exists with ID "%s".', $id)); } if ($key->getIsTrusted()) { throw new PhutilArgumentUsageException(pht('Public key with ID %s is already trusted.', $id)); } if (!$key->getObject() instanceof AlmanacDevice) { throw new PhutilArgumentUsageException(pht('You can only trust keys associated with Almanac devices.')); } $handle = id(new PhabricatorHandleQuery())->setViewer($this->getViewer())->withPHIDs(array($key->getObject()->getPHID()))->executeOne(); $console->writeOut("**<bg:red> %s </bg>**\n\n%s\n\n%s\n\n%s", pht('IMPORTANT!'), phutil_console_wrap(pht('Trusting a public key gives anyone holding the corresponding ' . 'private key complete, unrestricted access to all data in ' . 'Phabricator. The private key will be able to sign requests that ' . 'skip policy and security checks.')), phutil_console_wrap(pht('This is an advanced feature which should normally be used only ' . 'when building a Phabricator cluster. This feature is very ' . 'dangerous if misused.')), pht('This key is associated with device "%s".', $handle->getName())); $prompt = pht('Really trust this key?'); if (!phutil_console_confirm($prompt)) { throw new PhutilArgumentUsageException(pht('User aborted workflow.')); } $key->setIsTrusted(1); $key->save(); $console->writeOut("**<bg:green> %s </bg>** %s\n", pht('TRUSTED'), pht('Key %s has been marked as trusted.', $id)); }
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 didExecute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $patches = $this->getPatches(); if (!$this->isDryRun() && !$this->isForce()) { $console->writeOut(phutil_console_wrap(pht('Before running storage upgrades, you should take down the ' . 'Phabricator web interface and stop any running Phabricator ' . 'daemons (you can disable this warning with %s).', '--force'))); if (!phutil_console_confirm(pht('Are you ready to continue?'))) { $console->writeOut("%s\n", pht('Cancelled.')); return 1; } } $apply_only = $args->getArg('apply'); if ($apply_only) { if (empty($patches[$apply_only])) { throw new PhutilArgumentUsageException(pht("%s argument '%s' is not a valid patch. " . "Use '%s' to show patch status.", '--apply', $apply_only, './bin/storage status')); } } $no_quickstart = $args->getArg('no-quickstart'); $init_only = $args->getArg('init-only'); $no_adjust = $args->getArg('no-adjust'); $this->upgradeSchemata($apply_only, $no_quickstart, $init_only); if ($no_adjust || $init_only || $apply_only) { $console->writeOut("%s\n", pht('Declining to apply storage adjustments.')); return 0; } else { return $this->adjustSchemata(false); } }
public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $viewer = $this->getViewer(); $subscription_phid = $args->getArg('subscription'); if (!$subscription_phid) { throw new PhutilArgumentUsageException(pht('Specify which subscription to invoice with %s.', '--subscription')); } $subscription = id(new PhortuneSubscriptionQuery())->setViewer($viewer)->withPHIDs(array($subscription_phid))->needTriggers(true)->executeOne(); if (!$subscription) { throw new PhutilArgumentUsageException(pht('Unable to load subscription with PHID "%s".', $subscription_phid)); } $now = $args->getArg('now'); $now = $this->parseTimeArgument($now); if (!$now) { $now = PhabricatorTime::getNow(); } $time_guard = PhabricatorTime::pushTime($now, date_default_timezone_get()); $console->writeOut("%s\n", pht('Set current time to %s.', phabricator_datetime(PhabricatorTime::getNow(), $viewer))); $auto_range = $args->getArg('auto-range'); $last_arg = $args->getArg('last'); $next_arg = $args->getARg('next'); if (!$auto_range && !$last_arg && !$next_arg) { throw new PhutilArgumentUsageException(pht('Specify a billing range with %s and %s, or use %s.', '--last', '--next', '--auto-range')); } else { if (!$auto_range & (!$last_arg || !$next_arg)) { throw new PhutilArgumentUsageException(pht('When specifying %s or %s, you must specify both arguments ' . 'to define the beginning and end of the billing range.', '--last', '--next')); } else { if (!$auto_range && ($last_arg && $next_arg)) { $last_time = $this->parseTimeArgument($args->getArg('last')); $next_time = $this->parseTimeArgument($args->getArg('next')); } else { if ($auto_range && ($last_arg || $next_arg)) { throw new PhutilArgumentUsageException(pht('Use either %s or %s and %s to specify the ' . 'billing range, but not both.', '--auto-range', '--last', '--next')); } else { $trigger = $subscription->getTrigger(); $event = $trigger->getEvent(); if (!$event) { throw new PhutilArgumentUsageException(pht('Unable to calculate %s, this subscription has not been ' . 'scheduled for billing yet. Wait for the trigger daemon to ' . 'schedule the subscription.', '--auto-range')); } $last_time = $event->getLastEventEpoch(); $next_time = $event->getNextEventEpoch(); } } } } $console->writeOut("%s\n", pht('Preparing to invoice subscription "%s" from %s to %s.', $subscription->getSubscriptionName(), $last_time ? phabricator_datetime($last_time, $viewer) : pht('subscription creation'), phabricator_datetime($next_time, $viewer))); PhabricatorWorker::setRunAllTasksInProcess(true); if (!$args->getArg('force')) { $console->writeOut("**<bg:yellow> %s </bg>**\n%s\n", pht('WARNING'), phutil_console_wrap(pht('Manually invoicing will double bill payment accounts if the ' . 'range overlaps an existing or future invoice. This script is ' . 'intended for testing and development, and should not be part ' . 'of routine billing operations. If you continue, you may ' . 'incorrectly overcharge customers.'))); if (!phutil_console_confirm(pht('Really invoice this subscription?'))) { throw new Exception(pht('Declining to invoice.')); } } PhabricatorWorker::scheduleTask('PhortuneSubscriptionWorker', array('subscriptionPHID' => $subscription->getPHID(), 'trigger.last-epoch' => $last_time, 'trigger.this-epoch' => $next_time, 'manual' => true), array('objectPHID' => $subscription->getPHID())); return 0; }
public function testWrapIndent() { $turtles = <<<EOTURTLES turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle turtle EOTURTLES; $this->assertEqual($turtles, phutil_console_wrap(rtrim(str_repeat('turtle ', 20)), $indent = 20)); }
/** * @group console */ function phutil_console_prompt($prompt) { $prompt = "\n\n " . $prompt . " "; $prompt = phutil_console_wrap($prompt, 4); echo $prompt; // Require after echoing the prompt so the user has some idea what happened // if this throws. phutil_console_require_tty(); $response = fgets(STDIN); return rtrim($response, "\n"); }
protected function drawView() { $output = array(); foreach ($this->getItems() as $item) { if ($this->wrap) { $item = phutil_console_wrap($item, 8); } $item = ' - ' . $item; $output[] = $item; } return $this->drawLines($output); }
public function execute(PhutilArgumentParser $args) { $viewer = $this->getViewer(); $ids = $args->getArg('id'); $active = $args->getArg('active'); if (!$ids && !$active) { throw new PhutilArgumentUsageException(pht('Use --id or --active to select builds.')); } if ($ids && $active) { throw new PhutilArgumentUsageException(pht('Use one of --id or --active to select builds, but not both.')); } $query = id(new HarbormasterBuildQuery())->setViewer($viewer); if ($ids) { $query->withIDs($ids); } else { $query->withBuildStatuses(HarbormasterBuildStatus::getActiveStatusConstants()); } $builds = $query->execute(); $console = PhutilConsole::getConsole(); $count = count($builds); if (!$count) { $console->writeOut("%s\n", pht('No builds to restart.')); return 0; } $prompt = pht('Restart %s build(s)?', new PhutilNumber($count)); if (!phutil_console_confirm($prompt)) { $console->writeOut("%s\n", pht('Cancelled.')); return 1; } $app_phid = id(new PhabricatorHarbormasterApplication())->getPHID(); $editor = id(new HarbormasterBuildTransactionEditor())->setActor($viewer)->setActingAsPHID($app_phid)->setContentSource($this->newContentSource()); foreach ($builds as $build) { $console->writeOut("<bg:blue> %s </bg> %s\n", pht('RESTARTING'), pht('Build %d: %s', $build->getID(), $build->getName())); if (!$build->canRestartBuild()) { $console->writeOut("<bg:yellow> %s </bg> %s\n", pht('INVALID'), pht('Cannot be restarted.')); continue; } $xactions = array(); $xactions[] = id(new HarbormasterBuildTransaction())->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)->setNewValue(HarbormasterBuildCommand::COMMAND_RESTART); try { $editor->applyTransactions($build, $xactions); } catch (Exception $e) { $message = phutil_console_wrap($e->getMessage(), 2); $console->writeOut("<bg:red> %s </bg>\n%s\n", pht('FAILED'), $message); continue; } $console->writeOut("<bg:green> %s </bg>\n", pht('SUCCESS')); } return 0; }
public function execute(PhutilArgumentParser $args) { $api = $this->getAPI(); $patches = $this->getPatches(); $applied = $api->getAppliedPatches(); if ($applied === null) { $namespace = $api->getNamespace(); echo phutil_console_wrap(phutil_console_format("**No Storage**: There is no database storage initialized in this " . "storage namespace ('{$namespace}'). Use '**storage upgrade**' to " . "initialize storage.\n")); return 1; } $databases = $api->getDatabaseList($patches); list($host, $port) = $this->getBareHostAndPort($api->getHost()); $flag_password = $api->getPassword() ? csprintf('-p %s', $api->getPassword()) : ''; $flag_port = $port ? csprintf('--port %d', $port) : ''; return phutil_passthru('mysqldump --default-character-set=utf8 ' . '-u %s %C -h %s %C --databases %Ls', $api->getUser(), $flag_password, $host, $flag_port, $databases); }
public function renderHelpForArcanist() { $text = ''; $levels = $this->getLevels(); $default = $this->getDefaultLevel(); foreach ($levels as $level) { $name = $this->getNameForLevel($level); $description = $this->getDescriptionForLevel($level); $default_marker = ' '; if ($level === $default) { $default_marker = '*'; } $text .= " {$default_marker} **{$name}**\n"; $text .= phutil_console_wrap($description . "\n", 8); } return $text; }
protected function drawView() { $output = array(); foreach ($this->items as $spec) { $type = $spec['type']; $item = $spec['item']; switch ($type) { case 'paragraph': $item = phutil_console_wrap($item) . "\n"; break; case 'list': $item = $item; break; } $output[] = $item; } return $this->drawLines($output); }
protected function drawView() { $indent_depth = 6; $indent_string = str_repeat(' ', $indent_depth); if ($this->bullet !== null) { $bullet = $this->bullet . ' '; $indent_depth = $indent_depth + phutil_utf8_console_strlen($bullet); } else { $bullet = ''; } $output = array(); foreach ($this->getItems() as $item) { if ($this->wrap) { $item = phutil_console_wrap($item, $indent_depth); } $output[] = $indent_string . $bullet . $item; } return $this->drawLines($output); }
public function didExecute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); if (!$this->isDryRun() && !$this->isForce()) { $console->writeOut(phutil_console_wrap(pht('Are you completely sure you really want to permanently destroy ' . 'all storage for Phabricator data? This operation can not be ' . 'undone and your data will not be recoverable if you proceed.'))); if (!phutil_console_confirm(pht('Permanently destroy all data?'))) { $console->writeOut("%s\n", pht('Cancelled.')); exit(1); } if (!phutil_console_confirm(pht('Really destroy all data forever?'))) { $console->writeOut("%s\n", pht('Cancelled.')); exit(1); } } $apis = $this->getMasterAPIs(); foreach ($apis as $api) { $patches = $this->getPatches(); if ($args->getArg('unittest-fixtures')) { $conn = $api->getConn(null); $databases = queryfx_all($conn, 'SELECT DISTINCT(TABLE_SCHEMA) AS db ' . 'FROM INFORMATION_SCHEMA.TABLES ' . 'WHERE TABLE_SCHEMA LIKE %>', PhabricatorTestCase::NAMESPACE_PREFIX); $databases = ipull($databases, 'db'); } else { $databases = $api->getDatabaseList($patches); $databases[] = $api->getDatabaseName('meta_data'); // These are legacy databases that were dropped long ago. See T2237. $databases[] = $api->getDatabaseName('phid'); $databases[] = $api->getDatabaseName('directory'); } foreach ($databases as $database) { if ($this->isDryRun()) { $console->writeOut("%s\n", pht("DRYRUN: Would drop database '%s'.", $database)); } else { $console->writeOut("%s\n", pht("Dropping database '%s'...", $database)); queryfx($api->getConn(null), 'DROP DATABASE IF EXISTS %T', $database); } } if (!$this->isDryRun()) { $console->writeOut("%s\n", pht('Storage on "%s" was destroyed.', $api->getRef()->getRefKey())); } } return 0; }
public function execute(PhutilArgumentParser $args) { $is_dry = $args->getArg('dryrun'); $is_force = $args->getArg('force'); if (!$is_dry && !$is_force) { echo phutil_console_wrap('Are you completely sure you really want to permanently destroy all ' . 'storage for Phabricator data? This operation can not be undone and ' . 'your data will not be recoverable if you proceed.'); if (!phutil_console_confirm('Permanently destroy all data?')) { echo "Cancelled.\n"; exit(1); } if (!phutil_console_confirm('Really destroy all data forever?')) { echo "Cancelled.\n"; exit(1); } } $api = $this->getAPI(); $patches = $this->getPatches(); if ($args->getArg('unittest-fixtures')) { $conn = $api->getConn(null); $databases = queryfx_all($conn, 'SELECT DISTINCT(TABLE_SCHEMA) AS db ' . 'FROM INFORMATION_SCHEMA.TABLES ' . 'WHERE TABLE_SCHEMA LIKE %>', PhabricatorTestCase::NAMESPACE_PREFIX); $databases = ipull($databases, 'db'); } else { $databases = $api->getDatabaseList($patches); $databases[] = $api->getDatabaseName('meta_data'); // These are legacy databases that were dropped long ago. See T2237. $databases[] = $api->getDatabaseName('phid'); $databases[] = $api->getDatabaseName('directory'); } foreach ($databases as $database) { if ($is_dry) { echo "DRYRUN: Would drop database '{$database}'.\n"; } else { echo "Dropping database '{$database}'...\n"; queryfx($api->getConn(null), 'DROP DATABASE IF EXISTS %T', $database); } } if (!$is_dry) { echo "Storage was destroyed.\n"; } return 0; }
public function renderLintResult(ArcanistLintResult $result) { $messages = $result->getMessages(); $path = $result->getPath(); $lines = explode("\n", $result->getData()); $text = array(); foreach ($messages as $message) { if (!$this->showAutofixPatches && $message->isAutofix()) { continue; } if ($message->isError()) { $color = 'red'; } else { $color = 'yellow'; } $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity()); $code = $message->getCode(); $name = $message->getName(); $description = $message->getDescription(); if ($message->getOtherLocations()) { $locations = array(); foreach ($message->getOtherLocations() as $location) { $locations[] = idx($location, 'path', $path) . (!empty($location['line']) ? ":{$location['line']}" : ''); } $description .= "\n" . pht('Other locations: %s', implode(', ', $locations)); } $text[] = phutil_console_format(" **<bg:{$color}> %s </bg>** (%s) __%s__\n%s\n", $severity, $code, $name, phutil_console_wrap($description, 4)); if ($message->hasFileContext()) { $text[] = $this->renderContext($message, $lines); } } if ($text) { $prefix = phutil_console_format("**>>>** %s\n\n\n", pht('Lint for %s:', phutil_console_format('__%s__', $path))); return $prefix . implode("\n", $text); } else { return null; } }
protected function buildBaseCommit($symbolic_commit) { if ($symbolic_commit !== null) { if ($symbolic_commit == ArcanistGitAPI::GIT_MAGIC_ROOT_COMMIT) { $this->setBaseCommitExplanation('you explicitly specified the empty tree.'); return $symbolic_commit; } list($err, $merge_base) = $this->execManualLocal('merge-base %s %s', $symbolic_commit, $this->getHeadCommit()); if ($err) { throw new ArcanistUsageException("Unable to find any git commit named '{$symbolic_commit}' in " . "this repository."); } if ($this->symbolicHeadCommit === null) { $this->setBaseCommitExplanation("it is the merge-base of the explicitly specified base commit " . "'{$symbolic_commit}' and HEAD."); } else { $this->setBaseCommitExplanation("it is the merge-base of the explicitly specified base commit " . "'{$symbolic_commit}' and the explicitly specified head " . "commit '{$this->symbolicHeadCommit}'."); } return trim($merge_base); } // Detect zero-commit or one-commit repositories. There is only one // relative-commit value that makes any sense in these repositories: the // empty tree. list($err) = $this->execManualLocal('rev-parse --verify HEAD^'); if ($err) { list($err) = $this->execManualLocal('rev-parse --verify HEAD'); if ($err) { $this->repositoryHasNoCommits = true; } if ($this->repositoryHasNoCommits) { $this->setBaseCommitExplanation('the repository has no commits.'); } else { $this->setBaseCommitExplanation('the repository has only one commit.'); } return self::GIT_MAGIC_ROOT_COMMIT; } if ($this->getBaseCommitArgumentRules() || $this->getConfigurationManager()->getConfigFromAnySource('base')) { $base = $this->resolveBaseCommit(); if (!$base) { throw new ArcanistUsageException("None of the rules in your 'base' configuration matched a valid " . "commit. Adjust rules or specify which commit you want to use " . "explicitly."); } return $base; } $do_write = false; $default_relative = null; $working_copy = $this->getWorkingCopyIdentity(); if ($working_copy) { $default_relative = $working_copy->getProjectConfig('git.default-relative-commit'); $this->setBaseCommitExplanation("it is the merge-base of '{$default_relative}' and HEAD, as " . "specified in 'git.default-relative-commit' in '.arcconfig'. This " . "setting overrides other settings."); } if (!$default_relative) { list($err, $upstream) = $this->execManualLocal('rev-parse --abbrev-ref --symbolic-full-name %s', '@{upstream}'); if (!$err) { $default_relative = trim($upstream); $this->setBaseCommitExplanation("it is the merge-base of '{$default_relative}' (the Git upstream " . "of the current branch) HEAD."); } } if (!$default_relative) { $default_relative = $this->readScratchFile('default-relative-commit'); $default_relative = trim($default_relative); if ($default_relative) { $this->setBaseCommitExplanation("it is the merge-base of '{$default_relative}' and HEAD, as " . "specified in '.git/arc/default-relative-commit'."); } } if (!$default_relative) { // TODO: Remove the history lesson soon. echo phutil_console_format("<bg:green>** Select a Default Commit Range **</bg>\n\n"); echo phutil_console_wrap("You're running a command which operates on a range of revisions " . "(usually, from some revision to HEAD) but have not specified the " . "revision that should determine the start of the range.\n\n" . "Previously, arc assumed you meant 'HEAD^' when you did not specify " . "a start revision, but this behavior does not make much sense in " . "most workflows outside of Facebook's historic git-svn workflow.\n\n" . "arc no longer assumes 'HEAD^'. You must specify a relative commit " . "explicitly when you invoke a command (e.g., `arc diff HEAD^`, not " . "just `arc diff`) or select a default for this working copy.\n\n" . "In most cases, the best default is 'origin/master'. You can also " . "select 'HEAD^' to preserve the old behavior, or some other remote " . "or branch. But you almost certainly want to select " . "'origin/master'.\n\n" . "(Technically: the merge-base of the selected revision and HEAD is " . "used to determine the start of the commit range.)"); $prompt = 'What default do you want to use? [origin/master]'; $default = phutil_console_prompt($prompt); if (!strlen(trim($default))) { $default = 'origin/master'; } $default_relative = $default; $do_write = true; } list($object_type) = $this->execxLocal('cat-file -t %s', $default_relative); if (trim($object_type) !== 'commit') { throw new Exception("Relative commit '{$default_relative}' is not the name of a commit!"); } if ($do_write) { // Don't perform this write until we've verified that the object is a // valid commit name. $this->writeScratchFile('default-relative-commit', $default_relative); $this->setBaseCommitExplanation("it is the merge-base of '{$default_relative}' and HEAD, as you " . "just specified."); } list($merge_base) = $this->execxLocal('merge-base %s HEAD', $default_relative); return trim($merge_base); }
public function run() { $console = PhutilConsole::getConsole(); $linters = id(new PhutilClassMapQuery())->setAncestorClass('ArcanistLinter')->execute(); try { $built = $this->newLintEngine()->buildLinters(); } catch (ArcanistNoEngineException $ex) { $built = array(); } $linter_info = $this->getLintersInfo($linters, $built); $status_map = $this->getStatusMap(); $pad = ' '; $color_map = array('configured' => 'green', 'available' => 'yellow', 'error' => 'red'); $is_verbose = $this->getArgument('verbose'); $exact = $this->getArgument('exact'); $search_terms = $this->getArgument('search'); if ($exact && $search_terms) { throw new ArcanistUsageException('Specify either search expression or exact name'); } if ($exact) { $linter_info = $this->findExactNames($linter_info, $exact); if (!$linter_info) { $console->writeOut("%s\n", pht('No match found. Try `%s %s` to search for a linter.', 'arc linters --search', $exact[0])); return; } $is_verbose = true; } if ($search_terms) { $linter_info = $this->filterByNames($linter_info, $search_terms); } foreach ($linter_info as $key => $linter) { $status = $linter['status']; $color = $color_map[$status]; $text = $status_map[$status]; $print_tail = false; $console->writeOut("<bg:" . $color . ">** %s **</bg> **%s** (%s)\n", $text, nonempty($linter['name'], '-'), $linter['short']); if ($linter['exception']) { $console->writeOut("\n%s**%s**\n%s\n", $pad, get_class($linter['exception']), phutil_console_wrap($linter['exception']->getMessage(), strlen($pad))); $print_tail = true; } if ($is_verbose) { $version = $linter['version']; $uri = $linter['uri']; if ($version || $uri) { $console->writeOut("\n"); $print_tail = true; } if ($version) { $console->writeOut("%s%s **%s**\n", $pad, pht('Version'), $version); } if ($uri) { $console->writeOut("%s__%s__\n", $pad, $linter['uri']); } $description = $linter['description']; if ($description) { $console->writeOut("\n%s\n", phutil_console_wrap($linter['description'], strlen($pad))); $print_tail = true; } $options = $linter['options']; if ($options) { $console->writeOut("\n%s**%s**\n\n", $pad, pht('Configuration Options')); $last_option = last_key($options); foreach ($options as $option => $option_spec) { $console->writeOut("%s__%s__ (%s)\n", $pad, $option, $option_spec['type']); $console->writeOut("%s\n", phutil_console_wrap($option_spec['help'], strlen($pad) + 2)); if ($option != $last_option) { $console->writeOut("\n"); } } $print_tail = true; } if ($print_tail) { $console->writeOut("\n"); } } } if (!$is_verbose) { $console->writeOut("%s\n", pht('(Run `%s` for more details.)', 'arc linters --verbose')); } }
* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ $root = dirname(dirname(dirname(__FILE__))); require_once $root . '/scripts/__init_script__.php'; require_once $root . '/scripts/__init_env__.php'; phutil_require_module('phutil', 'console'); phutil_require_module('phutil', 'future/exec'); if (empty($argv[1])) { echo "usage: test_connection.php <repository_callsign>\n"; exit(1); } echo phutil_console_wrap(phutil_console_format('This script will test that you have configured valid credentials for ' . 'access to a repository, so the Phabricator daemons can pull from it. ' . 'You should run this as the **same user you will run the daemons as**, ' . 'from the **same machine they will run from**. Doing this will help ' . 'detect various problems with your configuration, such as SSH issues.')); list($whoami) = execx('whoami'); $whoami = trim($whoami); $ok = phutil_console_confirm("Do you want to continue as '{$whoami}'?"); if (!$ok) { die(1); } $callsign = $argv[1]; echo "Loading '{$callsign}' repository...\n"; $repository = id(new PhabricatorRepository())->loadOneWhere('callsign = %s', $argv[1]); if (!$repository) { throw new Exception("No such repository exists!"); } $vcs = $repository->getVersionControlSystem(); PhutilServiceProfiler::installEchoListener(); echo "Trying to connect to the remote...\n";
private final function doUpgradeSchemata($apply_only, $no_quickstart, $init_only) { $api = $this->getAPI(); $applied = $this->getApi()->getAppliedPatches(); if ($applied === null) { if ($this->dryRun) { echo pht("DRYRUN: Patch metadata storage doesn't exist yet, " . "it would be created.\n"); return 0; } if ($apply_only) { throw new PhutilArgumentUsageException(pht('Storage has not been initialized yet, you must initialize ' . 'storage before selectively applying patches.')); return 1; } $legacy = $api->getLegacyPatches($this->patches); if ($legacy || $no_quickstart || $init_only) { // If we have legacy patches, we can't quickstart. $api->createDatabase('meta_data'); $api->createTable('meta_data', 'patch_status', array('patch VARCHAR(255) NOT NULL PRIMARY KEY COLLATE utf8_general_ci', 'applied INT UNSIGNED NOT NULL')); foreach ($legacy as $patch) { $api->markPatchApplied($patch); } } else { echo pht('Loading quickstart template...') . "\n"; $root = dirname(phutil_get_library_root('phabricator')); $sql = $root . '/resources/sql/quickstart.sql'; $api->applyPatchSQL($sql); } } if ($init_only) { echo pht('Storage initialized.') . "\n"; return 0; } $applied = $api->getAppliedPatches(); $applied = array_fuse($applied); $skip_mark = false; if ($apply_only) { if (isset($applied[$apply_only])) { unset($applied[$apply_only]); $skip_mark = true; if (!$this->force && !$this->dryRun) { echo phutil_console_wrap(pht("Patch '%s' has already been applied. Are you sure you want " . "to apply it again? This may put your storage in a state " . "that the upgrade scripts can not automatically manage.", $apply_only)); if (!phutil_console_confirm(pht('Apply patch again?'))) { echo pht('Cancelled.') . "\n"; return 1; } } } } while (true) { $applied_something = false; foreach ($this->patches as $key => $patch) { if (isset($applied[$key])) { unset($this->patches[$key]); continue; } if ($apply_only && $apply_only != $key) { unset($this->patches[$key]); continue; } $can_apply = true; foreach ($patch->getAfter() as $after) { if (empty($applied[$after])) { if ($apply_only) { echo pht("Unable to apply patch '%s' because it depends " . "on patch '%s', which has not been applied.\n", $apply_only, $after); return 1; } $can_apply = false; break; } } if (!$can_apply) { continue; } $applied_something = true; if ($this->dryRun) { echo pht("DRYRUN: Would apply patch '%s'.", $key) . "\n"; } else { echo pht("Applying patch '%s'...", $key) . "\n"; $t_begin = microtime(true); $api->applyPatch($patch); $t_end = microtime(true); if (!$skip_mark) { $api->markPatchApplied($key, $t_end - $t_begin); } } unset($this->patches[$key]); $applied[$key] = true; } if (!$applied_something) { if (count($this->patches)) { throw new Exception(pht('Some patches could not be applied: %s', implode(', ', array_keys($this->patches)))); } else { if (!$this->dryRun && !$apply_only) { echo pht("Storage is up to date. Use '%s' for details.", 'storage status') . "\n"; } } break; } } }
phutil_require_module('phabricator', 'infrastructure/setup/sql'); define('SCHEMA_VERSION_TABLE_NAME', 'schema_version'); // TODO: getopt() is super terrible, move to something less terrible. $options = getopt('fhv:u:p:') + array('v' => null, 'u' => null, 'p' => null); foreach (array('h', 'f') as $key) { // By default, these keys are set to 'false' to indicate that the flag was // passed. if (array_key_exists($key, $options)) { $options[$key] = true; } } if (!empty($options['h']) || $options['v'] && !is_numeric($options['v'])) { usage(); } if (empty($options['f'])) { echo phutil_console_wrap("Before running this script, you should take down the Phabricator web " . "interface and stop any running Phabricator daemons."); if (!phutil_console_confirm('Are you ready to continue?')) { echo "Cancelled.\n"; exit(1); } } // Use always the version from the commandline if it is defined $next_version = isset($options['v']) ? (int) $options['v'] : null; $conf = DatabaseConfigurationProvider::getConfiguration(); if ($options['u']) { $conn_user = $options['u']; $conn_pass = $options['p']; } else { $conn_user = $conf->getUser(); $conn_pass = $conf->getPassword(); }
/** * @task message */ private function getCommitMessageFromUser() { $conduit = $this->getConduit(); $template = null; if (!$this->getArgument('verbatim')) { $saved = $this->readScratchFile('create-message'); if ($saved) { $where = $this->getReadableScratchFilePath('create-message'); $preview = explode("\n", $saved); $preview = array_shift($preview); $preview = trim($preview); $preview = id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(64)->truncateString($preview); if ($preview) { $preview = pht('Message begins:') . "\n\n {$preview}\n\n"; } else { $preview = null; } echo pht("You have a saved revision message in '%s'.\n%s" . "You can use this message, or discard it.", $where, $preview); $use = phutil_console_confirm(pht('Do you want to use this message?'), $default_no = false); if ($use) { $template = $saved; } else { $this->removeScratchFile('create-message'); } } } $template_is_default = false; $notes = array(); $included = array(); list($fields, $notes, $included_commits) = $this->getDefaultCreateFields(); if ($template) { $fields = array(); $notes = array(); } else { if (!$fields) { $template_is_default = true; } if ($notes) { $commit = head($this->getRepositoryAPI()->getLocalCommitInformation()); $template = $commit['message']; } else { $template = $conduit->callMethodSynchronous('differential.getcommitmessage', array('revision_id' => null, 'edit' => 'create', 'fields' => $fields)); } } $old_message = $template; $included = array(); if ($included_commits) { foreach ($included_commits as $commit) { $included[] = ' ' . $commit; } if (!$this->isRawDiffSource()) { $message = pht('Included commits in branch %s:', $this->getRepositoryAPI()->getBranchName()); } else { $message = pht('Included commits:'); } $included = array_merge(array('', $message, ''), $included); } $issues = array_merge(array(pht('NEW DIFFERENTIAL REVISION'), pht('Describe the changes in this new revision.')), $included, array('', pht('arc could not identify any existing revision in your working copy.'), pht('If you intended to update an existing revision, use:'), '', ' $ arc diff --update <revision>')); if ($notes) { $issues = array_merge($issues, array(''), $notes); } $done = false; $first = true; while (!$done) { $template = rtrim($template, "\r\n") . "\n\n"; foreach ($issues as $issue) { $template .= rtrim('# ' . $issue) . "\n"; } $template .= "\n"; if ($first && $this->getArgument('verbatim') && !$template_is_default) { $new_template = $template; } else { $new_template = $this->newInteractiveEditor($template)->setName('new-commit')->editInteractively(); } $first = false; if ($template_is_default && $new_template == $template) { throw new ArcanistUsageException(pht('Template not edited.')); } $template = ArcanistCommentRemover::removeComments($new_template); // With --raw-command, we may not have a repository API. if ($this->hasRepositoryAPI()) { $repository_api = $this->getRepositoryAPI(); // special check for whether to amend here. optimizes a common git // workflow. we can't do this for mercurial because the mq extension // is popular and incompatible with hg commit --amend ; see T2011. $should_amend = count($included_commits) == 1 && $repository_api instanceof ArcanistGitAPI && $this->shouldAmend(); } else { $should_amend = false; } if ($should_amend) { $wrote = rtrim($old_message) != rtrim($template); if ($wrote) { $repository_api->amendCommit($template); $where = pht('commit message'); } } else { $wrote = $this->writeScratchFile('create-message', $template); $where = "'" . $this->getReadableScratchFilePath('create-message') . "'"; } try { $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($template); $message->pullDataFromConduit($conduit); $this->validateCommitMessage($message); $done = true; } catch (ArcanistDifferentialCommitMessageParserException $ex) { echo pht('Commit message has errors:') . "\n\n"; $issues = array(pht('Resolve these errors:')); foreach ($ex->getParserErrors() as $error) { echo phutil_console_wrap("- " . $error . "\n", 6); $issues[] = ' - ' . $error; } echo "\n"; echo pht('You must resolve these errors to continue.'); $again = phutil_console_confirm(pht('Do you want to edit the message?'), $default_no = false); if ($again) { // Keep going. } else { $saved = null; if ($wrote) { $saved = pht('A copy was saved to %s.', $where); } throw new ArcanistUsageException(pht('Message has unresolved errors.') . " {$saved}"); } } catch (Exception $ex) { if ($wrote) { echo phutil_console_wrap(pht('(Message saved to %s.)', $where) . "\n"); } throw $ex; } } return $message; }
ini_set('display_errors', 1); $include_path = ini_get('include_path'); ini_set('include_path', $include_path . PATH_SEPARATOR . dirname(__FILE__) . '/../../'); @(include_once 'libphutil/scripts/__init_script__.php'); if (!@constant('__LIBPHUTIL__')) { echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to " . "include the parent directory of libphutil/.\n"; exit(1); } phutil_load_library(dirname(__FILE__) . '/../src/'); // NOTE: This is dangerous in general, but we know we're in a script context and // are not vulnerable to CSRF. AphrontWriteGuard::allowDangerousUnguardedWrites(true); require_once dirname(dirname(__FILE__)) . '/conf/__init_conf__.php'; $env = isset($_SERVER['PHABRICATOR_ENV']) ? $_SERVER['PHABRICATOR_ENV'] : getenv('PHABRICATOR_ENV'); if (!$env) { echo phutil_console_wrap(phutil_console_format("**ERROR**: PHABRICATOR_ENV Not Set\n\n" . "Define the __PHABRICATOR_ENV__ environment variable before running " . "this script. You can do it on the command line like this:\n\n" . " \$ PHABRICATOR_ENV=__custom/myconfig__ %s ...\n\n" . "Replace __custom/myconfig__ with the path to your configuration file. " . "For more information, see the 'Configuration Guide' in the " . "Phabricator documentation.\n\n", $argv[0])); exit(1); } $conf = phabricator_read_config_file($env); $conf['phabricator.env'] = $env; PhabricatorEnv::setEnvConfig($conf); phutil_load_library('arcanist/src'); foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) { phutil_load_library($library); } PhutilErrorHandler::initialize(); PhabricatorEventEngine::initialize(); $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone'); if ($tz) { date_default_timezone_set($tz); }
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; }
public function run() { $source = $this->getSource(); $param = $this->getSourceParam(); try { switch ($source) { case self::SOURCE_PATCH: if ($param == '-') { $patch = @file_get_contents('php://stdin'); if (!strlen($patch)) { throw new ArcanistUsageException(pht('Failed to read patch from stdin!')); } } else { $patch = Filesystem::readFile($param); } $bundle = ArcanistBundle::newFromDiff($patch); break; case self::SOURCE_BUNDLE: $path = $this->getArgument('arcbundle'); $bundle = ArcanistBundle::newFromArcBundle($path); break; case self::SOURCE_REVISION: $bundle = $this->loadRevisionBundleFromConduit($this->getConduit(), $param); break; case self::SOURCE_DIFF: $bundle = $this->loadDiffBundleFromConduit($this->getConduit(), $param); break; } } catch (ConduitClientException $ex) { if ($ex->getErrorCode() == 'ERR-INVALID-SESSION') { // Phabricator is not configured to allow anonymous access to // Differential. $this->authenticateConduit(); return $this->run(); } else { throw $ex; } } $try_encoding = nonempty($this->getArgument('encoding'), null); if (!$try_encoding) { if ($this->requiresConduit()) { try { $try_encoding = $this->getRepositoryEncoding(); } catch (ConduitClientException $e) { $try_encoding = null; } } } if ($try_encoding) { $bundle->setEncoding($try_encoding); } $sanity_check = !$this->getArgument('force', false); // we should update the working copy before we do ANYTHING else to // the working copy if ($this->shouldUpdateWorkingCopy()) { $this->updateWorkingCopy(); } if ($sanity_check) { $this->requireCleanWorkingCopy(); } $repository_api = $this->getRepositoryAPI(); $has_base_revision = $repository_api->hasLocalCommit($bundle->getBaseRevision()); if ($this->canBranch() && ($this->shouldBranch() || $this->shouldCommit() && $has_base_revision)) { if ($repository_api instanceof ArcanistGitAPI) { $original_branch = $repository_api->getBranchName(); } else { if ($repository_api instanceof ArcanistMercurialAPI) { $original_branch = $repository_api->getActiveBookmark(); } } // If we weren't on a branch, then record the ref we'll return to // instead. if ($original_branch === null) { if ($repository_api instanceof ArcanistGitAPI) { $original_branch = $repository_api->getCanonicalRevisionName('HEAD'); } else { if ($repository_api instanceof ArcanistMercurialAPI) { $original_branch = $repository_api->getCanonicalRevisionName('.'); } } } $new_branch = $this->createBranch($bundle, $has_base_revision); } if (!$has_base_revision && $this->shouldApplyDependencies()) { $this->applyDependencies($bundle); } if ($sanity_check) { $this->sanityCheck($bundle); } if ($repository_api instanceof ArcanistSubversionAPI) { $patch_err = 0; $copies = array(); $deletes = array(); $patches = array(); $propset = array(); $adds = array(); $symlinks = array(); $changes = $bundle->getChanges(); foreach ($changes as $change) { $type = $change->getType(); $should_patch = true; $filetype = $change->getFileType(); switch ($filetype) { case ArcanistDiffChangeType::FILE_SYMLINK: $should_patch = false; $symlinks[] = $change; break; } switch ($type) { case ArcanistDiffChangeType::TYPE_MOVE_AWAY: case ArcanistDiffChangeType::TYPE_MULTICOPY: case ArcanistDiffChangeType::TYPE_DELETE: $path = $change->getCurrentPath(); $fpath = $repository_api->getPath($path); if (!@file_exists($fpath)) { $ok = phutil_console_confirm(pht("Patch deletes file '%s', but the file does not exist in " . "the working copy. Continue anyway?", $path)); if (!$ok) { throw new ArcanistUserAbortException(); } } else { $deletes[] = $change->getCurrentPath(); } $should_patch = false; break; case ArcanistDiffChangeType::TYPE_COPY_HERE: case ArcanistDiffChangeType::TYPE_MOVE_HERE: $path = $change->getOldPath(); $fpath = $repository_api->getPath($path); if (!@file_exists($fpath)) { $cpath = $change->getCurrentPath(); if ($type == ArcanistDiffChangeType::TYPE_COPY_HERE) { $verbs = pht('copies'); } else { $verbs = pht('moves'); } $ok = phutil_console_confirm(pht("Patch %s '%s' to '%s', but source path does not exist " . "in the working copy. Continue anyway?", $verbs, $path, $cpath)); if (!$ok) { throw new ArcanistUserAbortException(); } } else { $copies[] = array($change->getOldPath(), $change->getCurrentPath()); } break; case ArcanistDiffChangeType::TYPE_ADD: $adds[] = $change->getCurrentPath(); break; } if ($should_patch) { $cbundle = ArcanistBundle::newFromChanges(array($change)); $patches[$change->getCurrentPath()] = $cbundle->toUnifiedDiff(); $prop_old = $change->getOldProperties(); $prop_new = $change->getNewProperties(); $props = $prop_old + $prop_new; foreach ($props as $key => $ignored) { if (idx($prop_old, $key) !== idx($prop_new, $key)) { $propset[$change->getCurrentPath()][$key] = idx($prop_new, $key); } } } } // Before we start doing anything, create all the directories we're going // to add files to if they don't already exist. foreach ($copies as $copy) { list($src, $dst) = $copy; $this->createParentDirectoryOf($dst); } foreach ($patches as $path => $patch) { $this->createParentDirectoryOf($path); } foreach ($adds as $add) { $this->createParentDirectoryOf($add); } // TODO: The SVN patch workflow likely does not work on windows because // of the (cd ...) stuff. foreach ($copies as $copy) { list($src, $dst) = $copy; passthru(csprintf('(cd %s; svn cp %s %s)', $repository_api->getPath(), ArcanistSubversionAPI::escapeFileNameForSVN($src), ArcanistSubversionAPI::escapeFileNameForSVN($dst))); } foreach ($deletes as $delete) { passthru(csprintf('(cd %s; svn rm %s)', $repository_api->getPath(), ArcanistSubversionAPI::escapeFileNameForSVN($delete))); } foreach ($symlinks as $symlink) { $link_target = $symlink->getSymlinkTarget(); $link_path = $symlink->getCurrentPath(); switch ($symlink->getType()) { case ArcanistDiffChangeType::TYPE_ADD: case ArcanistDiffChangeType::TYPE_CHANGE: case ArcanistDiffChangeType::TYPE_MOVE_HERE: case ArcanistDiffChangeType::TYPE_COPY_HERE: execx('(cd %s && ln -sf %s %s)', $repository_api->getPath(), $link_target, $link_path); break; } } foreach ($patches as $path => $patch) { $err = null; if ($patch) { $tmp = new TempFile(); Filesystem::writeFile($tmp, $patch); passthru(csprintf('(cd %s; patch -p0 < %s)', $repository_api->getPath(), $tmp), $err); } else { passthru(csprintf('(cd %s; touch %s)', $repository_api->getPath(), $path), $err); } if ($err) { $patch_err = max($patch_err, $err); } } foreach ($adds as $add) { passthru(csprintf('(cd %s; svn add %s)', $repository_api->getPath(), ArcanistSubversionAPI::escapeFileNameForSVN($add))); } foreach ($propset as $path => $changes) { foreach ($changes as $prop => $value) { if ($prop == 'unix:filemode') { // Setting this property also changes the file mode. $prop = 'svn:executable'; $value = octdec($value) & 0111 ? 'on' : null; } if ($value === null) { passthru(csprintf('(cd %s; svn propdel %s %s)', $repository_api->getPath(), $prop, ArcanistSubversionAPI::escapeFileNameForSVN($path))); } else { passthru(csprintf('(cd %s; svn propset %s %s %s)', $repository_api->getPath(), $prop, $value, ArcanistSubversionAPI::escapeFileNameForSVN($path))); } } } if ($patch_err == 0) { echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('OKAY'), pht('Successfully applied patch to the working copy.')); } else { echo phutil_console_format("\n\n<bg:yellow>** %s **</bg> %s\n", pht('WARNING'), pht("Some hunks could not be applied cleanly by the unix '%s' " . "utility. Your working copy may be different from the revision's " . "base, or you may be in the wrong subdirectory. You can export " . "the raw patch file using '%s', and then try to apply it by " . "fiddling with options to '%s' (particularly, %s), or manually. " . "The output above, from '%s', may be helpful in " . "figuring out what went wrong.", 'patch', 'arc export --unified', 'patch', '-p', 'patch')); } return $patch_err; } else { if ($repository_api instanceof ArcanistGitAPI) { $patchfile = new TempFile(); Filesystem::writeFile($patchfile, $bundle->toGitPatch()); $passthru = new PhutilExecPassthru('git apply --index --reject -- %s', $patchfile); $passthru->setCWD($repository_api->getPath()); $err = $passthru->execute(); if ($err) { echo phutil_console_format("\n<bg:red>** %s **</bg>\n", pht('Patch Failed!')); // NOTE: Git patches may fail if they change the case of a filename // (for instance, from 'example.c' to 'Example.c'). As of now, Git // can not apply these patches on case-insensitive filesystems and // there is no way to build a patch which works. throw new ArcanistUsageException(pht('Unable to apply patch!')); } // in case there were any submodule changes involved $repository_api->execpassthru('submodule update --init --recursive'); if ($this->shouldCommit()) { if ($bundle->getFullAuthor()) { $author_cmd = csprintf('--author=%s', $bundle->getFullAuthor()); } else { $author_cmd = ''; } $commit_message = $this->getCommitMessage($bundle); $future = $repository_api->execFutureLocal('commit -a %C -F - --no-verify', $author_cmd); $future->write($commit_message); $future->resolvex(); $verb = pht('committed'); } else { $verb = pht('applied'); } if ($this->canBranch() && !$this->shouldBranch() && $this->shouldCommit() && $has_base_revision) { $repository_api->execxLocal('checkout %s', $original_branch); $ex = null; try { $repository_api->execxLocal('cherry-pick %s', $new_branch); } catch (Exception $ex) { // do nothing } $repository_api->execxLocal('branch -D %s', $new_branch); if ($ex) { echo phutil_console_format("\n<bg:red>** %s**</bg>\n", pht('Cherry Pick Failed!')); throw $ex; } } echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('OKAY'), pht('Successfully %s patch.', $verb)); } else { if ($repository_api instanceof ArcanistMercurialAPI) { $future = $repository_api->execFutureLocal('import --no-commit -'); $future->write($bundle->toGitPatch()); try { $future->resolvex(); } catch (CommandException $ex) { echo phutil_console_format("\n<bg:red>** %s **</bg>\n", pht('Patch Failed!')); $stderr = $ex->getStdErr(); if (preg_match('/case-folding collision/', $stderr)) { echo phutil_console_wrap(phutil_console_format("\n<bg:yellow>** %s **</bg> %s\n", pht('WARNING'), pht("This patch may have failed because it attempts to change " . "the case of a filename (for instance, from '%s' to '%s'). " . "Mercurial cannot apply patches like this on case-insensitive " . "filesystems. You must apply this patch manually.", 'example.c', 'Example.c'))); } throw $ex; } if ($this->shouldCommit()) { $author = coalesce($bundle->getFullAuthor(), $bundle->getAuthorName()); if ($author !== null) { $author_cmd = csprintf('-u %s', $author); } else { $author_cmd = ''; } $commit_message = $this->getCommitMessage($bundle); $future = $repository_api->execFutureLocal('commit %C -l -', $author_cmd); $future->write($commit_message); $future->resolvex(); if (!$this->shouldBranch() && $has_base_revision) { $original_rev = $repository_api->getCanonicalRevisionName($original_branch); $current_parent = $repository_api->getCanonicalRevisionName(hgsprintf('%s^', $new_branch)); $err = 0; if ($original_rev != $current_parent) { list($err) = $repository_api->execManualLocal('rebase --dest %s --rev %s', hgsprintf('%s', $original_branch), hgsprintf('%s', $new_branch)); } $repository_api->execxLocal('bookmark --delete %s', $new_branch); if ($err) { $repository_api->execManualLocal('rebase --abort'); throw new ArcanistUsageException(phutil_console_format("\n<bg:red>** %s**</bg>\n", pht('Rebase onto %s failed!', $original_branch))); } } $verb = pht('committed'); } else { $verb = pht('applied'); } echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('OKAY'), pht('Successfully %s patch.', $verb)); } else { throw new Exception(pht('Unknown version control system.')); } } } return 0; }
public function run() { $console = PhutilConsole::getConsole(); $linters = id(new PhutilSymbolLoader())->setAncestorClass('ArcanistLinter')->loadObjects(); try { $built = $this->newLintEngine()->buildLinters(); } catch (ArcanistNoEngineException $ex) { $built = array(); } // Note that an engine can emit multiple linters of the same class to run // different rulesets on different groups of files, so these linters do not // necessarily have unique classes or types. $groups = array(); foreach ($built as $linter) { $groups[get_class($linter)][] = $linter; } $linter_info = array(); foreach ($linters as $key => $linter) { $installed = idx($groups, $key, array()); $exception = null; if ($installed) { $status = 'configured'; try { $version = head($installed)->getVersion(); } catch (Exception $ex) { $status = 'error'; $exception = $ex; } } else { $status = 'available'; $version = null; } $linter_info[$key] = array('short' => $linter->getLinterConfigurationName(), 'class' => get_class($linter), 'status' => $status, 'version' => $version, 'name' => $linter->getInfoName(), 'uri' => $linter->getInfoURI(), 'description' => $linter->getInfoDescription(), 'exception' => $exception, 'options' => $linter->getLinterConfigurationOptions()); } $linter_info = isort($linter_info, 'short'); $status_map = $this->getStatusMap(); $pad = ' '; $color_map = array('configured' => 'green', 'available' => 'yellow', 'error' => 'red'); foreach ($linter_info as $key => $linter) { $status = $linter['status']; $color = $color_map[$status]; $text = $status_map[$status]; $print_tail = false; $console->writeOut("<bg:" . $color . ">** %s **</bg> **%s** (%s)\n", $text, nonempty($linter['short'], '-'), $linter['name']); if ($linter['exception']) { $console->writeOut("\n%s**%s**\n%s\n", $pad, get_class($linter['exception']), phutil_console_wrap($linter['exception']->getMessage(), strlen($pad))); $print_tail = true; } $version = $linter['version']; $uri = $linter['uri']; if ($version || $uri) { $console->writeOut("\n"); $print_tail = true; } if ($version) { $console->writeOut("%s%s **%s**\n", $pad, pht('Version'), $version); } if ($uri) { $console->writeOut("%s__%s__\n", $pad, $linter['uri']); } $description = $linter['description']; if ($description) { $console->writeOut("\n%s\n", phutil_console_wrap($linter['description'], strlen($pad))); $print_tail = true; } $options = $linter['options']; if ($options && $this->getArgument('verbose')) { $console->writeOut("\n%s**%s**\n\n", $pad, pht('Configuration Options')); $last_option = last_key($options); foreach ($options as $option => $option_spec) { $console->writeOut("%s__%s__ (%s)\n", $pad, $option, $option_spec['type']); $console->writeOut("%s\n", phutil_console_wrap($option_spec['help'], strlen($pad) + 2)); if ($option != $last_option) { $console->writeOut("\n"); } } $print_tail = true; } if ($print_tail) { $console->writeOut("\n"); } } if (!$this->getArgument('verbose')) { $console->writeOut("%s\n", pht('(Run `%s` for more details.)', 'arc linters --verbose')); } }