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;
 }
Esempio n. 3
0
 /**
  * @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;
 }
Esempio n. 4
0
 /**
  * @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;
 }
Esempio n. 5
0
 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;
 }