protected function processDiffusionRequest(AphrontRequest $request)
 {
     $limit = 500;
     $offset = $request->getInt('offset', 0);
     $drequest = $this->getDiffusionRequest();
     $branch = $drequest->loadBranch();
     $messages = $this->loadLintMessages($branch, $limit, $offset);
     $is_dir = substr('/' . $drequest->getPath(), -1) == '/';
     $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID'));
     $rows = array();
     foreach ($messages as $message) {
         $path = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'lint', 'path' => $message['path']))), substr($message['path'], strlen($drequest->getPath()) + 1));
         $line = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'browse', 'path' => $message['path'], 'line' => $message['line'], 'commit' => $branch->getLintCommit()))), $message['line']);
         $author = $message['authorPHID'];
         if ($author && $authors[$author]) {
             $author = $authors[$author]->renderLink();
         }
         $rows[] = array($path, $line, $author, ArcanistLintSeverity::getStringForSeverity($message['severity']), $message['name'], $message['description']);
     }
     $table = id(new AphrontTableView($rows))->setHeaders(array(pht('Path'), pht('Line'), pht('Author'), pht('Severity'), pht('Name'), pht('Description')))->setColumnClasses(array('', 'n'))->setColumnVisibility(array($is_dir));
     $content = array();
     $pager = id(new AphrontPagerView())->setPageSize($limit)->setOffset($offset)->setHasMorePages(count($messages) >= $limit)->setURI($request->getRequestURI(), 'offset');
     $content[] = id(new PHUIObjectBoxView())->setHeaderText(pht('Lint Details'))->appendChild($table);
     $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'lint'));
     return $this->buildApplicationPage(array($crumbs, $content, $pager), array('title' => array(pht('Lint'), $drequest->getRepository()->getCallsign())));
 }
 public function renderLintResult(ArcanistLintResult $result)
 {
     $messages = $result->getMessages();
     $path = $result->getPath();
     $lines = explode("\n", $result->getData());
     $text = array();
     foreach ($messages as $message) {
         if (!$this->showAutofixPatches && $message->isAutofix()) {
             continue;
         }
         if ($message->isError()) {
             $color = 'red';
         } else {
             $color = 'yellow';
         }
         $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity());
         $code = $message->getCode();
         $name = $message->getName();
         $description = phutil_console_wrap($message->getDescription(), 4);
         $text[] = phutil_console_format("  **<bg:{$color}> %s </bg>** (%s) __%s__\n%s\n", $severity, $code, $name, $description);
         if ($message->hasFileContext()) {
             $text[] = $this->renderContext($message, $lines);
         }
     }
     if ($text) {
         $prefix = phutil_console_format("**>>>** Lint for __%s__:\n\n\n", $path);
         return $prefix . implode("\n", $text);
     } else {
         return null;
     }
 }
 private function renderSeverity($severity)
 {
     $names = ArcanistLintSeverity::getLintSeverities();
     $name = idx($names, $severity, $severity);
     // TODO: Add some color here?
     return $name;
 }
 public function renderLintResult(ArcanistLintResult $result)
 {
     $messages = $result->getMessages();
     $path = $result->getPath();
     $text = array();
     foreach ($messages as $message) {
         $name = $message->getName();
         $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity());
         $line = $message->getLine();
         $text[] = "{$path}:{$line}:{$severity}: {$name}\n";
     }
     return implode('', $text);
 }
 public function renderLintResult(ArcanistLintResult $result)
 {
     $lines = array();
     $messages = $result->getMessages();
     $path = $result->getPath();
     foreach ($messages as $message) {
         $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity());
         $line = $message->getLine();
         $code = $message->getCode();
         $description = $message->getDescription();
         $lines[] = sprintf("%s:%d:%s (%s) %s\n", $path, $line, $severity, $code, $description);
     }
     return implode('', $lines);
 }
 public function renderLintResult(ArcanistLintResult $result)
 {
     $this->writer->startElement('file');
     $this->writer->writeAttribute('name', $result->getPath());
     foreach ($result->getMessages() as $message) {
         $this->writer->startElement('error');
         $this->writer->writeAttribute('line', $message->getLine());
         $this->writer->writeAttribute('column', $message->getChar());
         $this->writer->writeAttribute('severity', ArcanistLintSeverity::getStringForSeverity($message->getSeverity()));
         $this->writer->writeAttribute('message', $message->getDescription());
         $this->writer->writeAttribute('source', $message->getCode());
         $this->writer->endElement();
     }
     $this->writer->endElement();
     return $this->writer->flush();
 }
 public function renderLintResult(ArcanistLintResult $result)
 {
     $messages = $result->getMessages();
     $path = $result->getPath();
     $lines = explode("\n", $result->getData());
     $text = array();
     foreach ($messages as $message) {
         if (!$this->showAutofixPatches && $message->isAutofix()) {
             continue;
         }
         if ($message->isError()) {
             $color = 'red';
         } else {
             $color = 'yellow';
         }
         $severity = ArcanistLintSeverity::getStringForSeverity($message->getSeverity());
         $code = $message->getCode();
         $name = $message->getName();
         $description = $message->getDescription();
         if ($message->getOtherLocations()) {
             $locations = array();
             foreach ($message->getOtherLocations() as $location) {
                 $locations[] = idx($location, 'path', $path) . (!empty($location['line']) ? ":{$location['line']}" : '');
             }
             $description .= "\n" . pht('Other locations: %s', implode(', ', $locations));
         }
         $text[] = phutil_console_format("  **<bg:{$color}> %s </bg>** (%s) __%s__\n%s\n", $severity, $code, $name, phutil_console_wrap($description, 4));
         if ($message->hasFileContext()) {
             $text[] = $this->renderContext($message, $lines);
         }
     }
     if ($text) {
         $prefix = phutil_console_format("**>>>** %s\n\n\n", pht('Lint for %s:', phutil_console_format('__%s__', $path)));
         return $prefix . implode("\n", $text);
     } else {
         return null;
     }
 }
 public function getArguments()
 {
     return array('lintall' => array('help' => pht('Show all lint warnings, not just those on changed lines. When ' . 'paths are specified, this is the default behavior.'), 'conflicts' => array('only-changed' => true)), 'only-changed' => array('help' => pht('Show lint warnings just on changed lines. When no paths are ' . 'specified, this is the default. This differs from only-new ' . 'in cases where line modifications introduce lint on other ' . 'unmodified lines.'), 'conflicts' => array('lintall' => true)), 'rev' => array('param' => 'revision', 'help' => pht('Lint changes since a specific revision.'), 'supports' => array('git', 'hg'), 'nosupport' => array('svn' => pht('Lint does not currently support %s in SVN.', '--rev'))), 'output' => array('param' => 'format', 'help' => pht("With 'summary', show lint warnings in a more compact format. " . "With 'json', show lint warnings in machine-readable JSON format. " . "With 'none', show no lint warnings. " . "With 'compiler', show lint warnings in suitable for your editor. " . "With 'xml', show lint warnings in the Checkstyle XML format.")), 'outfile' => array('param' => 'path', 'help' => pht('Output the linter results to a file. Defaults to stdout.')), 'only-new' => array('param' => 'bool', 'supports' => array('git', 'hg'), 'help' => pht('Display only messages not present in the original code.')), 'engine' => array('param' => 'classname', 'help' => pht('Override configured lint engine for this project.')), 'apply-patches' => array('help' => pht('Apply patches suggested by lint to the working copy without ' . 'prompting.'), 'conflicts' => array('never-apply-patches' => true)), 'never-apply-patches' => array('help' => pht('Never apply patches suggested by lint.'), 'conflicts' => array('apply-patches' => true)), 'amend-all' => array('help' => pht('When linting git repositories, amend HEAD with all patches ' . 'suggested by lint without prompting.')), 'amend-autofixes' => array('help' => pht('When linting git repositories, amend HEAD with autofix ' . 'patches suggested by lint without prompting.')), 'everything' => array('help' => pht('Lint all files in the project.'), 'conflicts' => array('cache' => pht('%s lints all files', '--everything'), 'rev' => pht('%s lints all files', '--everything'))), 'severity' => array('param' => 'string', 'help' => pht("Set minimum message severity. One of: %s. Defaults to '%s'.", sprintf("'%s'", implode("', '", array_keys(ArcanistLintSeverity::getLintSeverities()))), self::DEFAULT_SEVERITY)), 'cache' => array('param' => 'bool', 'help' => pht("%d to disable cache, %d to enable. The default value is determined " . "by '%s' in configuration, which defaults to off. See notes in '%s'.", 0, 1, 'arc.lint.cache', 'arc.lint.cache')), '*' => 'paths');
 }
예제 #9
0
 public final function isSeverityEnabled($severity)
 {
     $minimum = $this->minimumSeverity;
     return ArcanistLintSeverity::isAtLeastAsSevere($severity, $minimum);
 }
예제 #10
0
 protected function processDiffusionRequest(AphrontRequest $request)
 {
     $user = $request->getUser();
     $drequest = $this->diffusionRequest;
     if ($request->getStr('lint') !== null) {
         $controller = new DiffusionLintDetailsController();
         $controller->setDiffusionRequest($drequest);
         $controller->setCurrentApplication($this->getCurrentApplication());
         return $this->delegateToController($controller);
     }
     $owners = array();
     if (!$drequest) {
         if (!$request->getArr('owner')) {
             $owners = array($user->getPHID());
         } else {
             $owners = array(head($request->getArr('owner')));
         }
     }
     $codes = $this->loadLintCodes($owners);
     if ($codes && !$drequest) {
         // TODO: Build some real Query classes for this stuff.
         $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere('id IN (%Ld)', array_unique(ipull($codes, 'branchID')));
         $repositories = id(new PhabricatorRepositoryQuery())->setViewer($user)->withIDs(mpull($branches, 'getRepositoryID'))->execute();
         $drequests = array();
         foreach ($branches as $id => $branch) {
             if (empty($repositories[$branch->getRepositoryID()])) {
                 continue;
             }
             $drequests[$id] = DiffusionRequest::newFromDictionary(array('user' => $user, 'repository' => $repositories[$branch->getRepositoryID()], 'branch' => $branch->getName()));
         }
     }
     $rows = array();
     $total = 0;
     foreach ($codes as $code) {
         if (!$this->diffusionRequest) {
             $drequest = idx($drequests, $code['branchID']);
         }
         if (!$drequest) {
             continue;
         }
         $total += $code['n'];
         $href_lint = $drequest->generateURI(array('action' => 'lint', 'lint' => $code['code']));
         $href_browse = $drequest->generateURI(array('action' => 'browse', 'lint' => $code['code']));
         $href_repo = $drequest->generateURI(array('action' => 'lint'));
         $rows[] = array(phutil_tag('a', array('href' => $href_lint), $code['n']), phutil_tag('a', array('href' => $href_browse), $code['files']), phutil_tag('a', array('href' => $href_repo), $drequest->getRepository()->getDisplayName()), ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']), $code['code'], $code['maxName'], $code['maxDescription']);
     }
     $table = id(new AphrontTableView($rows))->setHeaders(array(pht('Problems'), pht('Files'), pht('Repository'), pht('Severity'), pht('Code'), pht('Name'), pht('Example')))->setColumnVisibility(array(true, true, !$this->diffusionRequest))->setColumnClasses(array('n', 'n', '', '', 'pri', '', ''));
     $content = array();
     if (!$this->diffusionRequest) {
         $form = id(new AphrontFormView())->setUser($user)->setMethod('GET')->appendControl(id(new AphrontFormTokenizerControl())->setDatasource(new PhabricatorPeopleDatasource())->setLimit(1)->setName('owner')->setLabel(pht('Owner'))->setValue($owners))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Filter')));
         $content[] = id(new AphrontListFilterView())->appendChild($form);
     }
     $content[] = id(new PHUIObjectBoxView())->setHeaderText(pht('Lint'))->setTable($table);
     $title = array('Lint');
     $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'lint'));
     if ($this->diffusionRequest) {
         $title[] = $drequest->getRepository()->getDisplayName();
     } else {
         $crumbs->addTextCrumb(pht('All Lint'));
     }
     if ($this->diffusionRequest) {
         $branch = $drequest->loadBranch();
         $header = id(new PHUIHeaderView())->setHeader($this->renderPathLinks($drequest, 'lint'))->setUser($user)->setPolicyObject($drequest->getRepository());
         $actions = $this->buildActionView($drequest);
         $properties = $this->buildPropertyView($drequest, $branch, $total, $actions);
         $object_box = id(new PHUIObjectBoxView())->setHeader($header)->addPropertyList($properties);
     } else {
         $object_box = null;
     }
     return $this->buildApplicationPage(array($crumbs, $object_box, $content), array('title' => $title));
 }
예제 #11
0
 public function run()
 {
     $stopped = array();
     $linters = $this->buildLinters();
     if (!$linters) {
         throw new ArcanistNoEffectException("No linters to run.");
     }
     $have_paths = false;
     foreach ($linters as $linter) {
         if ($linter->getPaths()) {
             $have_paths = true;
             break;
         }
     }
     if (!$have_paths) {
         throw new ArcanistNoEffectException("No paths are lintable.");
     }
     foreach ($linters as $linter) {
         $linter->setEngine($this);
         if (!$linter->canRun()) {
             continue;
         }
         $paths = $linter->getPaths();
         foreach ($paths as $key => $path) {
             // Make sure each path has a result generated, even if it is empty
             // (i.e., the file has no lint messages).
             $result = $this->getResultForPath($path);
             if (isset($stopped[$path])) {
                 unset($paths[$key]);
             }
         }
         $paths = array_values($paths);
         if ($paths) {
             $linter->willLintPaths($paths);
             foreach ($paths as $path) {
                 $linter->willLintPath($path);
                 $linter->lintPath($path);
                 if ($linter->didStopAllLinters()) {
                     $stopped[$path] = true;
                 }
             }
         }
         $minimum = $this->minimumSeverity;
         foreach ($linter->getLintMessages() as $message) {
             if (!ArcanistLintSeverity::isAtLeastAsSevere($message, $minimum)) {
                 continue;
             }
             // When a user runs "arc lint", we default to raising only warnings on
             // lines they have changed (errors are still raised anywhere in the
             // file). The list of $changed lines may be null, to indicate that the
             // path is a directory or a binary file so we should not exclude
             // warnings.
             $changed = $this->getPathChangedLines($message->getPath());
             if ($changed !== null && !$message->isError() && $message->getLine()) {
                 if (empty($changed[$message->getLine()])) {
                     continue;
                 }
             }
             $result = $this->getResultForPath($message->getPath());
             $result->addMessage($message);
         }
     }
     foreach ($this->results as $path => $result) {
         $disk_path = $this->getFilePathOnDisk($path);
         $result->setFilePathOnDisk($disk_path);
         if (isset($this->fileData[$path])) {
             $result->setData($this->fileData[$path]);
         } else {
             if ($disk_path && Filesystem::pathExists($disk_path)) {
                 // TODO: this may cause us to, e.g., load a large binary when we only
                 // raised an error about its filename. We could refine this by looking
                 // through the lint messages and doing this load only if any of them
                 // have original/replacement text or something like that.
                 try {
                     $this->fileData[$path] = Filesystem::readFile($disk_path);
                     $result->setData($this->fileData[$path]);
                 } catch (FilesystemException $ex) {
                     // Ignore this, it's noncritical that we access this data and it
                     // might be unreadable or a directory or whatever else for plenty
                     // of legitimate reasons.
                 }
             }
         }
     }
     return $this->results;
 }
 private function buildDisplayRows(array $lines, array $blame_list, array $blame_commits, $show_color, $show_blame)
 {
     $request = $this->getRequest();
     $viewer = $this->getViewer();
     $drequest = $this->getDiffusionRequest();
     $repository = $drequest->getRepository();
     $revision_map = array();
     $revisions = array();
     if ($blame_commits) {
         $commit_map = mpull($blame_commits, 'getCommitIdentifier', 'getPHID');
         $revision_ids = id(new DifferentialRevision())->loadIDsByCommitPHIDs(array_keys($commit_map));
         if ($revision_ids) {
             $revisions = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs($revision_ids)->execute();
             $revisions = mpull($revisions, null, 'getID');
         }
         foreach ($revision_ids as $commit_phid => $revision_id) {
             $revision_map[$commit_map[$commit_phid]] = $revision_id;
         }
     }
     $phids = array();
     foreach ($blame_commits as $commit) {
         $author_phid = $commit->getAuthorPHID();
         if ($author_phid === null) {
             continue;
         }
         $phids[$author_phid] = $author_phid;
     }
     foreach ($revisions as $revision) {
         $author_phid = $revision->getAuthorPHID();
         if ($author_phid === null) {
             continue;
         }
         $phids[$author_phid] = $author_phid;
     }
     $handles = $viewer->loadHandles($phids);
     $colors = array();
     if ($blame_commits) {
         $epochs = array();
         foreach ($blame_commits as $identifier => $commit) {
             $epochs[$identifier] = $commit->getEpoch();
         }
         $epoch_list = array_filter($epochs);
         $epoch_list = array_unique($epoch_list);
         $epoch_list = array_values($epoch_list);
         $epoch_min = min($epoch_list);
         $epoch_max = max($epoch_list);
         $epoch_range = $epoch_max - $epoch_min + 1;
         foreach ($blame_commits as $identifier => $commit) {
             $epoch = $epochs[$identifier];
             if (!$epoch) {
                 $color = '#ffffdd';
                 // Warning color, missing data.
             } else {
                 $color_ratio = ($epoch - $epoch_min) / $epoch_range;
                 $color_value = 0xe6 * (1.0 - $color_ratio);
                 $color = sprintf('#%02x%02x%02x', $color_value, 0xf6, $color_value);
             }
             $colors[$identifier] = $color;
         }
     }
     $display = array();
     $last_identifier = null;
     $last_color = null;
     foreach ($lines as $line_index => $line) {
         $color = '#f6f6f6';
         $duplicate = false;
         if (isset($blame_list[$line_index])) {
             $identifier = $blame_list[$line_index];
             if (isset($colors[$identifier])) {
                 $color = $colors[$identifier];
             }
             if ($identifier === $last_identifier) {
                 $duplicate = true;
             } else {
                 $last_identifier = $identifier;
             }
         }
         $display[$line_index] = array('data' => $line, 'target' => false, 'highlighted' => false, 'color' => $color, 'duplicate' => $duplicate);
     }
     $line_arr = array();
     $line_str = $drequest->getLine();
     $ranges = explode(',', $line_str);
     foreach ($ranges as $range) {
         if (strpos($range, '-') !== false) {
             list($min, $max) = explode('-', $range, 2);
             $line_arr[] = array('min' => min($min, $max), 'max' => max($min, $max));
         } else {
             if (strlen($range)) {
                 $line_arr[] = array('min' => $range, 'max' => $range);
             }
         }
     }
     // Mark the first highlighted line as the target line.
     if ($line_arr) {
         $target_line = $line_arr[0]['min'];
         if (isset($display[$target_line - 1])) {
             $display[$target_line - 1]['target'] = true;
         }
     }
     // Mark all other highlighted lines as highlighted.
     foreach ($line_arr as $range) {
         for ($ii = $range['min']; $ii <= $range['max']; $ii++) {
             if (isset($display[$ii - 1])) {
                 $display[$ii - 1]['highlighted'] = true;
             }
         }
     }
     $engine = null;
     $inlines = array();
     if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) {
         $engine = new PhabricatorMarkupEngine();
         $engine->setViewer($viewer);
         foreach ($this->lintMessages as $message) {
             $inline = id(new PhabricatorAuditInlineComment())->setSyntheticAuthor(ArcanistLintSeverity::getStringForSeverity($message['severity']) . ' ' . $message['code'] . ' (' . $message['name'] . ')')->setLineNumber($message['line'])->setContent($message['description']);
             $inlines[$message['line']][] = $inline;
             $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
         }
         $engine->process();
         require_celerity_resource('differential-changeset-view-css');
     }
     $rows = $this->renderInlines(idx($inlines, 0, array()), $show_blame, (bool) $this->coverage, $engine);
     // NOTE: We're doing this manually because rendering is otherwise
     // dominated by URI generation for very large files.
     $line_base = (string) $drequest->generateURI(array('action' => 'browse', 'stable' => true));
     require_celerity_resource('aphront-tooltip-css');
     Javelin::initBehavior('phabricator-oncopy');
     Javelin::initBehavior('phabricator-tooltips');
     Javelin::initBehavior('phabricator-line-linker');
     // Render these once, since they tend to get repeated many times in large
     // blame outputs.
     $commit_links = $this->renderCommitLinks($blame_commits, $handles);
     $revision_links = $this->renderRevisionLinks($revisions, $handles);
     $skip_text = pht('Skip Past This Commit');
     foreach ($display as $line_index => $line) {
         $row = array();
         $line_number = $line_index + 1;
         $line_href = $line_base . '$' . $line_number;
         if (isset($blame_list[$line_index])) {
             $identifier = $blame_list[$line_index];
         } else {
             $identifier = null;
         }
         $revision_link = null;
         $commit_link = null;
         $before_link = null;
         $style = 'background: ' . $line['color'] . ';';
         if ($identifier && !$line['duplicate']) {
             if (isset($commit_links[$identifier])) {
                 $commit_link = $commit_links[$identifier];
             }
             if (isset($revision_map[$identifier])) {
                 $revision_id = $revision_map[$identifier];
                 if (isset($revision_links[$revision_id])) {
                     $revision_link = $revision_links[$revision_id];
                 }
             }
             $skip_href = $line_href . '?before=' . $identifier . '&view=blame';
             $before_link = javelin_tag('a', array('href' => $skip_href, 'sigil' => 'has-tooltip', 'meta' => array('tip' => $skip_text, 'align' => 'E', 'size' => 300)), "«");
         }
         $row[] = phutil_tag('th', array('class' => 'diffusion-blame-link'), $before_link);
         $object_links = array();
         $object_links[] = $commit_link;
         if ($revision_link) {
             $object_links[] = phutil_tag('span', array(), '/');
             $object_links[] = $revision_link;
         }
         $row[] = phutil_tag('th', array('class' => 'diffusion-rev-link'), $object_links);
         $line_link = phutil_tag('a', array('href' => $line_href, 'style' => $style), $line_number);
         $row[] = javelin_tag('th', array('class' => 'diffusion-line-link', 'sigil' => 'phabricator-source-line', 'style' => $style), $line_link);
         if ($line['target']) {
             Javelin::initBehavior('diffusion-jump-to', array('target' => 'scroll_target'));
             $anchor_text = phutil_tag('a', array('id' => 'scroll_target'), '');
         } else {
             $anchor_text = null;
         }
         $row[] = phutil_tag('td', array(), array($anchor_text, "​", phutil_safe_html(str_replace("\t", '  ', $line['data']))));
         if ($this->coverage) {
             require_celerity_resource('differential-changeset-view-css');
             $cov_index = $line_index;
             if (isset($this->coverage[$cov_index])) {
                 $cov_class = $this->coverage[$cov_index];
             } else {
                 $cov_class = 'N';
             }
             $row[] = phutil_tag('td', array('class' => 'cov cov-' . $cov_class), '');
         }
         $rows[] = phutil_tag('tr', array('class' => $line['highlighted'] ? 'phabricator-source-highlight' : null), $row);
         $cur_inlines = $this->renderInlines(idx($inlines, $line_number, array()), $show_blame, $this->coverage, $engine);
         foreach ($cur_inlines as $cur_inline) {
             $rows[] = $cur_inline;
         }
     }
     return $rows;
 }
 private function buildDisplayRows(array $text_list, array $rev_list, array $blame_dict, $needs_blame, DiffusionRequest $drequest, $show_blame, $show_color)
 {
     $handles = array();
     if ($blame_dict) {
         $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch');
         $epoch_min = min($epoch_list);
         $epoch_max = max($epoch_list);
         $epoch_range = $epoch_max - $epoch_min + 1;
         $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID');
         $handles = $this->loadViewerHandles($author_phids);
     }
     $line_arr = array();
     $line_str = $drequest->getLine();
     $ranges = explode(',', $line_str);
     foreach ($ranges as $range) {
         if (strpos($range, '-') !== false) {
             list($min, $max) = explode('-', $range, 2);
             $line_arr[] = array('min' => min($min, $max), 'max' => max($min, $max));
         } else {
             if (strlen($range)) {
                 $line_arr[] = array('min' => $range, 'max' => $range);
             }
         }
     }
     $display = array();
     $line_number = 1;
     $last_rev = null;
     $color = null;
     foreach ($text_list as $k => $line) {
         $display_line = array('epoch' => null, 'commit' => null, 'author' => null, 'target' => null, 'highlighted' => null, 'line' => $line_number, 'data' => $line);
         if ($show_blame) {
             // If the line's rev is same as the line above, show empty content
             // with same color; otherwise generate blame info. The newer a change
             // is, the more saturated the color.
             $rev = idx($rev_list, $k, $last_rev);
             if ($last_rev == $rev) {
                 $display_line['color'] = $color;
             } else {
                 $blame = $blame_dict[$rev];
                 if (!isset($blame['epoch'])) {
                     $color = '#ffd';
                     // Render as warning.
                 } else {
                     $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range;
                     $color_value = 0xe6 * (1.0 - $color_ratio);
                     $color = sprintf('#%02x%02x%02x', $color_value, 0xf6, $color_value);
                 }
                 $display_line['epoch'] = idx($blame, 'epoch');
                 $display_line['color'] = $color;
                 $display_line['commit'] = $rev;
                 $author_phid = idx($blame, 'authorPHID');
                 if ($author_phid && $handles[$author_phid]) {
                     $author_link = $handles[$author_phid]->renderLink();
                 } else {
                     $author_link = $blame['author'];
                 }
                 $display_line['author'] = $author_link;
                 $last_rev = $rev;
             }
         }
         if ($line_arr) {
             if ($line_number == $line_arr[0]['min']) {
                 $display_line['target'] = true;
             }
             foreach ($line_arr as $range) {
                 if ($line_number >= $range['min'] && $line_number <= $range['max']) {
                     $display_line['highlighted'] = true;
                 }
             }
         }
         $display[] = $display_line;
         ++$line_number;
     }
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $commits = array_filter(ipull($display, 'commit'));
     if ($commits) {
         $commits = id(new DiffusionCommitQuery())->setViewer($viewer)->withRepository($drequest->getRepository())->withIdentifiers($commits)->execute();
         $commits = mpull($commits, null, 'getCommitIdentifier');
     }
     $revision_ids = id(new DifferentialRevision())->loadIDsByCommitPHIDs(mpull($commits, 'getPHID'));
     $revisions = array();
     if ($revision_ids) {
         $revisions = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs($revision_ids)->execute();
     }
     $phids = array();
     foreach ($commits as $commit) {
         if ($commit->getAuthorPHID()) {
             $phids[] = $commit->getAuthorPHID();
         }
     }
     foreach ($revisions as $revision) {
         if ($revision->getAuthorPHID()) {
             $phids[] = $revision->getAuthorPHID();
         }
     }
     $handles = $this->loadViewerHandles($phids);
     Javelin::initBehavior('phabricator-oncopy', array());
     $engine = null;
     $inlines = array();
     if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) {
         $engine = new PhabricatorMarkupEngine();
         $engine->setViewer($viewer);
         foreach ($this->lintMessages as $message) {
             $inline = id(new PhabricatorAuditInlineComment())->setSyntheticAuthor(ArcanistLintSeverity::getStringForSeverity($message['severity']) . ' ' . $message['code'] . ' (' . $message['name'] . ')')->setLineNumber($message['line'])->setContent($message['description']);
             $inlines[$message['line']][] = $inline;
             $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
         }
         $engine->process();
         require_celerity_resource('differential-changeset-view-css');
     }
     $rows = $this->renderInlines(idx($inlines, 0, array()), $show_blame, (bool) $this->coverage, $engine);
     foreach ($display as $line) {
         $line_href = $drequest->generateURI(array('action' => 'browse', 'line' => $line['line'], 'stable' => true));
         $blame = array();
         $style = null;
         if (array_key_exists('color', $line)) {
             if ($line['color']) {
                 $style = 'background: ' . $line['color'] . ';';
             }
             $before_link = null;
             $commit_link = null;
             $revision_link = null;
             if (idx($line, 'commit')) {
                 $commit = $line['commit'];
                 if (idx($commits, $commit)) {
                     $tooltip = $this->renderCommitTooltip($commits[$commit], $handles, $line['author']);
                 } else {
                     $tooltip = null;
                 }
                 Javelin::initBehavior('phabricator-tooltips', array());
                 require_celerity_resource('aphront-tooltip-css');
                 $commit_link = javelin_tag('a', array('href' => $drequest->generateURI(array('action' => 'commit', 'commit' => $line['commit'])), 'sigil' => 'has-tooltip', 'meta' => array('tip' => $tooltip, 'align' => 'E', 'size' => 600)), id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(9)->setTerminator('')->truncateString($line['commit']));
                 $revision_id = null;
                 if (idx($commits, $commit)) {
                     $revision_id = idx($revision_ids, $commits[$commit]->getPHID());
                 }
                 if ($revision_id) {
                     $revision = idx($revisions, $revision_id);
                     if ($revision) {
                         $tooltip = $this->renderRevisionTooltip($revision, $handles);
                         $revision_link = javelin_tag('a', array('href' => '/D' . $revision->getID(), 'sigil' => 'has-tooltip', 'meta' => array('tip' => $tooltip, 'align' => 'E', 'size' => 600)), 'D' . $revision->getID());
                     }
                 }
                 $uri = $line_href->alter('before', $commit);
                 $before_link = javelin_tag('a', array('href' => $uri->setQueryParam('view', 'blame'), 'sigil' => 'has-tooltip', 'meta' => array('tip' => pht('Skip Past This Commit'), 'align' => 'E', 'size' => 300)), "«");
             }
             $blame[] = phutil_tag('th', array('class' => 'diffusion-blame-link'), $before_link);
             $object_links = array();
             $object_links[] = $commit_link;
             if ($revision_link) {
                 $object_links[] = phutil_tag('span', array(), '/');
                 $object_links[] = $revision_link;
             }
             $blame[] = phutil_tag('th', array('class' => 'diffusion-rev-link'), $object_links);
         }
         $line_link = phutil_tag('a', array('href' => $line_href, 'style' => $style), $line['line']);
         $blame[] = javelin_tag('th', array('class' => 'diffusion-line-link', 'sigil' => 'phabricator-source-line', 'style' => $style), $line_link);
         Javelin::initBehavior('phabricator-line-linker');
         if ($line['target']) {
             Javelin::initBehavior('diffusion-jump-to', array('target' => 'scroll_target'));
             $anchor_text = phutil_tag('a', array('id' => 'scroll_target'), '');
         } else {
             $anchor_text = null;
         }
         $blame[] = phutil_tag('td', array(), array($anchor_text, "​", phutil_safe_html(str_replace("\t", '  ', $line['data']))));
         if ($this->coverage) {
             require_celerity_resource('differential-changeset-view-css');
             $cov_index = $line['line'] - 1;
             if (isset($this->coverage[$cov_index])) {
                 $cov_class = $this->coverage[$cov_index];
             } else {
                 $cov_class = 'N';
             }
             $blame[] = phutil_tag('td', array('class' => 'cov cov-' . $cov_class), '');
         }
         $rows[] = phutil_tag('tr', array('class' => $line['highlighted'] ? 'phabricator-source-highlight' : null), $blame);
         $cur_inlines = $this->renderInlines(idx($inlines, $line['line'], array()), $show_blame, $this->coverage, $engine);
         foreach ($cur_inlines as $cur_inline) {
             $rows[] = $cur_inline;
         }
     }
     return $rows;
 }
 public function getMethodDescription()
 {
     $messages = HarbormasterMessageType::getAllMessages();
     $head_type = pht('Constant');
     $head_desc = pht('Description');
     $head_key = pht('Key');
     $head_type = pht('Type');
     $head_name = pht('Name');
     $rows = array();
     $rows[] = "| {$head_type} | {$head_desc} |";
     $rows[] = '|--------------|--------------|';
     foreach ($messages as $message) {
         $description = HarbormasterMessageType::getMessageDescription($message);
         $rows[] = "| `{$message}` | {$description} |";
     }
     $message_table = implode("\n", $rows);
     $rows = array();
     $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";
     $rows[] = '|-------------|--------------|--------------|';
     $unit_spec = HarbormasterBuildUnitMessage::getParameterSpec();
     foreach ($unit_spec as $key => $parameter) {
         $type = idx($parameter, 'type');
         $type = str_replace('|', ' ' . pht('or') . ' ', $type);
         $description = idx($parameter, 'description');
         $rows[] = "| `{$key}` | //{$type}// | {$description} |";
     }
     $unit_table = implode("\n", $rows);
     $rows = array();
     $rows[] = "| {$head_key} | {$head_name} | {$head_desc} |";
     $rows[] = '|-------------|--------------|--------------|';
     $results = ArcanistUnitTestResult::getAllResultCodes();
     foreach ($results as $result_code) {
         $name = ArcanistUnitTestResult::getResultCodeName($result_code);
         $description = ArcanistUnitTestResult::getResultCodeDescription($result_code);
         $rows[] = "| `{$result_code}` | **{$name}** | {$description} |";
     }
     $result_table = implode("\n", $rows);
     $rows = array();
     $rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";
     $rows[] = '|-------------|--------------|--------------|';
     $lint_spec = HarbormasterBuildLintMessage::getParameterSpec();
     foreach ($lint_spec as $key => $parameter) {
         $type = idx($parameter, 'type');
         $type = str_replace('|', ' ' . pht('or') . ' ', $type);
         $description = idx($parameter, 'description');
         $rows[] = "| `{$key}` | //{$type}// | {$description} |";
     }
     $lint_table = implode("\n", $rows);
     $rows = array();
     $rows[] = "| {$head_key} | {$head_name} |";
     $rows[] = '|-------------|--------------|';
     $severities = ArcanistLintSeverity::getLintSeverities();
     foreach ($severities as $key => $name) {
         $rows[] = "| `{$key}` | **{$name}** |";
     }
     $severity_table = implode("\n", $rows);
     $valid_unit = array(array('name' => 'PassingTest', 'result' => ArcanistUnitTestResult::RESULT_PASS), array('name' => 'FailingTest', 'result' => ArcanistUnitTestResult::RESULT_FAIL));
     $valid_lint = array(array('name' => pht('Syntax Error'), 'code' => 'EXAMPLE1', 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, 'path' => 'path/to/example.c', 'line' => 17, 'char' => 3), array('name' => pht('Not A Haiku'), 'code' => 'EXAMPLE2', 'severity' => ArcanistLintSeverity::SEVERITY_ERROR, 'path' => 'path/to/source.cpp', 'line' => 23, 'char' => 1, 'description' => pht('This function definition is not a haiku.')));
     $json = new PhutilJSON();
     $valid_unit = $json->encodeAsList($valid_unit);
     $valid_lint = $json->encodeAsList($valid_lint);
     return pht("Send a message about the status of a build target to Harbormaster, " . "notifying the application of build results in an external system." . "\n\n" . "Sending Messages\n" . "================\n" . "If you run external builds, you can use this method to publish build " . "results back into Harbormaster after the external system finishes work " . "or as it makes progress." . "\n\n" . "The simplest way to use this method is to call it once after the " . "build finishes with a `pass` or `fail` message. This will record the " . "build result, and continue the next step in the build if the build was " . "waiting for a result." . "\n\n" . "When you send a status message about a build target, you can " . "optionally include detailed `lint` or `unit` results alongside the " . "message. See below for details." . "\n\n" . "If you want to report intermediate results but a build hasn't " . "completed yet, you can use the `work` message. This message doesn't " . "have any direct effects, but allows you to send additional data to " . "update the progress of the build target. The target will continue " . "waiting for a completion message, but the UI will update to show the " . "progress which has been made." . "\n\n" . "Message Types\n" . "=============\n" . "When you send Harbormaster a message, you must include a `type`, " . "which describes the overall state of the build. For example, use " . "`pass` to tell Harbomaster that a build completed successfully." . "\n\n" . "Supported message types are:" . "\n\n" . "%s" . "\n\n" . "Unit Results\n" . "============\n" . "You can report test results alongside a message. The simplest way to " . "do this is to report all the results alongside a `pass` or `fail` " . "message, but you can also send a `work` message to report intermediate " . "results.\n\n" . "To provide unit test results, pass a list of results in the `unit` " . "parameter. Each result shoud be a dictionary with these keys:" . "\n\n" . "%s" . "\n\n" . "The `result` parameter recognizes these test results:" . "\n\n" . "%s" . "\n\n" . "This is a simple, valid value for the `unit` parameter. It reports " . "one passing test and one failing test:\n\n" . "\n\n" . "```lang=json\n" . "%s" . "```" . "\n\n" . "Lint Results\n" . "============\n" . "Like unit test results, you can report lint results alongside a " . "message. The `lint` parameter should contain results as a list of " . "dictionaries with these keys:" . "\n\n" . "%s" . "\n\n" . "The `severity` parameter recognizes these severity levels:" . "\n\n" . "%s" . "\n\n" . "This is a simple, valid value for the `lint` parameter. It reports one " . "error and one warning:" . "\n\n" . "```lang=json\n" . "%s" . "```" . "\n\n", $message_table, $unit_table, $result_table, $valid_unit, $lint_table, $severity_table, $valid_lint);
 }
 public function getArguments()
 {
     return array('lintall' => array('help' => "Show all lint warnings, not just those on changed lines."), 'rev' => array('param' => 'revision', 'help' => "Lint changes since a specific revision.", 'supports' => array('git', 'hg'), 'nosupport' => array('svn' => "Lint does not currently support --rev in SVN.")), 'output' => array('param' => 'format', 'help' => "With 'summary', show lint warnings in a more compact format. " . "With 'json', show lint warnings in machine-readable JSON format. " . "With 'none', show no lint warnings. " . "With 'compiler', show lint warnings in suitable for your editor."), 'only-new' => array('param' => 'bool', 'supports' => array('git', 'hg'), 'help' => 'Display only messages not present in the original code.'), 'engine' => array('param' => 'classname', 'help' => "Override configured lint engine for this project."), 'apply-patches' => array('help' => 'Apply patches suggested by lint to the working copy without ' . 'prompting.', 'conflicts' => array('never-apply-patches' => true)), 'never-apply-patches' => array('help' => 'Never apply patches suggested by lint.', 'conflicts' => array('apply-patches' => true)), 'amend-all' => array('help' => 'When linting git repositories, amend HEAD with all patches ' . 'suggested by lint without prompting.'), 'amend-autofixes' => array('help' => 'When linting git repositories, amend HEAD with autofix ' . 'patches suggested by lint without prompting.'), 'severity' => array('param' => 'string', 'help' => "Set minimum message severity. One of: '" . implode("', '", array_keys(ArcanistLintSeverity::getLintSeverities())) . "'. Defaults to '" . self::DEFAULT_SEVERITY . "'."), 'cache' => array('param' => 'bool', 'help' => "0 to disable cache, 1 to enable (default)."), '*' => 'paths');
 }
 private function buildDetailsResponse()
 {
     $request = $this->getRequest();
     $limit = 500;
     $pager = id(new PHUIPagerView())->readFromRequest($request)->setPageSize($limit);
     $offset = $pager->getOffset();
     $drequest = $this->getDiffusionRequest();
     $branch = $drequest->loadBranch();
     $messages = $this->loadLintMessages($branch, $limit, $offset);
     $is_dir = substr('/' . $drequest->getPath(), -1) == '/';
     $pager->setHasMorePages(count($messages) >= $limit);
     $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID'));
     $rows = array();
     foreach ($messages as $message) {
         $path = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'lint', 'path' => $message['path']))), substr($message['path'], strlen($drequest->getPath()) + 1));
         $line = phutil_tag('a', array('href' => $drequest->generateURI(array('action' => 'browse', 'path' => $message['path'], 'line' => $message['line'], 'commit' => $branch->getLintCommit()))), $message['line']);
         $author = $message['authorPHID'];
         if ($author && $authors[$author]) {
             $author = $authors[$author]->renderLink();
         }
         $rows[] = array($path, $line, $author, ArcanistLintSeverity::getStringForSeverity($message['severity']), $message['name'], $message['description']);
     }
     $table = id(new AphrontTableView($rows))->setHeaders(array(pht('Path'), pht('Line'), pht('Author'), pht('Severity'), pht('Name'), pht('Description')))->setColumnClasses(array('', 'n'))->setColumnVisibility(array($is_dir));
     $content = array();
     $content[] = id(new PHUIObjectBoxView())->setHeaderText(pht('Lint Details'))->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)->setTable($table);
     $crumbs = $this->buildCrumbs(array('branch' => true, 'path' => true, 'view' => 'lint'));
     $pager_box = $this->renderTablePagerBox($pager);
     $header = id(new PHUIHeaderView())->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName()))->setHeaderIcon('fa-code');
     $view = id(new PHUITwoColumnView())->setHeader($header)->setFooter(array($content, $pager_box));
     return $this->newPage()->setTitle(array(pht('Lint'), $drequest->getRepository()->getDisplayName()))->setCrumbs($crumbs)->appendChild(array($view));
 }