private function renderInlineCommentsForMail(PhabricatorLiskDAO $object, array $inlines) { $context_key = 'metamta.differential.unified-comment-context'; $show_context = PhabricatorEnv::getEnvConfig($context_key); $changeset_ids = array(); $line_numbers_by_changeset = array(); foreach ($inlines as $inline) { $id = $inline->getComment()->getChangesetID(); $changeset_ids[$id] = $id; $line_numbers_by_changeset[$id][] = $inline->getComment()->getLineNumber(); } $changesets = id(new DifferentialChangesetQuery())->setViewer($this->getActor())->withIDs($changeset_ids)->needHunks(true)->execute(); $inline_groups = DifferentialTransactionComment::sortAndGroupInlines($inlines, $changesets); if ($show_context) { $hunk_parser = new DifferentialHunkParser(); $table = new DifferentialTransactionComment(); $conn_r = $table->establishConnection('r'); $queries = array(); foreach ($line_numbers_by_changeset as $id => $line_numbers) { $queries[] = qsprintf($conn_r, '(changesetID = %d AND lineNumber IN (%Ld))', $id, $line_numbers); } $all_comments = id(new DifferentialTransactionComment())->loadAllWhere('transactionPHID IS NOT NULL AND (%Q)', implode(' OR ', $queries)); $comments_by_line_number = array(); foreach ($all_comments as $comment) { $comments_by_line_number[$comment->getChangesetID()][$comment->getLineNumber()][$comment->getID()] = $comment; } $author_phids = mpull($all_comments, 'getAuthorPHID'); $authors = id(new PhabricatorPeopleQuery())->setViewer($this->getActor())->withPHIDs($author_phids)->execute(); $authors_by_phid = mpull($authors, null, 'getPHID'); } $section = new PhabricatorMetaMTAMailSection(); foreach ($inline_groups as $changeset_id => $group) { $changeset = idx($changesets, $changeset_id); if (!$changeset) { continue; } foreach ($group as $inline) { $comment = $inline->getComment(); $file = $changeset->getFilename(); $start = $comment->getLineNumber(); $len = $comment->getLineLength(); if ($len) { $range = $start . '-' . ($start + $len); } else { $range = $start; } $inline_content = $comment->getContent(); if (!$show_context) { $section->addFragment("{$file}:{$range} {$inline_content}"); } else { $patch = $hunk_parser->makeContextDiff($changeset->getHunks(), $comment->getIsNewFile(), $comment->getLineNumber(), $comment->getLineLength(), 1); $nested_comments = $this->nestCommentHistory($inline->getComment(), $comments_by_line_number, $authors_by_phid); $section->addFragment('================')->addFragment(pht('Comment at: %s:%s', $file, $range))->addPlaintextFragment($patch)->addHTMLFragment($this->renderPatchHTMLForMail($patch))->addFragment('----------------')->addFragment($nested_comments)->addFragment(null); } } } return $section; }
public function testMissingContext() { $tests = array('missing_context.diff' => array(4 => true), 'missing_context_2.diff' => array(5 => true), 'missing_context_3.diff' => array(4 => true, 13 => true)); foreach ($tests as $name => $expect) { $hunks = $this->createHunksFromFile($name); $parser = new DifferentialHunkParser(); $actual = $parser->getHunkStartLines($hunks); $this->assertEqual($expect, $actual, $name); } }
private function process() { $whitespace_mode = $this->whitespaceMode; $changeset = $this->changeset; $ignore_all = $whitespace_mode == self::WHITESPACE_IGNORE_MOST || $whitespace_mode == self::WHITESPACE_IGNORE_ALL; $force_ignore = $whitespace_mode == self::WHITESPACE_IGNORE_ALL; if (!$force_ignore) { if ($ignore_all && $changeset->getWhitespaceMatters()) { $ignore_all = false; } } // The "ignore all whitespace" algorithm depends on rediffing the // files, and we currently need complete representations of both // files to do anything reasonable. If we only have parts of the files, // don't use the "ignore all" algorithm. if ($ignore_all) { $hunks = $changeset->getHunks(); if (count($hunks) !== 1) { $ignore_all = false; } else { $first_hunk = reset($hunks); if ($first_hunk->getOldOffset() != 1 || $first_hunk->getNewOffset() != 1) { $ignore_all = false; } } } if ($ignore_all) { $old_file = $changeset->makeOldFile(); $new_file = $changeset->makeNewFile(); if ($old_file == $new_file) { // If the old and new files are exactly identical, the synthetic // diff below will give us nonsense and whitespace modes are // irrelevant anyway. This occurs when you, e.g., copy a file onto // itself in Subversion (see T271). $ignore_all = false; } } $hunk_parser = new DifferentialHunkParser(); $hunk_parser->setWhitespaceMode($whitespace_mode); $hunk_parser->parseHunksForLineData($changeset->getHunks()); // Depending on the whitespace mode, we may need to compute a different // set of changes than the set of changes in the hunk data (specificaly, // we might want to consider changed lines which have only whitespace // changes as unchanged). if ($ignore_all) { $engine = new PhabricatorDifferenceEngine(); $engine->setIgnoreWhitespace(true); $no_whitespace_changeset = $engine->generateChangesetFromFileContent($old_file, $new_file); $type_parser = new DifferentialHunkParser(); $type_parser->parseHunksForLineData($no_whitespace_changeset->getHunks()); $hunk_parser->setOldLineTypeMap($type_parser->getOldLineTypeMap()); $hunk_parser->setNewLineTypeMap($type_parser->getNewLineTypeMap()); } $hunk_parser->reparseHunksForSpecialAttributes(); $unchanged = false; if (!$hunk_parser->getHasAnyChanges()) { $filetype = $this->changeset->getFileType(); if ($filetype == DifferentialChangeType::FILE_TEXT || $filetype == DifferentialChangeType::FILE_SYMLINK) { $unchanged = true; } } $moveaway = false; $changetype = $this->changeset->getChangeType(); if ($changetype == DifferentialChangeType::TYPE_MOVE_AWAY) { $moveaway = true; } $this->setSpecialAttributes(array(self::ATTR_UNCHANGED => $unchanged, self::ATTR_DELETED => $hunk_parser->getIsDeleted(), self::ATTR_WHITELINES => !$hunk_parser->getHasTextChanges(), self::ATTR_MOVEAWAY => $moveaway)); $lines_context = $this->getLinesOfContext(); $hunk_parser->generateIntraLineDiffs(); $hunk_parser->generateVisibileLinesMask($lines_context); $this->setOldLines($hunk_parser->getOldLines()); $this->setNewLines($hunk_parser->getNewLines()); $this->setIntraLineDiffs($hunk_parser->getIntraLineDiffs()); $this->setVisibileLinesMask($hunk_parser->getVisibleLinesMask()); $this->hunkStartLines = $hunk_parser->getHunkStartLines($changeset->getHunks()); $new_corpus = $hunk_parser->getNewCorpus(); $new_corpus_block = implode('', $new_corpus); $this->markGenerated($new_corpus_block); if ($this->isTopLevel && !$this->comments && ($this->isGenerated() || $this->isUnchanged() || $this->isDeleted())) { return; } $old_corpus = $hunk_parser->getOldCorpus(); $old_corpus_block = implode('', $old_corpus); $old_future = $this->getHighlightFuture($old_corpus_block); $new_future = $this->getHighlightFuture($new_corpus_block); $futures = array('old' => $old_future, 'new' => $new_future); $corpus_blocks = array('old' => $old_corpus_block, 'new' => $new_corpus_block); $this->highlightErrors = false; foreach (new FutureIterator($futures) as $key => $future) { try { try { $highlighted = $future->resolve(); } catch (PhutilSyntaxHighlighterException $ex) { $this->highlightErrors = true; $highlighted = id(new PhutilDefaultSyntaxHighlighter())->getHighlightFuture($corpus_blocks[$key])->resolve(); } switch ($key) { case 'old': $this->oldRender = $this->processHighlightedSource($this->old, $highlighted); break; case 'new': $this->newRender = $this->processHighlightedSource($this->new, $highlighted); break; } } catch (Exception $ex) { phlog($ex); throw $ex; } } $this->applyIntraline($this->oldRender, ipull($this->intra, 0), $old_corpus); $this->applyIntraline($this->newRender, ipull($this->intra, 1), $new_corpus); }
private function getPatch(DifferentialHunkParser $parser, DifferentialTransactionComment $comment, $is_html) { $changeset = $this->getChangeset($comment->getChangesetID()); $is_new = $comment->getIsNewFile(); $start = $comment->getLineNumber(); $length = $comment->getLineLength(); // By default, show one line of context around the target inline. $context = 1; // If the inline is at least 3 lines long, don't show any extra context. if ($length >= 2) { $context = 0; } // If the inline is more than 7 lines long, only show the first 7 lines. if ($length >= 6) { $length = 6; } if (!$is_html) { $hunks = $changeset->getHunks(); $patch = $parser->makeContextDiff($hunks, $is_new, $start, $length, $context); $patch = phutil_split_lines($patch); // Remove the "@@ -x,y +u,v @@" line. array_shift($patch); return implode('', $patch); } $viewer = $this->getViewer(); $engine = new PhabricatorMarkupEngine(); if ($is_new) { $offset_mode = 'new'; } else { $offset_mode = 'old'; } $parser = id(new DifferentialChangesetParser())->setUser($viewer)->setChangeset($changeset)->setOffsetMode($offset_mode)->setMarkupEngine($engine); $parser->setRenderer(new DifferentialChangesetOneUpMailRenderer()); return $parser->render($start - $context, $length + 1 + 2 * $context, array()); }