private function liberateRunVerify($path)
 {
     phutil_load_library($path);
     $paths = $this->liberateGetChangedPaths($path);
     $results = $this->liberateLintModules($path, $paths);
     $renderer = new ArcanistLintRenderer();
     $unresolved = false;
     foreach ($results as $result) {
         foreach ($result->getMessages() as $message) {
             echo $renderer->renderLintResult($result);
             $unresolved = true;
             break;
         }
     }
     return (int) $unresolved;
 }
 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;
     }
     PhutilSymbolLoader::loadClass($lint_engine);
     $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;
     }
     $renderer = new ArcanistLintRenderer();
     $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);
         foreach ($failures as $result) {
             echo $renderer->renderLintResult($result);
         }
         return 1;
     }
     return 0;
 }
示例#3
0
 public function run()
 {
     $working_copy = $this->getWorkingCopy();
     $engine = $this->getArgument('engine');
     if (!$engine) {
         $engine = $working_copy->getConfig('lint_engine');
         if (!$engine) {
             throw new ArcanistNoEngineException("No lint engine configured for this project. Edit .arcconfig to " . "specify a lint engine.");
         }
     }
     $rev = $this->getArgument('rev');
     $paths = $this->getArgument('paths');
     if ($rev && $paths) {
         throw new ArcanistUsageException("Specify either --rev or paths.");
     }
     $should_lint_all = $this->getArgument('lintall');
     if ($paths) {
         // NOTE: When the user specifies paths, we imply --lintall and show all
         // warnings for the paths in question. This is easier to deal with for
         // us and less confusing for users.
         $should_lint_all = true;
     }
     $paths = $this->selectPathsForWorkflow($paths, $rev);
     PhutilSymbolLoader::loadClass($engine);
     if (!is_subclass_of($engine, 'ArcanistLintEngine')) {
         throw new ArcanistUsageException("Configured lint engine '{$engine}' is not a subclass of " . "'ArcanistLintEngine'.");
     }
     $engine = newv($engine, array());
     $engine->setWorkingCopy($working_copy);
     if ($this->getArgument('advice')) {
         $engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ADVICE);
     } else {
         $engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_AUTOFIX);
     }
     // Propagate information about which lines changed to the lint engine.
     // This is used so that the lint engine can drop warning messages
     // concerning lines that weren't in the change.
     $engine->setPaths($paths);
     if (!$should_lint_all) {
         foreach ($paths as $path) {
             // Note that getChangedLines() returns null to indicate that a file
             // is binary or a directory (i.e., changed lines are not relevant).
             $engine->setPathChangedLines($path, $this->getChangedLines($path, 'new'));
         }
     }
     $results = $engine->run();
     if ($this->getArgument('never-apply-patches')) {
         $apply_patches = false;
     } else {
         $apply_patches = true;
     }
     if ($this->getArgument('apply-patches')) {
         $prompt_patches = false;
     } else {
         $prompt_patches = true;
     }
     if ($this->getArgument('amend-all')) {
         $this->shouldAmendChanges = true;
         $this->shouldAmendWithoutPrompt = true;
     }
     if ($this->getArgument('amend-autofixes')) {
         $prompt_autofix_patches = false;
         $this->shouldAmendChanges = true;
         $this->shouldAmendAutofixesWithoutPrompt = true;
     } else {
         $prompt_autofix_patches = true;
     }
     $wrote_to_disk = false;
     switch ($this->getArgument('output')) {
         case 'json':
             $renderer = new ArcanistLintJSONRenderer();
             $prompt_patches = false;
             $apply_patches = $this->getArgument('apply-patches');
             break;
         case 'summary':
             $renderer = new ArcanistLintSummaryRenderer();
             break;
         case 'compiler':
             $renderer = new ArcanistLintLikeCompilerRenderer();
             $prompt_patches = false;
             $apply_patches = $this->getArgument('apply-patches');
             break;
         default:
             $renderer = new ArcanistLintRenderer();
             $renderer->setShowAutofixPatches($prompt_autofix_patches);
             break;
     }
     $all_autofix = true;
     foreach ($results as $result) {
         $result_all_autofix = $result->isAllAutofix();
         if (!$result->getMessages() && !$result_all_autofix) {
             continue;
         }
         if (!$result_all_autofix) {
             $all_autofix = false;
         }
         $lint_result = $renderer->renderLintResult($result);
         if ($lint_result) {
             echo $lint_result;
         }
         if ($apply_patches && $result->isPatchable()) {
             $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
             $old = $patcher->getUnmodifiedFileContent();
             $new = $patcher->getModifiedFileContent();
             if ($prompt_patches && !($result_all_autofix && !$prompt_autofix_patches)) {
                 $old_file = $result->getFilePathOnDisk();
                 if (!Filesystem::pathExists($old_file)) {
                     $old_file = '/dev/null';
                 }
                 $new_file = new TempFile();
                 Filesystem::writeFile($new_file, $new);
                 // TODO: Improve the behavior here, make it more like
                 // difference_render().
                 passthru(csprintf("diff -u %s %s", $old_file, $new_file));
                 $prompt = phutil_console_format("Apply this patch to __%s__?", $result->getPath());
                 if (!phutil_console_confirm($prompt, $default_no = false)) {
                     continue;
                 }
             }
             $patcher->writePatchToDisk();
             $wrote_to_disk = true;
         }
     }
     $repository_api = $this->getRepositoryAPI();
     if ($wrote_to_disk && $repository_api instanceof ArcanistGitAPI && $this->shouldAmendChanges) {
         if ($this->shouldAmendWithoutPrompt || $this->shouldAmendAutofixesWithoutPrompt && $all_autofix) {
             echo phutil_console_format("<bg:yellow>** LINT NOTICE **</bg> Automatically amending HEAD " . "with lint patches.\n");
             $amend = true;
         } else {
             $amend = phutil_console_confirm("Amend HEAD with lint patches?");
         }
         if ($amend) {
             execx('(cd %s; git commit -a --amend -C HEAD)', $repository_api->getPath());
         } else {
             throw new ArcanistUsageException("Sort out the lint changes that were applied to the working " . "copy and relint.");
         }
     }
     $unresolved = array();
     $has_warnings = false;
     $has_errors = false;
     foreach ($results as $result) {
         foreach ($result->getMessages() as $message) {
             if (!$message->isPatchApplied()) {
                 if ($message->isError()) {
                     $has_errors = true;
                 } else {
                     if ($message->isWarning()) {
                         $has_warnings = true;
                     }
                 }
                 $unresolved[] = $message;
             }
         }
     }
     $this->unresolvedMessages = $unresolved;
     // Take the most severe lint message severity and use that
     // as the result code.
     if ($has_errors) {
         $result_code = self::RESULT_ERRORS;
     } else {
         if ($has_warnings) {
             $result_code = self::RESULT_WARNINGS;
         } else {
             $result_code = self::RESULT_OKAY;
         }
     }
     if (!$this->getParentWorkflow()) {
         if ($result_code == self::RESULT_OKAY) {
             echo $renderer->renderOkayResult();
         }
     }
     return $result_code;
 }