public function testRemover() { $test = <<<EOTEXT Here is a list: # Stuff # More Stuff The end. # Instructional comments. # Appear here. # At the bottom. EOTEXT; $expect = <<<EOTEXT Here is a list: # Stuff # More Stuff The end. EOTEXT; $this->assertEqual($expect, ArcanistCommentRemover::removeComments($test)); }
private function getCommitMessage(ArcanistBundle $bundle) { $revision_id = $bundle->getRevisionID(); $commit_message = null; $prompt_message = null; // if we have a revision id the commit message is in differential // TODO: See T848 for the authenticated stuff. if ($revision_id && $this->isConduitAuthenticated()) { $conduit = $this->getConduit(); $commit_message = $conduit->callMethodSynchronous('differential.getcommitmessage', array('revision_id' => $revision_id)); $prompt_message = pht(' Note arcanist failed to load the commit message ' . 'from differential for revision %s.', "D{$revision_id}"); } // no revision id or failed to fetch commit message so get it from the // user on the command line if (!$commit_message) { $template = sprintf("\n\n# %s%s\n", pht('Enter a commit message for this patch. If you just want to apply ' . 'the patch to the working copy without committing, re-run arc patch ' . 'with the %s flag.', '--nocommit'), $prompt_message); $commit_message = $this->newInteractiveEditor($template)->setName('arcanist-patch-commit-message')->editInteractively(); $commit_message = ArcanistCommentRemover::removeComments($commit_message); if (!strlen(trim($commit_message))) { throw new ArcanistUserAbortException(); } } return $commit_message; }
/** * @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; }
/** * @task message */ private function getUpdateMessage(array $fields) { $comments = $this->getArgument('message'); if (strlen($comments)) { return $comments; } if ($this->getArgument('raw')) { throw new ArcanistUsageException("When using '--raw' to update a revision, specify an update message " . "with '--message'. (Normally, we'd launch an editor to ask you for a " . "message, but can not do that because stdin is the diff source.)"); } // When updating a revision using git without specifying '--message', try // to prefill with the message in HEAD if it isn't a template message. The // idea is that if you do: // // $ git commit -a -m 'fix some junk' // $ arc diff // // ...you shouldn't have to retype the update message. Similar things apply // to Mercurial. $comments = $this->getDefaultUpdateMessage(); $template = rtrim($comments) . "\n\n" . "# Updating D{$fields['revisionID']}: {$fields['title']}\n" . "#\n" . "# Enter a brief description of the changes included in this update.\n" . "# The first line is used as subject, next lines as comment.\n" . "#\n" . "# If you intended to create a new revision, use:\n" . "# \$ arc diff --create\n" . "\n"; $comments = $this->newInteractiveEditor($template)->setName('differential-update-comments')->editInteractively(); $comments = ArcanistCommentRemover::removeComments($comments); if (!strlen(trim($comments))) { throw new ArcanistUserAbortException(); } return $comments; }
public final function requireCleanWorkingCopy() { $api = $this->getRepositoryAPI(); $must_commit = array(); $working_copy_desc = phutil_console_format(" %s: __%s__\n\n", pht('Working copy'), $api->getPath()); // NOTE: this is a subversion-only concept. $incomplete = $api->getIncompleteChanges(); if ($incomplete) { throw new ArcanistUsageException(sprintf("%s\n\n%s %s\n %s\n\n%s", pht("You have incompletely checked out directories in this working " . "copy. Fix them before proceeding.'"), $working_copy_desc, pht('Incomplete directories in working copy:'), implode("\n ", $incomplete), pht("You can fix these paths by running '%s' on them.", 'svn update'))); } $conflicts = $api->getMergeConflicts(); if ($conflicts) { throw new ArcanistUsageException(sprintf("%s\n\n%s %s\n %s", pht('You have merge conflicts in this working copy. Resolve merge ' . 'conflicts before proceeding.'), $working_copy_desc, pht('Conflicts in working copy:'), implode("\n ", $conflicts))); } $missing = $api->getMissingChanges(); if ($missing) { throw new ArcanistUsageException(sprintf("%s\n\n%s %s\n %s\n", pht('You have missing files in this working copy. Revert or formally ' . 'remove them (with `%s`) before proceeding.', 'svn rm'), $working_copy_desc, pht('Missing files in working copy:'), implode("\n ", $missing))); } $uncommitted = $api->getUncommittedChanges(); $unstaged = $api->getUnstagedChanges(); // We only want files which are purely uncommitted. $uncommitted = array_diff($uncommitted, $unstaged); $untracked = $api->getUntrackedChanges(); if (!$this->shouldRequireCleanUntrackedFiles()) { $untracked = array(); } if ($untracked) { echo sprintf("%s\n\n%s", pht('You have untracked files in this working copy.'), $working_copy_desc); if ($api instanceof ArcanistGitAPI) { $hint = pht('(To ignore these %s change(s), add them to "%s".)', new PhutilNumber(count($untracked)), '.git/info/exclude'); } else { if ($api instanceof ArcanistSubversionAPI) { $hint = pht('(To ignore these %s change(s), add them to "%s".)', new PhutilNumber(count($untracked)), 'svn:ignore'); } else { if ($api instanceof ArcanistMercurialAPI) { $hint = pht('(To ignore these %s change(s), add them to "%s".)', new PhutilNumber(count($untracked)), '.hgignore'); } } } $untracked_list = " " . implode("\n ", $untracked); echo sprintf(" %s\n %s\n%s", pht('Untracked changes in working copy:'), $hint, $untracked_list); $prompt = pht('Ignore these %s untracked file(s) and continue?', new PhutilNumber(count($untracked))); if (!phutil_console_confirm($prompt)) { throw new ArcanistUserAbortException(); } } $should_commit = false; if ($unstaged || $uncommitted) { // NOTE: We're running this because it builds a cache and can take a // perceptible amount of time to arrive at an answer, but we don't want // to pause in the middle of printing the output below. $this->getShouldAmend(); echo sprintf("%s\n\n%s", pht('You have uncommitted changes in this working copy.'), $working_copy_desc); $lists = array(); if ($unstaged) { $unstaged_list = " " . implode("\n ", $unstaged); $lists[] = sprintf(" %s\n%s", pht('Unstaged changes in working copy:'), $unstaged_list); } if ($uncommitted) { $uncommitted_list = " " . implode("\n ", $uncommitted); $lists[] = sprintf("%s\n%s", pht('Uncommitted changes in working copy:'), $uncommitted_list); } echo implode("\n\n", $lists) . "\n"; $all_uncommitted = array_merge($unstaged, $uncommitted); if ($this->askForAdd($all_uncommitted)) { if ($unstaged) { $api->addToCommit($unstaged); } $should_commit = true; } else { $permit_autostash = $this->getConfigFromAnySource('arc.autostash', false); if ($permit_autostash && $api->canStashChanges()) { echo pht('Stashing uncommitted changes. (You can restore them with `%s`).', 'git stash pop') . "\n"; $api->stashChanges(); $this->stashed = true; } else { throw new ArcanistUsageException(pht('You can not continue with uncommitted changes. ' . 'Commit or discard them before proceeding.')); } } } if ($should_commit) { if ($this->getShouldAmend()) { $commit = head($api->getLocalCommitInformation()); $api->amendCommit($commit['message']); } else { if ($api->supportsLocalCommits()) { $template = sprintf("\n\n# %s\n#\n# %s\n#\n", pht('Enter a commit message.'), pht('Changes:')); $paths = array_merge($uncommitted, $unstaged); $paths = array_unique($paths); sort($paths); foreach ($paths as $path) { $template .= "# " . $path . "\n"; } $commit_message = $this->newInteractiveEditor($template)->setName(pht('commit-message'))->editInteractively(); if ($commit_message === $template) { throw new ArcanistUsageException(pht('You must provide a commit message.')); } $commit_message = ArcanistCommentRemover::removeComments($commit_message); if (!strlen($commit_message)) { throw new ArcanistUsageException(pht('You must provide a nonempty commit message.')); } $api->doCommit($commit_message); } } } }
/** * Retrieves default template from differential and pre-fills info. */ private function buildCommitMessage($commit_hash) { $conduit = $this->getConduit(); $repository_api = $this->getRepositoryAPI(); $summary = $repository_api->getBackoutMessage($commit_hash); $fields = array('summary' => $summary, 'testPlan' => 'revert-hammer'); $template = $conduit->callMethodSynchronous('differential.getcommitmessage', array('revision_id' => null, 'edit' => 'create', 'fields' => $fields)); $template = $this->newInteractiveEditor($template)->setName('new-commit')->editInteractively(); $template = ArcanistCommentRemover::removeComments($template); return $template; }