[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/diffusion/controller/ -> DiffusionBrowseFileController.php (source)

   1  <?php
   2  
   3  final class DiffusionBrowseFileController extends DiffusionBrowseController {
   4  
   5    private $lintCommit;
   6    private $lintMessages;
   7    private $coverage;
   8  
   9    public function processRequest() {
  10      $request = $this->getRequest();
  11      $drequest = $this->getDiffusionRequest();
  12      $viewer = $request->getUser();
  13  
  14      $before = $request->getStr('before');
  15      if ($before) {
  16        return $this->buildBeforeResponse($before);
  17      }
  18  
  19      $path = $drequest->getPath();
  20  
  21      $preferences = $viewer->loadPreferences();
  22  
  23      $show_blame = $request->getBool(
  24        'blame',
  25        $preferences->getPreference(
  26          PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME,
  27          false));
  28      $show_color = $request->getBool(
  29        'color',
  30        $preferences->getPreference(
  31          PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR,
  32          true));
  33  
  34      $view = $request->getStr('view');
  35      if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) {
  36        $preferences->setPreference(
  37          PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME,
  38          $show_blame);
  39        $preferences->setPreference(
  40          PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR,
  41          $show_color);
  42        $preferences->save();
  43  
  44        $uri = $request->getRequestURI()
  45          ->alter('blame', null)
  46          ->alter('color', null);
  47  
  48        return id(new AphrontRedirectResponse())->setURI($uri);
  49      }
  50  
  51      // We need the blame information if blame is on and we're building plain
  52      // text, or blame is on and this is an Ajax request. If blame is on and
  53      // this is a colorized request, we don't show blame at first (we ajax it
  54      // in afterward) so we don't need to query for it.
  55      $needs_blame = ($show_blame && !$show_color) ||
  56                     ($show_blame && $request->isAjax());
  57  
  58      $file_content = DiffusionFileContent::newFromConduit(
  59        $this->callConduitWithDiffusionRequest(
  60          'diffusion.filecontentquery',
  61          array(
  62            'commit' => $drequest->getCommit(),
  63            'path' => $drequest->getPath(),
  64            'needsBlame' => $needs_blame,
  65          )));
  66      $data = $file_content->getCorpus();
  67  
  68      if ($view === 'raw') {
  69        return $this->buildRawResponse($path, $data);
  70      }
  71  
  72      $this->loadLintMessages();
  73      $this->coverage = $drequest->loadCoverage();
  74  
  75      $binary_uri = null;
  76      if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
  77        $file = $this->loadFileForData($path, $data);
  78        $file_uri = $file->getBestURI();
  79  
  80        if ($file->isViewableImage()) {
  81          $corpus = $this->buildImageCorpus($file_uri);
  82        } else {
  83          $corpus = $this->buildBinaryCorpus($file_uri, $data);
  84          $binary_uri = $file_uri;
  85        }
  86      } else {
  87        // Build the content of the file.
  88        $corpus = $this->buildCorpus(
  89          $show_blame,
  90          $show_color,
  91          $file_content,
  92          $needs_blame,
  93          $drequest,
  94          $path,
  95          $data);
  96      }
  97  
  98      if ($request->isAjax()) {
  99        return id(new AphrontAjaxResponse())->setContent($corpus);
 100      }
 101  
 102      require_celerity_resource('diffusion-source-css');
 103  
 104      // Render the page.
 105      $view = $this->buildActionView($drequest);
 106      $action_list = $this->enrichActionView(
 107        $view,
 108        $drequest,
 109        $show_blame,
 110        $show_color);
 111  
 112      $properties = $this->buildPropertyView($drequest, $action_list);
 113      $object_box = id(new PHUIObjectBoxView())
 114        ->setHeader($this->buildHeaderView($drequest))
 115        ->addPropertyList($properties);
 116  
 117      $content = array();
 118      $content[] = $object_box;
 119  
 120      $follow  = $request->getStr('follow');
 121      if ($follow) {
 122        $notice = new AphrontErrorView();
 123        $notice->setSeverity(AphrontErrorView::SEVERITY_WARNING);
 124        $notice->setTitle(pht('Unable to Continue'));
 125        switch ($follow) {
 126          case 'first':
 127            $notice->appendChild(
 128              pht('Unable to continue tracing the history of this file because '.
 129              'this commit is the first commit in the repository.'));
 130            break;
 131          case 'created':
 132            $notice->appendChild(
 133              pht('Unable to continue tracing the history of this file because '.
 134              'this commit created the file.'));
 135            break;
 136        }
 137        $content[] = $notice;
 138      }
 139  
 140      $renamed = $request->getStr('renamed');
 141      if ($renamed) {
 142        $notice = new AphrontErrorView();
 143        $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
 144        $notice->setTitle(pht('File Renamed'));
 145        $notice->appendChild(
 146          pht("File history passes through a rename from '%s' to '%s'.",
 147            $drequest->getPath(), $renamed));
 148        $content[] = $notice;
 149      }
 150  
 151      $content[] = $corpus;
 152      $content[] = $this->buildOpenRevisions();
 153  
 154      $crumbs = $this->buildCrumbs(
 155        array(
 156          'branch' => true,
 157          'path'   => true,
 158          'view'   => 'browse',
 159        ));
 160  
 161      $basename = basename($this->getDiffusionRequest()->getPath());
 162  
 163      return $this->buildApplicationPage(
 164        array(
 165          $crumbs,
 166          $content,
 167        ),
 168        array(
 169          'title' => $basename,
 170          'device' => false,
 171        ));
 172    }
 173  
 174    private function loadLintMessages() {
 175      $drequest = $this->getDiffusionRequest();
 176      $branch = $drequest->loadBranch();
 177  
 178      if (!$branch || !$branch->getLintCommit()) {
 179        return;
 180      }
 181  
 182      $this->lintCommit = $branch->getLintCommit();
 183  
 184      $conn = id(new PhabricatorRepository())->establishConnection('r');
 185  
 186      $where = '';
 187      if ($drequest->getLint()) {
 188        $where = qsprintf(
 189          $conn,
 190          'AND code = %s',
 191          $drequest->getLint());
 192      }
 193  
 194      $this->lintMessages = queryfx_all(
 195        $conn,
 196        'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s',
 197        PhabricatorRepository::TABLE_LINTMESSAGE,
 198        $branch->getID(),
 199        $where,
 200        '/'.$drequest->getPath());
 201    }
 202  
 203    private function buildCorpus(
 204      $show_blame,
 205      $show_color,
 206      DiffusionFileContent $file_content,
 207      $needs_blame,
 208      DiffusionRequest $drequest,
 209      $path,
 210      $data) {
 211  
 212      if (!$show_color) {
 213        $style =
 214          'border: none; width: 100%; height: 80em; font-family: monospace';
 215        if (!$show_blame) {
 216          $corpus = phutil_tag(
 217            'textarea',
 218            array(
 219              'style' => $style,
 220            ),
 221            $file_content->getCorpus());
 222        } else {
 223          $text_list = $file_content->getTextList();
 224          $rev_list = $file_content->getRevList();
 225          $blame_dict = $file_content->getBlameDict();
 226  
 227          $rows = array();
 228          foreach ($text_list as $k => $line) {
 229            $rev = $rev_list[$k];
 230            $author = $blame_dict[$rev]['author'];
 231            $rows[] =
 232              sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line);
 233          }
 234  
 235          $corpus = phutil_tag(
 236            'textarea',
 237            array(
 238              'style' => $style,
 239            ),
 240            implode("\n", $rows));
 241        }
 242      } else {
 243        require_celerity_resource('syntax-highlighting-css');
 244        $text_list = $file_content->getTextList();
 245        $rev_list = $file_content->getRevList();
 246        $blame_dict = $file_content->getBlameDict();
 247  
 248        $text_list = implode("\n", $text_list);
 249        $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename(
 250          $path,
 251          $text_list);
 252        $text_list = explode("\n", $text_list);
 253  
 254        $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict,
 255          $needs_blame, $drequest, $show_blame, $show_color);
 256  
 257        $corpus_table = javelin_tag(
 258          'table',
 259          array(
 260            'class' => 'diffusion-source remarkup-code PhabricatorMonospaced',
 261            'sigil' => 'phabricator-source',
 262          ),
 263          $rows);
 264  
 265        if ($this->getRequest()->isAjax()) {
 266          return $corpus_table;
 267        }
 268  
 269        $id = celerity_generate_unique_node_id();
 270  
 271        $projects = $drequest->loadArcanistProjects();
 272        $langs = array();
 273        foreach ($projects as $project) {
 274          $ls = $project->getSymbolIndexLanguages();
 275          if (!$ls) {
 276            continue;
 277          }
 278          $dep_projects = $project->getSymbolIndexProjects();
 279          $dep_projects[] = $project->getPHID();
 280          foreach ($ls as $lang) {
 281            if (!isset($langs[$lang])) {
 282              $langs[$lang] = array();
 283            }
 284            $langs[$lang] += $dep_projects + array($project);
 285          }
 286        }
 287  
 288        $lang = last(explode('.', $drequest->getPath()));
 289  
 290        if (isset($langs[$lang])) {
 291          Javelin::initBehavior(
 292            'repository-crossreference',
 293            array(
 294              'container' => $id,
 295              'lang' => $lang,
 296              'projects' => $langs[$lang],
 297            ));
 298        }
 299  
 300        $corpus = phutil_tag(
 301          'div',
 302          array(
 303            'id' => $id,
 304          ),
 305          $corpus_table);
 306  
 307        Javelin::initBehavior('load-blame', array('id' => $id));
 308      }
 309  
 310      $edit = $this->renderEditButton();
 311      $file = $this->renderFileButton();
 312      $header = id(new PHUIHeaderView())
 313        ->setHeader(pht('File Contents'))
 314        ->addActionLink($edit)
 315        ->addActionLink($file);
 316  
 317      $corpus = id(new PHUIObjectBoxView())
 318        ->setHeader($header)
 319        ->appendChild($corpus);
 320  
 321      return $corpus;
 322    }
 323  
 324    private function enrichActionView(
 325      PhabricatorActionListView $view,
 326      DiffusionRequest $drequest,
 327      $show_blame,
 328      $show_color) {
 329  
 330      $viewer = $this->getRequest()->getUser();
 331      $base_uri = $this->getRequest()->getRequestURI();
 332  
 333      $view->addAction(
 334        id(new PhabricatorActionView())
 335          ->setName(pht('Show Last Change'))
 336          ->setHref(
 337            $drequest->generateURI(
 338              array(
 339                'action' => 'change',
 340              )))
 341          ->setIcon('fa-backward'));
 342  
 343      if ($show_blame) {
 344        $blame_text = pht('Disable Blame');
 345        $blame_icon = 'fa-exclamation-circle lightgreytext';
 346        $blame_value = 0;
 347      } else {
 348        $blame_text = pht('Enable Blame');
 349        $blame_icon = 'fa-exclamation-circle';
 350        $blame_value = 1;
 351      }
 352  
 353      $view->addAction(
 354        id(new PhabricatorActionView())
 355          ->setName($blame_text)
 356          ->setHref($base_uri->alter('blame', $blame_value))
 357          ->setIcon($blame_icon)
 358          ->setUser($viewer)
 359          ->setRenderAsForm($viewer->isLoggedIn()));
 360  
 361      if ($show_color) {
 362        $highlight_text = pht('Disable Highlighting');
 363        $highlight_icon = 'fa-star-o grey';
 364        $highlight_value = 0;
 365      } else {
 366        $highlight_text = pht('Enable Highlighting');
 367        $highlight_icon = 'fa-star';
 368        $highlight_value = 1;
 369      }
 370  
 371      $view->addAction(
 372        id(new PhabricatorActionView())
 373          ->setName($highlight_text)
 374          ->setHref($base_uri->alter('color', $highlight_value))
 375          ->setIcon($highlight_icon)
 376          ->setUser($viewer)
 377          ->setRenderAsForm($viewer->isLoggedIn()));
 378  
 379      $href = null;
 380      if ($this->getRequest()->getStr('lint') !== null) {
 381        $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages));
 382        $href = $base_uri->alter('lint', null);
 383  
 384      } else if ($this->lintCommit === null) {
 385        $lint_text = pht('Lint not Available');
 386      } else {
 387        $lint_text = pht(
 388          'Show %d Lint Message(s)',
 389          count($this->lintMessages));
 390        $href = $this->getDiffusionRequest()->generateURI(array(
 391          'action' => 'browse',
 392          'commit' => $this->lintCommit,
 393        ))->alter('lint', '');
 394      }
 395  
 396      $view->addAction(
 397        id(new PhabricatorActionView())
 398          ->setName($lint_text)
 399          ->setHref($href)
 400          ->setIcon('fa-exclamation-triangle')
 401          ->setDisabled(!$href));
 402  
 403      return $view;
 404    }
 405  
 406    private function renderEditButton() {
 407      $request = $this->getRequest();
 408      $user = $request->getUser();
 409  
 410      $drequest = $this->getDiffusionRequest();
 411  
 412      $repository = $drequest->getRepository();
 413      $path = $drequest->getPath();
 414      $line = nonempty((int)$drequest->getLine(), 1);
 415  
 416      $callsign = $repository->getCallsign();
 417      $editor_link = $user->loadEditorLink($path, $line, $callsign);
 418      $template = $user->loadEditorLink($path, '%l', $callsign);
 419  
 420      $icon_edit = id(new PHUIIconView())
 421        ->setIconFont('fa-pencil');
 422      $button = id(new PHUIButtonView())
 423        ->setTag('a')
 424        ->setText(pht('Open in Editor'))
 425        ->setHref($editor_link)
 426        ->setIcon($icon_edit)
 427        ->setID('editor_link')
 428        ->setMetadata(array('link_template' => $template))
 429        ->setDisabled(!$editor_link);
 430  
 431      return $button;
 432    }
 433  
 434    private function renderFileButton($file_uri = null) {
 435  
 436      $base_uri = $this->getRequest()->getRequestURI();
 437  
 438      if ($file_uri) {
 439        $text = pht('Download Raw File');
 440        $href = $file_uri;
 441        $icon = 'fa-download';
 442      } else {
 443        $text = pht('View Raw File');
 444        $href = $base_uri->alter('view', 'raw');
 445        $icon = 'fa-file-text';
 446      }
 447  
 448      $iconview = id(new PHUIIconView())
 449        ->setIconFont($icon);
 450      $button = id(new PHUIButtonView())
 451        ->setTag('a')
 452        ->setText($text)
 453        ->setHref($href)
 454        ->setIcon($iconview);
 455  
 456      return $button;
 457    }
 458  
 459  
 460    private function buildDisplayRows(
 461      array $text_list,
 462      array $rev_list,
 463      array $blame_dict,
 464      $needs_blame,
 465      DiffusionRequest $drequest,
 466      $show_blame,
 467      $show_color) {
 468  
 469      $handles = array();
 470      if ($blame_dict) {
 471        $epoch_list  = ipull(ifilter($blame_dict, 'epoch'), 'epoch');
 472        $epoch_min   = min($epoch_list);
 473        $epoch_max   = max($epoch_list);
 474        $epoch_range = ($epoch_max - $epoch_min) + 1;
 475  
 476        $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID');
 477        $handles = $this->loadViewerHandles($author_phids);
 478      }
 479  
 480      $line_arr = array();
 481      $line_str = $drequest->getLine();
 482      $ranges = explode(',', $line_str);
 483      foreach ($ranges as $range) {
 484        if (strpos($range, '-') !== false) {
 485          list($min, $max) = explode('-', $range, 2);
 486          $line_arr[] = array(
 487            'min' => min($min, $max),
 488            'max' => max($min, $max),
 489          );
 490        } else if (strlen($range)) {
 491          $line_arr[] = array(
 492            'min' => $range,
 493            'max' => $range,
 494          );
 495        }
 496      }
 497  
 498      $display = array();
 499  
 500      $line_number = 1;
 501      $last_rev = null;
 502      $color = null;
 503      foreach ($text_list as $k => $line) {
 504        $display_line = array(
 505          'epoch'       => null,
 506          'commit'      => null,
 507          'author'      => null,
 508          'target'      => null,
 509          'highlighted' => null,
 510          'line'        => $line_number,
 511          'data'        => $line,
 512        );
 513  
 514        if ($show_blame) {
 515          // If the line's rev is same as the line above, show empty content
 516          // with same color; otherwise generate blame info. The newer a change
 517          // is, the more saturated the color.
 518  
 519          $rev = idx($rev_list, $k, $last_rev);
 520  
 521          if ($last_rev == $rev) {
 522            $display_line['color'] = $color;
 523          } else {
 524            $blame = $blame_dict[$rev];
 525  
 526            if (!isset($blame['epoch'])) {
 527              $color = '#ffd'; // Render as warning.
 528            } else {
 529              $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range;
 530              $color_value = 0xE6 * (1.0 - $color_ratio);
 531              $color = sprintf(
 532                '#%02x%02x%02x',
 533                $color_value,
 534                0xF6,
 535                $color_value);
 536            }
 537  
 538            $display_line['epoch'] = idx($blame, 'epoch');
 539            $display_line['color'] = $color;
 540            $display_line['commit'] = $rev;
 541  
 542            $author_phid = idx($blame, 'authorPHID');
 543            if ($author_phid && $handles[$author_phid]) {
 544              $author_link = $handles[$author_phid]->renderLink();
 545            } else {
 546              $author_link = $blame['author'];
 547            }
 548            $display_line['author'] = $author_link;
 549  
 550            $last_rev = $rev;
 551          }
 552        }
 553  
 554        if ($line_arr) {
 555          if ($line_number == $line_arr[0]['min']) {
 556            $display_line['target'] = true;
 557          }
 558          foreach ($line_arr as $range) {
 559            if ($line_number >= $range['min'] &&
 560                $line_number <= $range['max']) {
 561              $display_line['highlighted'] = true;
 562            }
 563          }
 564        }
 565  
 566        $display[] = $display_line;
 567        ++$line_number;
 568      }
 569  
 570      $request = $this->getRequest();
 571      $viewer = $request->getUser();
 572  
 573      $commits = array_filter(ipull($display, 'commit'));
 574      if ($commits) {
 575        $commits = id(new DiffusionCommitQuery())
 576          ->setViewer($viewer)
 577          ->withRepository($drequest->getRepository())
 578          ->withIdentifiers($commits)
 579          ->execute();
 580        $commits = mpull($commits, null, 'getCommitIdentifier');
 581      }
 582  
 583      $revision_ids = id(new DifferentialRevision())
 584        ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID'));
 585      $revisions = array();
 586      if ($revision_ids) {
 587        $revisions = id(new DifferentialRevisionQuery())
 588          ->setViewer($viewer)
 589          ->withIDs($revision_ids)
 590          ->execute();
 591      }
 592  
 593      $phids = array();
 594      foreach ($commits as $commit) {
 595        if ($commit->getAuthorPHID()) {
 596          $phids[] = $commit->getAuthorPHID();
 597        }
 598      }
 599      foreach ($revisions as $revision) {
 600        if ($revision->getAuthorPHID()) {
 601          $phids[] = $revision->getAuthorPHID();
 602        }
 603      }
 604      $handles = $this->loadViewerHandles($phids);
 605  
 606      Javelin::initBehavior('phabricator-oncopy', array());
 607  
 608      $engine = null;
 609      $inlines = array();
 610      if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) {
 611        $engine = new PhabricatorMarkupEngine();
 612        $engine->setViewer($viewer);
 613  
 614        foreach ($this->lintMessages as $message) {
 615          $inline = id(new PhabricatorAuditInlineComment())
 616            ->setSyntheticAuthor(
 617              ArcanistLintSeverity::getStringForSeverity($message['severity']).
 618              ' '.$message['code'].' ('.$message['name'].')')
 619            ->setLineNumber($message['line'])
 620            ->setContent($message['description']);
 621          $inlines[$message['line']][] = $inline;
 622  
 623          $engine->addObject(
 624            $inline,
 625            PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
 626        }
 627  
 628        $engine->process();
 629        require_celerity_resource('differential-changeset-view-css');
 630      }
 631  
 632      $rows = $this->renderInlines(
 633        idx($inlines, 0, array()),
 634        $show_blame,
 635        (bool)$this->coverage,
 636        $engine);
 637  
 638      foreach ($display as $line) {
 639  
 640        $line_href = $drequest->generateURI(
 641          array(
 642            'action'  => 'browse',
 643            'line'    => $line['line'],
 644            'stable'  => true,
 645          ));
 646  
 647        $blame = array();
 648        $style = null;
 649        if (array_key_exists('color', $line)) {
 650          if ($line['color']) {
 651            $style = 'background: '.$line['color'].';';
 652          }
 653  
 654          $before_link = null;
 655          $commit_link = null;
 656          $revision_link = null;
 657          if (idx($line, 'commit')) {
 658            $commit = $line['commit'];
 659  
 660            if (idx($commits, $commit)) {
 661              $tooltip = $this->renderCommitTooltip(
 662                $commits[$commit],
 663                $handles,
 664                $line['author']);
 665            } else {
 666              $tooltip = null;
 667            }
 668  
 669            Javelin::initBehavior('phabricator-tooltips', array());
 670            require_celerity_resource('aphront-tooltip-css');
 671  
 672            $commit_link = javelin_tag(
 673              'a',
 674              array(
 675                'href' => $drequest->generateURI(
 676                  array(
 677                    'action' => 'commit',
 678                    'commit' => $line['commit'],
 679                  )),
 680                'sigil' => 'has-tooltip',
 681                'meta'  => array(
 682                  'tip'   => $tooltip,
 683                  'align' => 'E',
 684                  'size'  => 600,
 685                ),
 686              ),
 687              id(new PhutilUTF8StringTruncator())
 688              ->setMaximumGlyphs(9)
 689              ->setTerminator('')
 690              ->truncateString($line['commit']));
 691  
 692            $revision_id = null;
 693            if (idx($commits, $commit)) {
 694              $revision_id = idx($revision_ids, $commits[$commit]->getPHID());
 695            }
 696  
 697            if ($revision_id) {
 698              $revision = idx($revisions, $revision_id);
 699              if ($revision) {
 700                $tooltip = $this->renderRevisionTooltip($revision, $handles);
 701                $revision_link = javelin_tag(
 702                  'a',
 703                  array(
 704                    'href' => '/D'.$revision->getID(),
 705                    'sigil' => 'has-tooltip',
 706                    'meta'  => array(
 707                      'tip'   => $tooltip,
 708                      'align' => 'E',
 709                      'size'  => 600,
 710                    ),
 711                  ),
 712                  'D'.$revision->getID());
 713              }
 714            }
 715  
 716            $uri = $line_href->alter('before', $commit);
 717            $before_link = javelin_tag(
 718              'a',
 719              array(
 720                'href'  => $uri->setQueryParam('view', 'blame'),
 721                'sigil' => 'has-tooltip',
 722                'meta'  => array(
 723                  'tip'     => pht('Skip Past This Commit'),
 724                  'align'   => 'E',
 725                  'size'    => 300,
 726                ),
 727              ),
 728              "\xC2\xAB");
 729          }
 730  
 731          $blame[] = phutil_tag(
 732            'th',
 733            array(
 734              'class' => 'diffusion-blame-link',
 735            ),
 736            $before_link);
 737  
 738          $object_links = array();
 739          $object_links[] = $commit_link;
 740          if ($revision_link) {
 741            $object_links[] = phutil_tag('span', array(), '/');
 742            $object_links[] = $revision_link;
 743          }
 744  
 745          $blame[] = phutil_tag(
 746            'th',
 747            array(
 748              'class' => 'diffusion-rev-link',
 749            ),
 750            $object_links);
 751        }
 752  
 753        $line_link = phutil_tag(
 754          'a',
 755          array(
 756            'href' => $line_href,
 757            'style' => $style,
 758          ),
 759          $line['line']);
 760  
 761        $blame[] = javelin_tag(
 762          'th',
 763          array(
 764            'class' => 'diffusion-line-link',
 765            'sigil' => 'phabricator-source-line',
 766            'style' => $style,
 767          ),
 768          $line_link);
 769  
 770        Javelin::initBehavior('phabricator-line-linker');
 771  
 772        if ($line['target']) {
 773          Javelin::initBehavior(
 774            'diffusion-jump-to',
 775            array(
 776              'target' => 'scroll_target',
 777            ));
 778          $anchor_text = phutil_tag(
 779            'a',
 780            array(
 781              'id' => 'scroll_target',
 782            ),
 783            '');
 784        } else {
 785          $anchor_text = null;
 786        }
 787  
 788        $blame[] = phutil_tag(
 789          'td',
 790          array(
 791          ),
 792          array(
 793            $anchor_text,
 794  
 795            // NOTE: See phabricator-oncopy behavior.
 796            "\xE2\x80\x8B",
 797  
 798            // TODO: [HTML] Not ideal.
 799            phutil_safe_html(str_replace("\t", '  ', $line['data'])),
 800          ));
 801  
 802        if ($this->coverage) {
 803          require_celerity_resource('differential-changeset-view-css');
 804          $cov_index = $line['line'] - 1;
 805  
 806          if (isset($this->coverage[$cov_index])) {
 807            $cov_class = $this->coverage[$cov_index];
 808          } else {
 809            $cov_class = 'N';
 810          }
 811  
 812          $blame[] = phutil_tag(
 813            'td',
 814            array(
 815              'class' => 'cov cov-'.$cov_class,
 816            ),
 817            '');
 818        }
 819  
 820        $rows[] = phutil_tag(
 821          'tr',
 822          array(
 823            'class' => ($line['highlighted'] ?
 824                        'phabricator-source-highlight' :
 825                        null),
 826          ),
 827          $blame);
 828  
 829        $cur_inlines = $this->renderInlines(
 830          idx($inlines, $line['line'], array()),
 831          $show_blame,
 832          $this->coverage,
 833          $engine);
 834        foreach ($cur_inlines as $cur_inline) {
 835          $rows[] = $cur_inline;
 836        }
 837      }
 838  
 839      return $rows;
 840    }
 841  
 842    private function renderInlines(
 843      array $inlines,
 844      $needs_blame,
 845      $has_coverage,
 846      $engine) {
 847  
 848      $rows = array();
 849      foreach ($inlines as $inline) {
 850        $inline_view = id(new DifferentialInlineCommentView())
 851          ->setMarkupEngine($engine)
 852          ->setInlineComment($inline)
 853          ->render();
 854  
 855        $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th'));
 856  
 857        $row[] = phutil_tag('td', array(), $inline_view);
 858  
 859        if ($has_coverage) {
 860          $row[] = phutil_tag(
 861            'td',
 862            array(
 863              'class' => 'cov cov-I',
 864            ));
 865        }
 866  
 867        $rows[] = phutil_tag('tr', array('class' => 'inline'), $row);
 868      }
 869  
 870      return $rows;
 871    }
 872  
 873    private function loadFileForData($path, $data) {
 874      $file = PhabricatorFile::buildFromFileDataOrHash(
 875        $data,
 876        array(
 877          'name' => basename($path),
 878          'ttl' => time() + 60 * 60 * 24,
 879          'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
 880        ));
 881  
 882      $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 883        $file->attachToObject(
 884          $this->getDiffusionRequest()->getRepository()->getPHID());
 885      unset($unguarded);
 886  
 887      return $file;
 888    }
 889  
 890    private function buildRawResponse($path, $data) {
 891      $file = $this->loadFileForData($path, $data);
 892      return $file->getRedirectResponse();
 893    }
 894  
 895    private function buildImageCorpus($file_uri) {
 896      $properties = new PHUIPropertyListView();
 897  
 898      $properties->addImageContent(
 899        phutil_tag(
 900          'img',
 901          array(
 902            'src' => $file_uri,
 903          )));
 904  
 905      $file = $this->renderFileButton($file_uri);
 906      $header = id(new PHUIHeaderView())
 907        ->setHeader(pht('Image'))
 908        ->addActionLink($file);
 909  
 910      return id(new PHUIObjectBoxView())
 911        ->setHeader($header)
 912        ->addPropertyList($properties);
 913    }
 914  
 915    private function buildBinaryCorpus($file_uri, $data) {
 916  
 917      $size = new PhutilNumber(strlen($data));
 918      $text = pht('This is a binary file. It is %s byte(s) in length.', $size);
 919      $text = id(new PHUIBoxView())
 920        ->addPadding(PHUI::PADDING_LARGE)
 921        ->appendChild($text);
 922  
 923      $file = $this->renderFileButton($file_uri);
 924      $header = id(new PHUIHeaderView())
 925        ->setHeader(pht('Details'))
 926        ->addActionLink($file);
 927  
 928      $box = id(new PHUIObjectBoxView())
 929        ->setHeader($header)
 930        ->appendChild($text);
 931  
 932      return $box;
 933    }
 934  
 935    private function buildBeforeResponse($before) {
 936      $request = $this->getRequest();
 937      $drequest = $this->getDiffusionRequest();
 938  
 939      // NOTE: We need to get the grandparent so we can capture filename changes
 940      // in the parent.
 941  
 942      $parent = $this->loadParentCommitOf($before);
 943      $old_filename = null;
 944      $was_created = false;
 945      if ($parent) {
 946        $grandparent = $this->loadParentCommitOf($parent);
 947  
 948        if ($grandparent) {
 949          $rename_query = new DiffusionRenameHistoryQuery();
 950          $rename_query->setRequest($drequest);
 951          $rename_query->setOldCommit($grandparent);
 952          $rename_query->setViewer($request->getUser());
 953          $old_filename = $rename_query->loadOldFilename();
 954          $was_created = $rename_query->getWasCreated();
 955        }
 956      }
 957  
 958      $follow = null;
 959      if ($was_created) {
 960        // If the file was created in history, that means older commits won't
 961        // have it. Since we know it existed at 'before', it must have been
 962        // created then; jump there.
 963        $target_commit = $before;
 964        $follow = 'created';
 965      } else if ($parent) {
 966        // If we found a parent, jump to it. This is the normal case.
 967        $target_commit = $parent;
 968      } else {
 969        // If there's no parent, this was probably created in the initial commit?
 970        // And the "was_created" check will fail because we can't identify the
 971        // grandparent. Keep the user at 'before'.
 972        $target_commit = $before;
 973        $follow = 'first';
 974      }
 975  
 976      $path = $drequest->getPath();
 977      $renamed = null;
 978      if ($old_filename !== null &&
 979          $old_filename !== '/'.$path) {
 980        $renamed = $path;
 981        $path = $old_filename;
 982      }
 983  
 984      $line = null;
 985      // If there's a follow error, drop the line so the user sees the message.
 986      if (!$follow) {
 987        $line = $this->getBeforeLineNumber($target_commit);
 988      }
 989  
 990      $before_uri = $drequest->generateURI(
 991        array(
 992          'action'    => 'browse',
 993          'commit'    => $target_commit,
 994          'line'      => $line,
 995          'path'      => $path,
 996        ));
 997  
 998      $before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
 999      $before_uri = $before_uri->alter('before', null);
1000      $before_uri = $before_uri->alter('renamed', $renamed);
1001      $before_uri = $before_uri->alter('follow', $follow);
1002  
1003      return id(new AphrontRedirectResponse())->setURI($before_uri);
1004    }
1005  
1006    private function getBeforeLineNumber($target_commit) {
1007      $drequest = $this->getDiffusionRequest();
1008  
1009      $line = $drequest->getLine();
1010      if (!$line) {
1011        return null;
1012      }
1013  
1014      $raw_diff = $this->callConduitWithDiffusionRequest(
1015        'diffusion.rawdiffquery',
1016        array(
1017          'commit' => $drequest->getCommit(),
1018          'path' => $drequest->getPath(),
1019          'againstCommit' => $target_commit,
1020        ));
1021      $old_line = 0;
1022      $new_line = 0;
1023  
1024      foreach (explode("\n", $raw_diff) as $text) {
1025        if ($text[0] == '-' || $text[0] == ' ') {
1026          $old_line++;
1027        }
1028        if ($text[0] == '+' || $text[0] == ' ') {
1029          $new_line++;
1030        }
1031        if ($new_line == $line) {
1032          return $old_line;
1033        }
1034      }
1035  
1036      // We didn't find the target line.
1037      return $line;
1038    }
1039  
1040    private function loadParentCommitOf($commit) {
1041      $drequest = $this->getDiffusionRequest();
1042      $user = $this->getRequest()->getUser();
1043  
1044      $before_req = DiffusionRequest::newFromDictionary(
1045        array(
1046          'user' => $user,
1047          'repository' => $drequest->getRepository(),
1048          'commit' => $commit,
1049        ));
1050  
1051      $parents = DiffusionQuery::callConduitWithDiffusionRequest(
1052        $user,
1053        $before_req,
1054        'diffusion.commitparentsquery',
1055        array(
1056          'commit' => $commit,
1057        ));
1058  
1059      return head($parents);
1060    }
1061  
1062    private function renderRevisionTooltip(
1063      DifferentialRevision $revision,
1064      array $handles) {
1065      $viewer = $this->getRequest()->getUser();
1066  
1067      $date = phabricator_date($revision->getDateModified(), $viewer);
1068      $id = $revision->getID();
1069      $title = $revision->getTitle();
1070      $header = "D{$id} {$title}";
1071  
1072      $author = $handles[$revision->getAuthorPHID()]->getName();
1073  
1074      return "{$header}\n{$date} \xC2\xB7 {$author}";
1075    }
1076  
1077    private function renderCommitTooltip(
1078      PhabricatorRepositoryCommit $commit,
1079      array $handles,
1080      $author) {
1081  
1082      $viewer = $this->getRequest()->getUser();
1083  
1084      $date = phabricator_date($commit->getEpoch(), $viewer);
1085      $summary = trim($commit->getSummary());
1086  
1087      if ($commit->getAuthorPHID()) {
1088        $author = $handles[$commit->getAuthorPHID()]->getName();
1089      }
1090  
1091      return "{$summary}\n{$date} \xC2\xB7 {$author}";
1092    }
1093  
1094  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1