Ejemplo n.º 1
0
 public function run()
 {
     $source = $this->getSource();
     switch ($source) {
         case self::SOURCE_LOCAL:
             $repository_api = $this->getRepositoryAPI();
             $parser = new ArcanistDiffParser();
             if ($repository_api instanceof ArcanistGitAPI) {
                 $repository_api->parseRelativeLocalCommit($this->getArgument('paths'));
                 $diff = $repository_api->getFullGitDiff();
                 $changes = $parser->parseDiff($diff);
             } else {
                 // TODO: paths support
                 $paths = $repository_api->getWorkingCopyStatus();
                 $changes = $parser->parseSubversionDiff($repository_api, $paths);
             }
             $bundle = ArcanistBundle::newFromChanges($changes);
             $bundle->setProjectID($this->getWorkingCopy()->getProjectID());
             $bundle->setBaseRevision($repository_api->getSourceControlBaseRevision());
             // note we can't get a revision ID for SOURCE_LOCAL
             break;
         case self::SOURCE_REVISION:
             $bundle = $this->loadRevisionBundleFromConduit($this->getConduit(), $this->getSourceID());
             break;
         case self::SOURCE_DIFF:
             $bundle = $this->loadDiffBundleFromConduit($this->getConduit(), $this->getSourceID());
             break;
     }
     $try_encoding = nonempty($this->getArgument('encoding'), null);
     if (!$try_encoding) {
         try {
             $try_encoding = $this->getRepositoryEncoding();
         } catch (ConduitClientException $e) {
             $try_encoding = null;
         }
     }
     if ($try_encoding) {
         $bundle->setEncoding($try_encoding);
     }
     $format = $this->getFormat();
     switch ($format) {
         case self::FORMAT_GIT:
             echo $bundle->toGitPatch();
             break;
         case self::FORMAT_UNIFIED:
             echo $bundle->toUnifiedDiff();
             break;
         case self::FORMAT_BUNDLE:
             $path = $this->getArgument('arcbundle');
             echo "Writing bundle to '{$path}'...\n";
             $bundle->writeToDisk($path);
             echo "done.\n";
             break;
     }
     return 0;
 }
Ejemplo n.º 2
0
 public function run()
 {
     $source = $this->getSource();
     switch ($source) {
         case self::SOURCE_LOCAL:
             $repository_api = $this->getRepositoryAPI();
             $parser = new ArcanistDiffParser();
             $parser->setRepositoryAPI($repository_api);
             if ($repository_api instanceof ArcanistGitAPI) {
                 $this->parseBaseCommitArgument($this->getArgument('paths'));
                 $diff = $repository_api->getFullGitDiff($repository_api->getBaseCommit(), $repository_api->getHeadCommit());
                 $changes = $parser->parseDiff($diff);
                 $authors = $this->getConduit()->callMethodSynchronous('user.query', array('phids' => array($this->getUserPHID())));
                 $author_dict = reset($authors);
                 list($email) = $repository_api->execxLocal('config user.email');
                 $author = sprintf('%s <%s>', $author_dict['realName'], $email);
             } else {
                 if ($repository_api instanceof ArcanistMercurialAPI) {
                     $this->parseBaseCommitArgument($this->getArgument('paths'));
                     $diff = $repository_api->getFullMercurialDiff();
                     $changes = $parser->parseDiff($diff);
                     $authors = $this->getConduit()->callMethodSynchronous('user.query', array('phids' => array($this->getUserPHID())));
                     list($author) = $repository_api->execxLocal('showconfig ui.username');
                 } else {
                     // TODO: paths support
                     $paths = $repository_api->getWorkingCopyStatus();
                     $changes = $parser->parseSubversionDiff($repository_api, $paths);
                     $author = $this->getUserName();
                 }
             }
             $bundle = ArcanistBundle::newFromChanges($changes);
             $bundle->setProjectID($this->getWorkingCopy()->getProjectID());
             $bundle->setBaseRevision($repository_api->getSourceControlBaseRevision());
             // NOTE: we can't get a revision ID for SOURCE_LOCAL
             $parser = new PhutilEmailAddress($author);
             $bundle->setAuthorName($parser->getDisplayName());
             $bundle->setAuthorEmail($parser->getAddress());
             break;
         case self::SOURCE_REVISION:
             $bundle = $this->loadRevisionBundleFromConduit($this->getConduit(), $this->getSourceID());
             break;
         case self::SOURCE_DIFF:
             $bundle = $this->loadDiffBundleFromConduit($this->getConduit(), $this->getSourceID());
             break;
     }
     $try_encoding = nonempty($this->getArgument('encoding'), null);
     if (!$try_encoding) {
         try {
             $project_info = $this->getConduit()->callMethodSynchronous('arcanist.projectinfo', array('name' => $bundle->getProjectID()));
             $try_encoding = $project_info['encoding'];
         } catch (ConduitClientException $e) {
             $try_encoding = null;
         }
     }
     if ($try_encoding) {
         $bundle->setEncoding($try_encoding);
     }
     $format = $this->getFormat();
     switch ($format) {
         case self::FORMAT_GIT:
             echo $bundle->toGitPatch();
             break;
         case self::FORMAT_UNIFIED:
             echo $bundle->toUnifiedDiff();
             break;
         case self::FORMAT_BUNDLE:
             $path = $this->getArgument('arcbundle');
             echo "Writing bundle to '{$path}'...\n";
             $bundle->writeToDisk($path);
             echo "done.\n";
             break;
     }
     return 0;
 }
Ejemplo n.º 3
0
 protected function generateChanges()
 {
     $parser = new ArcanistDiffParser();
     $is_raw = $this->isRawDiffSource();
     if ($is_raw) {
         if ($this->getArgument('raw')) {
             file_put_contents('php://stderr', "Reading diff from stdin...\n");
             $raw_diff = file_get_contents('php://stdin');
         } else {
             if ($this->getArgument('raw-command')) {
                 list($raw_diff) = execx($this->getArgument('raw-command'));
             } else {
                 throw new Exception("Unknown raw diff source.");
             }
         }
         $changes = $parser->parseDiff($raw_diff);
         foreach ($changes as $key => $change) {
             // Remove "message" changes, e.g. from "git show".
             if ($change->getType() == ArcanistDiffChangeType::TYPE_MESSAGE) {
                 unset($changes[$key]);
             }
         }
         return $changes;
     }
     $repository_api = $this->getRepositoryAPI();
     if ($repository_api instanceof ArcanistSubversionAPI) {
         $paths = $this->generateAffectedPaths();
         $this->primeSubversionWorkingCopyData($paths);
         // Check to make sure the user is diffing from a consistent base revision.
         // This is mostly just an abuse sanity check because it's silly to do this
         // and makes the code more difficult to effectively review, but it also
         // affects patches and makes them nonportable.
         $bases = $repository_api->getSVNBaseRevisions();
         // Remove all files with baserev "0"; these files are new.
         foreach ($bases as $path => $baserev) {
             if ($bases[$path] <= 0) {
                 unset($bases[$path]);
             }
         }
         if ($bases) {
             $rev = reset($bases);
             $revlist = array();
             foreach ($bases as $path => $baserev) {
                 $revlist[] = "    Revision {$baserev}, {$path}";
             }
             $revlist = implode("\n", $revlist);
             foreach ($bases as $path => $baserev) {
                 if ($baserev !== $rev) {
                     throw new ArcanistUsageException("Base revisions of changed paths are mismatched. Update all " . "paths to the same base revision before creating a diff: " . "\n\n" . $revlist);
                 }
             }
             // If you have a change which affects several files, all of which are
             // at a consistent base revision, treat that revision as the effective
             // base revision. The use case here is that you made a change to some
             // file, which updates it to HEAD, but want to be able to change it
             // again without updating the entire working copy. This is a little
             // sketchy but it arises in Facebook Ops workflows with config files and
             // doesn't have any real material tradeoffs (e.g., these patches are
             // perfectly applyable).
             $repository_api->overrideSVNBaseRevisionNumber($rev);
         }
         $changes = $parser->parseSubversionDiff($repository_api, $paths);
     } else {
         if ($repository_api instanceof ArcanistGitAPI) {
             $diff = $repository_api->getFullGitDiff();
             if (!strlen($diff)) {
                 throw new ArcanistUsageException("No changes found. (Did you specify the wrong commit range?)");
             }
             $changes = $parser->parseDiff($diff);
         } else {
             if ($repository_api instanceof ArcanistMercurialAPI) {
                 $diff = $repository_api->getFullMercurialDiff();
                 if (!strlen($diff)) {
                     throw new ArcanistUsageException("No changes found. (Did you specify the wrong commit range?)");
                 }
                 $changes = $parser->parseDiff($diff);
             } else {
                 throw new Exception("Repository API is not supported.");
             }
         }
     }
     if (count($changes) > 250) {
         $count = number_format(count($changes));
         $message = "This diff has a very large number of changes ({$count}). " . "Differential works best for changes which will receive detailed " . "human review, and not as well for large automated changes or " . "bulk checkins. Continue anyway?";
         if (!phutil_console_confirm($message)) {
             throw new ArcanistUsageException("Aborted generation of gigantic diff.");
         }
     }
     $limit = 1024 * 1024 * 4;
     foreach ($changes as $change) {
         $size = 0;
         foreach ($change->getHunks() as $hunk) {
             $size += strlen($hunk->getCorpus());
         }
         if ($size > $limit) {
             $file_name = $change->getCurrentPath();
             $change_size = number_format($size);
             $byte_warning = "Diff for '{$file_name}' with context is {$change_size} bytes in " . "length. Generally, source changes should not be this large.";
             if (!$this->getArgument('less-context')) {
                 $byte_warning .= " If this file is a huge text file, try using the " . "'--less-context' flag.";
             }
             if ($repository_api instanceof ArcanistSubversionAPI) {
                 throw new ArcanistUsageException("{$byte_warning} If the file is not a text file, mark it as " . "binary with:" . "\n\n" . "  \$ svn propset svn:mime-type application/octet-stream <filename>" . "\n");
             } else {
                 $confirm = "{$byte_warning} If the file is not a text file, you can " . "mark it 'binary'. Mark this file as 'binary' and continue?";
                 if (phutil_console_confirm($confirm)) {
                     $change->convertToBinaryChange();
                 } else {
                     throw new ArcanistUsageException("Aborted generation of gigantic diff.");
                 }
             }
         }
     }
     $try_encoding = nonempty($this->getArgument('encoding'), null);
     $utf8_problems = array();
     foreach ($changes as $change) {
         foreach ($change->getHunks() as $hunk) {
             $corpus = $hunk->getCorpus();
             if (!phutil_is_utf8($corpus)) {
                 // If this corpus is heuristically binary, don't try to convert it.
                 // mb_check_encoding() and mb_convert_encoding() are both very very
                 // liberal about what they're willing to process.
                 $is_binary = ArcanistDiffUtils::isHeuristicBinaryFile($corpus);
                 if (!$is_binary) {
                     if (!$try_encoding) {
                         try {
                             $try_encoding = $this->getRepositoryEncoding();
                         } catch (ConduitClientException $e) {
                             if ($e->getErrorCode() == 'ERR-BAD-ARCANIST-PROJECT') {
                                 echo phutil_console_wrap("Lookup of encoding in arcanist project failed\n" . $e->getMessage());
                             } else {
                                 throw $e;
                             }
                         }
                     }
                     if ($try_encoding) {
                         // NOTE: This feature is HIGHLY EXPERIMENTAL and will cause a lot
                         // of issues. Use it at your own risk.
                         $corpus = mb_convert_encoding($corpus, 'UTF-8', $try_encoding);
                         $name = $change->getCurrentPath();
                         if (phutil_is_utf8($corpus)) {
                             $this->writeStatusMessage("[Experimental] Converted a '{$name}' hunk from " . "'{$try_encoding}' to UTF-8.\n");
                             $hunk->setCorpus($corpus);
                             continue;
                         }
                     }
                 }
                 $utf8_problems[] = $change;
                 break;
             }
         }
     }
     // If there are non-binary files which aren't valid UTF-8, warn the user
     // and treat them as binary changes. See D327 for discussion of why Arcanist
     // has this behavior.
     if ($utf8_problems) {
         $learn_more = "You can learn more about how Phabricator handles character encodings " . "(and how to configure encoding settings and detect and correct " . "encoding problems) by reading 'User Guide: UTF-8 and Character " . "Encoding' in the Phabricator documentation.\n\n";
         if (count($utf8_problems) == 1) {
             $utf8_warning = "This diff includes a file which is not valid UTF-8 (it has invalid " . "byte sequences). You can either stop this workflow and fix it, or " . "continue. If you continue, this file will be marked as binary.\n\n" . $learn_more . "    AFFECTED FILE\n";
             $confirm = "Do you want to mark this file as binary and continue?";
         } else {
             $utf8_warning = "This diff includes files which are not valid UTF-8 (they contain " . "invalid byte sequences). You can either stop this workflow and fix " . "these files, or continue. If you continue, these files will be " . "marked as binary.\n\n" . $learn_more . "    AFFECTED FILES\n";
             $confirm = "Do you want to mark these files as binary and continue?";
         }
         echo phutil_console_format("**Invalid Content Encoding (Non-UTF8)**\n");
         echo phutil_console_wrap($utf8_warning);
         $file_list = mpull($utf8_problems, 'getCurrentPath');
         $file_list = '    ' . implode("\n    ", $file_list);
         echo $file_list;
         if (!phutil_console_confirm($confirm, $default_no = false)) {
             throw new ArcanistUsageException("Aborted workflow to fix UTF-8.");
         } else {
             foreach ($utf8_problems as $change) {
                 $change->convertToBinaryChange();
             }
         }
     }
     foreach ($changes as $change) {
         if ($change->getFileType() != ArcanistDiffChangeType::FILE_BINARY) {
             continue;
         }
         $path = $change->getCurrentPath();
         $name = basename($path);
         $old_file = $repository_api->getOriginalFileData($path);
         $old_dict = $this->uploadFile($old_file, $name, 'old binary');
         if ($old_dict['guid']) {
             $change->setMetadata('old:binary-phid', $old_dict['guid']);
         }
         $change->setMetadata('old:file:size', $old_dict['size']);
         $change->setMetadata('old:file:mime-type', $old_dict['mime']);
         $new_file = $repository_api->getCurrentFileData($path);
         $new_dict = $this->uploadFile($new_file, $name, 'new binary');
         if ($new_dict['guid']) {
             $change->setMetadata('new:binary-phid', $new_dict['guid']);
         }
         $change->setMetadata('new:file:size', $new_dict['size']);
         $change->setMetadata('new:file:mime-type', $new_dict['mime']);
         if (preg_match('@^image/@', $new_dict['mime'])) {
             $change->setFileType(ArcanistDiffChangeType::FILE_IMAGE);
         }
     }
     return $changes;
 }