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());
 }