public function handleMessage(PhutilConsoleMessage $message)
 {
     $data = $message->getData();
     $type = $message->getType();
     switch ($type) {
         case PhutilConsoleMessage::TYPE_CONFIRM:
             $ok = phutil_console_confirm($data['prompt'], !$data['default']);
             return $this->buildMessage(PhutilConsoleMessage::TYPE_INPUT, $ok);
         case PhutilConsoleMessage::TYPE_PROMPT:
             $response = phutil_console_prompt($data['prompt'], idx($data, 'history'));
             return $this->buildMessage(PhutilConsoleMessage::TYPE_INPUT, $response);
         case PhutilConsoleMessage::TYPE_OUT:
             $this->writeText(STDOUT, $data);
             return null;
         case PhutilConsoleMessage::TYPE_ERR:
             $this->writeText(STDERR, $data);
             return null;
         case PhutilConsoleMessage::TYPE_LOG:
             if ($this->enableLog) {
                 $this->writeText(STDERR, $data);
             }
             return null;
         default:
             if ($this->handler) {
                 return call_user_func($this->handler, $message);
             } else {
                 throw new Exception("Received unknown console message of type '{$type}'.");
             }
     }
 }
Beispiel #2
0
/**
 * @group console
 */
function phutil_console_confirm($prompt, $default_no = true)
{
    $prompt_options = $default_no ? '[y/N]' : '[Y/n]';
    do {
        $response = phutil_console_prompt($prompt . ' ' . $prompt_options);
        $c = trim(strtolower($response));
    } while ($c != 'y' && $c != 'n' && $c != '');
    echo "\n";
    if ($default_no) {
        return $c == 'y';
    } else {
        return $c != 'n';
    }
}
 public function run()
 {
     $uri = $this->determineConduitURI();
     $this->setConduitURI($uri);
     $configuration_manager = $this->getConfigurationManager();
     echo "Installing certificate for '{$uri}'...\n";
     $config = $configuration_manager->readUserConfigurationFile();
     echo "Trying to connect to server...\n";
     $conduit = $this->establishConduit()->getConduit();
     try {
         $conduit->callMethodSynchronous('conduit.ping', array());
     } catch (Exception $ex) {
         throw new ArcanistUsageException('Failed to connect to server: ' . $ex->getMessage());
     }
     echo "Connection OK!\n";
     $token_uri = new PhutilURI($uri);
     $token_uri->setPath('/conduit/token/');
     echo "\n";
     echo phutil_console_format("**LOGIN TO PHABRICATOR**\n");
     echo "Open this page in your browser and login to Phabricator if " . "necessary:\n";
     echo "\n";
     echo "    {$token_uri}\n";
     echo "\n";
     echo 'Then paste the token on that page below.';
     do {
         $token = phutil_console_prompt('Paste token from that page:');
         $token = trim($token);
         if (strlen($token)) {
             break;
         }
     } while (true);
     echo "\n";
     echo "Downloading authentication certificate...\n";
     $info = $conduit->callMethodSynchronous('conduit.getcertificate', array('token' => $token, 'host' => $uri));
     $user = $info['username'];
     echo "Installing certificate for '{$user}'...\n";
     $config['hosts'][$uri] = array('user' => $user, 'cert' => $info['certificate']);
     echo "Writing ~/.arcrc...\n";
     $configuration_manager->writeUserConfigurationFile($config);
     echo phutil_console_format("<bg:green>** SUCCESS! **</bg> Certificate installed.\n");
     return 0;
 }
 public function execute(PhutilArgumentParser $args)
 {
     $console = PhutilConsole::getConsole();
     $console->getServer()->setEnableLog(true);
     PhabricatorLDAPAuthProvider::assertLDAPExtensionInstalled();
     $provider = PhabricatorLDAPAuthProvider::getLDAPProvider();
     if (!$provider) {
         $console->writeOut("%s\n", pht('The LDAP authentication provider is not enabled.'));
         exit(1);
     }
     if (!function_exists('ldap_connect')) {
         $console->writeOut("%s\n", pht('The LDAP extension is not enabled.'));
         exit(1);
     }
     $adapter = $provider->getAdapter();
     $console->writeOut("%s\n", pht('Enter LDAP Credentials'));
     $username = phutil_console_prompt(pht('LDAP Username: '******'You must enter an LDAP username.'));
     }
     phutil_passthru('stty -echo');
     $password = phutil_console_prompt(pht('LDAP Password: '******'stty echo');
     if (!strlen($password)) {
         throw new PhutilArgumentUsageException(pht('You must enter an LDAP password.'));
     }
     $adapter->setLoginUsername($username);
     $adapter->setLoginPassword(new PhutilOpaqueEnvelope($password));
     $console->writeOut("\n");
     $console->writeOut("%s\n", pht('Connecting to LDAP...'));
     $account_id = $adapter->getAccountID();
     if ($account_id) {
         $console->writeOut("%s\n", pht('Found LDAP Account: %s', $account_id));
     } else {
         $console->writeOut("%s\n", pht('Unable to find LDAP account!'));
     }
     return 0;
 }
 public function handleMessage(PhutilConsoleMessage $message)
 {
     $data = $message->getData();
     $type = $message->getType();
     switch ($type) {
         case PhutilConsoleMessage::TYPE_CONFIRM:
             $ok = phutil_console_confirm($data['prompt'], !$data['default']);
             return $this->buildMessage(PhutilConsoleMessage::TYPE_INPUT, $ok);
         case PhutilConsoleMessage::TYPE_PROMPT:
             $response = phutil_console_prompt($data['prompt'], idx($data, 'history'));
             return $this->buildMessage(PhutilConsoleMessage::TYPE_INPUT, $response);
         case PhutilConsoleMessage::TYPE_OUT:
             $this->writeText(STDOUT, $data);
             return null;
         case PhutilConsoleMessage::TYPE_ERR:
             $this->writeText(STDERR, $data);
             return null;
         case PhutilConsoleMessage::TYPE_LOG:
             if ($this->enableLog) {
                 $this->writeText(STDERR, $data);
             }
             return null;
         case PhutilConsoleMessage::TYPE_ENABLED:
             switch ($data['which']) {
                 case PhutilConsoleMessage::TYPE_LOG:
                     $enabled = $this->enableLog;
                     break;
                 default:
                     $enabled = true;
                     break;
             }
             return $this->buildMessage(PhutilConsoleMessage::TYPE_IS_ENABLED, $enabled);
         case PhutilConsoleMessage::TYPE_TTY:
         case PhutilConsoleMessage::TYPE_COLS:
             switch ($data['which']) {
                 case PhutilConsoleMessage::TYPE_OUT:
                     $which = STDOUT;
                     break;
                 case PhutilConsoleMessage::TYPE_ERR:
                     $which = STDERR;
                     break;
             }
             switch ($type) {
                 case PhutilConsoleMessage::TYPE_TTY:
                     if (function_exists('posix_isatty')) {
                         $is_a_tty = posix_isatty($which);
                     } else {
                         $is_a_tty = null;
                     }
                     return $this->buildMessage(PhutilConsoleMessage::TYPE_IS_TTY, $is_a_tty);
                 case PhutilConsoleMessage::TYPE_COLS:
                     // TODO: This is an approximation which might not be perfectly
                     // accurate.
                     $width = phutil_console_get_terminal_width();
                     return $this->buildMessage(PhutilConsoleMessage::TYPE_COL_WIDTH, $width);
             }
             break;
         default:
             if ($this->handler) {
                 return call_user_func($this->handler, $message);
             } else {
                 throw new Exception(pht("Received unknown console message of type '%s'.", $type));
             }
     }
 }
 public function run()
 {
     $console = PhutilConsole::getConsole();
     $uri = $this->determineConduitURI();
     $this->setConduitURI($uri);
     $configuration_manager = $this->getConfigurationManager();
     $config = $configuration_manager->readUserConfigurationFile();
     $this->writeInfo(pht('CONNECT'), pht('Connecting to "%s"...', $uri));
     $conduit = $this->establishConduit()->getConduit();
     try {
         $conduit->callMethodSynchronous('conduit.ping', array());
     } catch (Exception $ex) {
         throw new ArcanistUsageException(pht('Failed to connect to server (%s): %s', $uri, $ex->getMessage()));
     }
     $token_uri = new PhutilURI($uri);
     $token_uri->setPath('/conduit/token/');
     // Check if this server supports the more modern token-based login.
     $is_token_auth = false;
     try {
         $capabilities = $conduit->callMethodSynchronous('conduit.getcapabilities', array());
         $auth = idx($capabilities, 'authentication', array());
         if (in_array('token', $auth)) {
             $token_uri->setPath('/conduit/login/');
             $is_token_auth = true;
         }
     } catch (Exception $ex) {
         // Ignore.
     }
     echo phutil_console_format("**%s**\n", pht('LOGIN TO PHABRICATOR'));
     echo phutil_console_format("%s\n\n%s\n\n%s", pht('Open this page in your browser and login to ' . 'Phabricator if necessary:'), $token_uri, pht('Then paste the API Token on that page below.'));
     do {
         $token = phutil_console_prompt(pht('Paste API Token from that page:'));
         $token = trim($token);
         if (strlen($token)) {
             break;
         }
     } while (true);
     if ($is_token_auth) {
         if (strlen($token) != 32) {
             throw new ArcanistUsageException(pht('The token "%s" is not formatted correctly. API tokens should ' . 'be 32 characters long. Make sure you visited the correct URI ' . 'and copy/pasted the token correctly.', $token));
         }
         if (strncmp($token, 'cli-', 4) !== 0) {
             throw new ArcanistUsageException(pht('The token "%s" is not formatted correctly. Valid API tokens ' . 'should begin "cli-" and be 32 characters long. Make sure you ' . 'visited the correct URI and copy/pasted the token correctly.', $token));
         }
         $conduit->setConduitToken($token);
         try {
             $conduit->callMethodSynchronous('user.whoami', array());
         } catch (Exception $ex) {
             throw new ArcanistUsageException(pht('The token "%s" is not a valid API Token. The server returned ' . 'this response when trying to use it as a token: %s', $token, $ex->getMessage()));
         }
         $config['hosts'][$uri] = array('token' => $token);
     } else {
         echo "\n";
         echo pht('Downloading authentication certificate...') . "\n";
         $info = $conduit->callMethodSynchronous('conduit.getcertificate', array('token' => $token, 'host' => $uri));
         $user = $info['username'];
         echo pht("Installing certificate for '%s'...", $user) . "\n";
         $config['hosts'][$uri] = array('user' => $user, 'cert' => $info['certificate']);
     }
     echo pht('Writing %s...', '~/.arcrc') . "\n";
     $configuration_manager->writeUserConfigurationFile($config);
     if ($is_token_auth) {
         echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('SUCCESS!'), pht('API Token installed.'));
     } else {
         echo phutil_console_format("<bg:green>** %s **</bg> %s\n", pht('SUCCESS!'), pht('Certificate installed.'));
     }
     return 0;
 }
 /**
  * Handle resolving "arc:*" rules.
  */
 private function resolveArcRule($rule, $name, $source)
 {
     switch ($name) {
         case 'verbose':
             $this->verbose = true;
             $this->log("Enabled verbose mode.");
             break;
         case 'prompt':
             $reason = "it is what you typed when prompted.";
             $this->api->setBaseCommitExplanation($reason);
             return phutil_console_prompt('Against which commit?');
         case 'local':
         case 'global':
         case 'project':
         case 'args':
         case 'system':
             // Push the other source on top of the list.
             array_unshift($this->try, $name);
             $this->log("Switching to source '{$name}'.");
             return false;
         case 'yield':
             // Cycle this source to the end of the list.
             $this->try[] = array_shift($this->try);
             $this->log("Yielding processing of rules from '{$source}'.");
             return false;
         case 'halt':
             // Dump the whole stack.
             $this->try = array();
             $this->log("Halting all rule processing.");
             return false;
         case 'skip':
             return null;
         case 'empty':
         case 'upstream':
         case 'outgoing':
         case 'bookmark':
         case 'amended':
         case 'this':
             return $this->api->resolveBaseCommitRule($rule, $source);
         default:
             $matches = null;
             if (preg_match('/^exec\\((.*)\\)$/', $name, $matches)) {
                 $root = $this->api->getWorkingCopyIdentity()->getProjectRoot();
                 $future = new ExecFuture('%C', $matches[1]);
                 $future->setCWD($root);
                 list($err, $stdout) = $future->resolve();
                 if (!$err) {
                     return trim($stdout);
                 } else {
                     return null;
                 }
             } else {
                 if (preg_match('/^nodiff\\((.*)\\)$/', $name, $matches)) {
                     return $this->api->resolveBaseCommitRule($rule, $source);
                 }
             }
             throw new ArcanistUsageException("Base commit rule '{$rule}' (from source '{$source}') " . "is not a recognized rule.");
     }
 }
Beispiel #8
0
    do {
        $email = phutil_console_prompt(pht('Enter user email address:'));
        $duplicate = id(new PhabricatorUserEmail())->loadOneWhere('address = %s', $email);
        if ($duplicate) {
            echo pht("ERROR: There is already a user with that email address. " . "Each user must have a unique email address.\n");
        } else {
            break;
        }
    } while (true);
    $create_email = $email;
}
$changed_pass = false;
// This disables local echo, so the user's password is not shown as they type
// it.
phutil_passthru('stty -echo');
$password = phutil_console_prompt(pht('Enter a password for this user [blank to leave unchanged]:'));
phutil_passthru('stty echo');
if (strlen($password)) {
    $changed_pass = $password;
}
$is_system_agent = $user->getIsSystemAgent();
$set_system_agent = phutil_console_confirm(pht('Is this user a bot?'), $default_no = !$is_system_agent);
$verify_email = null;
$set_verified = false;
// Allow administrators to verify primary email addresses at this time in edit
// scenarios. (Create will work just fine from here as we auto-verify email
// on create.)
if (!$is_new) {
    $verify_email = $user->loadPrimaryEmail();
    if (!$verify_email->getIsVerified()) {
        $set_verified = phutil_console_confirm(pht('Should the primary email address be verified?'), $default_no = true);
 private function getErrorExcuse($prompt, $history)
 {
     if ($this->getArgument('excuse')) {
         $prompt .= " Ignore them?";
         if (!phutil_console_confirm($prompt)) {
             throw new ArcanistUserAbortException();
         }
         return $this->getArgument('excuse');
     }
     $history = $this->getRepositoryAPI()->getScratchFilePath($history);
     $prompt .= " Provide explanation to continue or press Enter to abort.";
     echo "\n\n";
     echo phutil_console_wrap($prompt);
     $return = phutil_console_prompt("Explanation:", $history);
     if ($return == '') {
         throw new ArcanistUserAbortException();
     }
     return $return;
 }
 private function liberateCreateLibrary($path)
 {
     $init_path = $path . '/__phutil_library_init__.php';
     if (Filesystem::pathExists($init_path)) {
         return;
     }
     echo pht("Creating new libphutil library in '%s'.", $path) . "\n";
     do {
         $name = $this->getArgument('library-name');
         if ($name === null) {
             echo pht('Choose a name for the new library.') . "\n";
             $name = phutil_console_prompt(pht('What do you want to name this library?'));
         } else {
             echo pht('Using library name %s.', $name) . "\n";
         }
         if (preg_match('/^[a-z-]+$/', $name)) {
             break;
         } else {
             echo phutil_console_format("%s\n", pht('Library name should contain only lowercase letters and hyphens.'));
         }
     } while (true);
     $template = "<?php\n\n" . "phutil_register_library('{$name}', __FILE__);\n";
     echo pht("Writing '%s' to '%s'...\n", '__phutil_library_init__.php', $path);
     Filesystem::writeFile($init_path, $template);
     $this->liberateVersion2($path);
 }
Beispiel #11
0
#!/usr/bin/env php
<?php 
require_once dirname(__FILE__) . '/../__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('test console prompting'));
$args->setSynopsis(<<<EOHELP
**prompt.php** __options__
    Test console prompting.
EOHELP
);
$args->parseStandardArguments();
$args->parse(array(array('name' => 'history', 'param' => 'file', 'default' => '', 'help' => pht('Use specified history __file__.')), array('name' => 'prompt', 'param' => 'text', 'default' => pht('Enter some text:'), 'help' => pht('Change the prompt text to __text__.'))));
$result = phutil_console_prompt($args->getArg('prompt'), $args->getArg('history'));
$console = PhutilConsole::getConsole();
$console->writeOut("%s\n", pht('Input is: %s', $result));
 public final function requireCleanWorkingCopy()
 {
     $api = $this->getRepositoryAPI();
     $must_commit = array();
     $working_copy_desc = phutil_console_format("  Working copy: __%s__\n\n", $api->getPath());
     $untracked = $api->getUntrackedChanges();
     if ($this->shouldRequireCleanUntrackedFiles()) {
         if (!empty($untracked)) {
             echo "You have untracked files in this working copy.\n\n" . $working_copy_desc . "  Untracked files in working copy:\n" . "    " . implode("\n    ", $untracked) . "\n\n";
             if ($api instanceof ArcanistGitAPI) {
                 echo phutil_console_wrap("Since you don't have '.gitignore' rules for these files and have " . "not listed them in '.git/info/exclude', you may have forgotten " . "to 'git add' them to your commit.\n");
             } else {
                 if ($api instanceof ArcanistSubversionAPI) {
                     echo phutil_console_wrap("Since you don't have 'svn:ignore' rules for these files, you may " . "have forgotten to 'svn add' them.\n");
                 } else {
                     if ($api instanceof ArcanistMercurialAPI) {
                         echo phutil_console_wrap("Since you don't have '.hgignore' rules for these files, you " . "may have forgotten to 'hg add' them to your commit.\n");
                     }
                 }
             }
             if ($this->askForAdd($untracked)) {
                 $api->addToCommit($untracked);
                 $must_commit += array_flip($untracked);
             } else {
                 if ($this->commitMode == self::COMMIT_DISABLE) {
                     $prompt = $this->getAskForAddPrompt($untracked);
                     if (phutil_console_confirm($prompt)) {
                         throw new ArcanistUsageException(pht("Add these files and then run 'arc %s' again.", $this->getWorkflowName()));
                     }
                 }
             }
         }
     }
     // NOTE: this is a subversion-only concept.
     $incomplete = $api->getIncompleteChanges();
     if ($incomplete) {
         throw new ArcanistUsageException("You have incompletely checked out directories in this working copy. " . "Fix them before proceeding.\n\n" . $working_copy_desc . "  Incomplete directories in working copy:\n" . "    " . implode("\n    ", $incomplete) . "\n\n" . "You can fix these paths by running 'svn update' on them.");
     }
     $conflicts = $api->getMergeConflicts();
     if ($conflicts) {
         throw new ArcanistUsageException("You have merge conflicts in this working copy. Resolve merge " . "conflicts before proceeding.\n\n" . $working_copy_desc . "  Conflicts in working copy:\n" . "    " . implode("\n    ", $conflicts) . "\n");
     }
     $missing = $api->getMissingChanges();
     if ($missing) {
         throw new ArcanistUsageException(pht("You have missing files in this working copy. Revert or formally " . "remove them (with `svn rm`) before proceeding.\n\n" . "%s" . "  Missing files in working copy:\n%s\n", $working_copy_desc, "    " . implode("\n    ", $missing)));
     }
     $unstaged = $api->getUnstagedChanges();
     if ($unstaged) {
         echo "You have unstaged changes in this working copy.\n\n" . $working_copy_desc . "  Unstaged changes in working copy:\n" . "    " . implode("\n    ", $unstaged) . "\n";
         if ($this->askForAdd($unstaged)) {
             $api->addToCommit($unstaged);
             $must_commit += array_flip($unstaged);
         } else {
             $permit_autostash = $this->getConfigFromAnySource('arc.autostash', false);
             if ($permit_autostash && $api->canStashChanges()) {
                 echo "Stashing uncommitted changes. (You can restore them with " . "`git stash pop`.)\n";
                 $api->stashChanges();
                 $this->stashed = true;
             } else {
                 throw new ArcanistUsageException('Stage and commit (or revert) them before proceeding.');
             }
         }
     }
     $uncommitted = $api->getUncommittedChanges();
     foreach ($uncommitted as $key => $path) {
         if (array_key_exists($path, $must_commit)) {
             unset($uncommitted[$key]);
         }
     }
     if ($uncommitted) {
         echo "You have uncommitted changes in this working copy.\n\n" . $working_copy_desc . "  Uncommitted changes in working copy:\n" . "    " . implode("\n    ", $uncommitted) . "\n";
         if ($this->askForAdd($uncommitted)) {
             $must_commit += array_flip($uncommitted);
         } else {
             throw new ArcanistUncommittedChangesException('Commit (or revert) them before proceeding.');
         }
     }
     if ($must_commit) {
         if ($this->getShouldAmend()) {
             $commit = head($api->getLocalCommitInformation());
             $api->amendCommit($commit['message']);
         } else {
             if ($api->supportsLocalCommits()) {
                 $commit_message = phutil_console_prompt('Enter commit message:');
                 if ($commit_message == '') {
                     $commit_message = self::AUTO_COMMIT_TITLE;
                 }
                 $api->doCommit($commit_message);
             }
         }
     }
 }
 private function liberateCreateLibrary($path)
 {
     $init_path = $path . '/__phutil_library_init__.php';
     if (Filesystem::pathExists($init_path)) {
         return;
     }
     echo "Creating new libphutil library in '{$path}'.\n";
     echo "Choose a name for the new library.\n";
     do {
         $name = phutil_console_prompt('What do you want to name this library?');
         if (preg_match('/^[a-z]+$/', $name)) {
             break;
         } else {
             echo "Library name should contain only lowercase letters.\n";
         }
     } while (true);
     $template = "<?php\n\n" . "phutil_register_library('{$name}', __FILE__);\n";
     echo "Writing '__phutil_library_init__.php' to '{$init_path}'...\n";
     Filesystem::writeFile($init_path, $template);
 }
Beispiel #14
0
 public function getRelativeCommit()
 {
     if ($this->relativeCommit === null) {
         // 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;
             }
             $this->relativeCommit = self::GIT_MAGIC_ROOT_COMMIT;
             return $this->relativeCommit;
         }
         $do_write = false;
         $default_relative = null;
         list($err, $upstream) = $this->execManualLocal("rev-parse --abbrev-ref --symbolic-full-name '@{upstream}'");
         if (!$err) {
             $default_relative = trim($upstream);
         }
         if (!$default_relative) {
             $default_relative = $this->readScratchFile('default-relative-commit');
             $default_relative = trim($default_relative);
         }
         if (!$default_relative) {
             $working_copy = $this->getWorkingCopyIdentity();
             if ($working_copy) {
                 $default_relative = $working_copy->getConfig('git.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 '{$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);
         }
         list($merge_base) = $this->execxLocal('merge-base %s HEAD', $default_relative);
         $this->relativeCommit = trim($merge_base);
     }
     return $this->relativeCommit;
 }
Beispiel #15
0
 public function handleServerMessage(PhutilConsoleMessage $message)
 {
     $data = $message->getData();
     if ($this->getArgument('excuse')) {
         try {
             phutil_console_require_tty();
         } catch (PhutilConsoleStdinNotInteractiveException $ex) {
             $this->excuses[$data['type']] = $this->getArgument('excuse');
             return null;
         }
     }
     $response = '';
     if (isset($data['prompt'])) {
         $response = phutil_console_prompt($data['prompt'], idx($data, 'history'));
     } else {
         if (phutil_console_confirm($data['confirm'])) {
             $response = $this->getArgument('excuse');
         }
     }
     if ($response == '') {
         throw new ArcanistUserAbortException();
     }
     $this->excuses[$data['type']] = $response;
     return null;
 }
Beispiel #16
0
 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);
 }
 /**
  * Detect alternate branches and prompt the user for how to handle
  * them. An alternate branch is a branch that forks from the landing
  * branch prior to the landing branch tip.
  *
  * In a situation like this:
  *   -a--------b  master
  *     \
  *      w--x  landingbranch
  *       \  \-- g subbranch
  *        \--y  altbranch1
  *         \--z  altbranch2
  *
  * y and z are alternate branches and will get deleted by the squash,
  * so we need to detect them and ask the user what they want to do.
  *
  * @param string The revision id of the landing branch's root commit.
  * @param string The revset specifying all the commits in the landing branch.
  * @return void
  */
 private function handleAlternateBranches($branch_root, $branch_range)
 {
     $repository_api = $this->getRepositoryAPI();
     // Using the tree in the doccomment, the revset below resolves as follows:
     // 1. roots(descendants(w) - descendants(x) - (w::x))
     // 2. roots({x,g,y,z} - {g} - {w,x})
     // 3. roots({y,z})
     // 4. {y,z}
     $alt_branch_revset = hgsprintf('roots(descendants(%s)-descendants(%s)-%R)', $branch_root, $this->branch, $branch_range);
     list($alt_branches) = $repository_api->execxLocal("log --template '{node}\n' -r %s", $alt_branch_revset);
     $alt_branches = phutil_split_lines($alt_branches, false);
     $alt_branches = array_filter($alt_branches);
     $alt_count = count($alt_branches);
     if ($alt_count > 0) {
         $input = phutil_console_prompt(ucfirst($this->branchType) . " '{$this->branch}' has {$alt_count} " . "{$this->branchType}(s) forking off of it that would be deleted " . "during a squash. Would you like to keep a non-squashed copy, rebase " . "them on top of '{$this->branch}', or abort and deal with them " . "yourself? (k)eep, (r)ebase, (a)bort:");
         if ($input == 'k' || $input == 'keep') {
             $this->keepBranch = true;
         } else {
             if ($input == 'r' || $input == 'rebase') {
                 foreach ($alt_branches as $alt_branch) {
                     $repository_api->execxLocal('rebase --keep --keepbranches -d %s -s %s', $this->branch, $alt_branch);
                 }
             } else {
                 if ($input == 'a' || $input == 'abort') {
                     $branch_string = implode("\n", $alt_branches);
                     echo "\nRemove the {$this->branchType}s starting at these revisions " . "and run arc land again:\n{$branch_string}\n\n";
                     throw new ArcanistUserAbortException();
                 } else {
                     throw new ArcanistUsageException("Invalid choice. Aborting arc land.");
                 }
             }
         }
     }
 }