[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/differential/controller/ -> DifferentialRevisionViewController.php (source)

   1  <?php
   2  
   3  final class DifferentialRevisionViewController extends DifferentialController {
   4  
   5    private $revisionID;
   6  
   7    public function shouldAllowPublic() {
   8      return true;
   9    }
  10  
  11    public function willProcessRequest(array $data) {
  12      $this->revisionID = $data['id'];
  13    }
  14  
  15    public function processRequest() {
  16  
  17      $request = $this->getRequest();
  18      $user = $request->getUser();
  19      $viewer_is_anonymous = !$user->isLoggedIn();
  20  
  21      $revision = id(new DifferentialRevisionQuery())
  22        ->withIDs(array($this->revisionID))
  23        ->setViewer($request->getUser())
  24        ->needRelationships(true)
  25        ->needReviewerStatus(true)
  26        ->needReviewerAuthority(true)
  27        ->executeOne();
  28  
  29      if (!$revision) {
  30        return new Aphront404Response();
  31      }
  32  
  33      $diffs = id(new DifferentialDiffQuery())
  34        ->setViewer($request->getUser())
  35        ->withRevisionIDs(array($this->revisionID))
  36        ->needArcanistProjects(true)
  37        ->execute();
  38      $diffs = array_reverse($diffs, $preserve_keys = true);
  39  
  40      if (!$diffs) {
  41        throw new Exception(
  42          'This revision has no diffs. Something has gone quite wrong.');
  43      }
  44  
  45      $revision->attachActiveDiff(last($diffs));
  46  
  47      $diff_vs = $request->getInt('vs');
  48  
  49      $target_id = $request->getInt('id');
  50      $target = idx($diffs, $target_id, end($diffs));
  51  
  52      $target_manual = $target;
  53      if (!$target_id) {
  54        foreach ($diffs as $diff) {
  55          if ($diff->getCreationMethod() != 'commit') {
  56            $target_manual = $diff;
  57          }
  58        }
  59      }
  60  
  61      if (empty($diffs[$diff_vs])) {
  62        $diff_vs = null;
  63      }
  64  
  65      $repository = null;
  66      $repository_phid = $target->getRepositoryPHID();
  67      if ($repository_phid) {
  68        if ($repository_phid == $revision->getRepositoryPHID()) {
  69          $repository = $revision->getRepository();
  70        } else {
  71          $repository = id(new PhabricatorRepositoryQuery())
  72            ->setViewer($user)
  73            ->withPHIDs(array($repository_phid))
  74            ->executeOne();
  75        }
  76      }
  77  
  78      list($changesets, $vs_map, $vs_changesets, $rendering_references) =
  79        $this->loadChangesetsAndVsMap(
  80          $target,
  81          idx($diffs, $diff_vs),
  82          $repository);
  83  
  84      if ($request->getExists('download')) {
  85        return $this->buildRawDiffResponse(
  86          $revision,
  87          $changesets,
  88          $vs_changesets,
  89          $vs_map,
  90          $repository);
  91      }
  92  
  93      $props = id(new DifferentialDiffProperty())->loadAllWhere(
  94        'diffID = %d',
  95        $target_manual->getID());
  96      $props = mpull($props, 'getData', 'getName');
  97  
  98      $all_changesets = $changesets;
  99      $inlines = $this->loadInlineComments(
 100        $revision,
 101        $all_changesets);
 102  
 103      $object_phids = array_merge(
 104        $revision->getReviewers(),
 105        $revision->getCCPHIDs(),
 106        $revision->loadCommitPHIDs(),
 107        array(
 108          $revision->getAuthorPHID(),
 109          $user->getPHID(),
 110        ));
 111  
 112      foreach ($revision->getAttached() as $type => $phids) {
 113        foreach ($phids as $phid => $info) {
 114          $object_phids[] = $phid;
 115        }
 116      }
 117  
 118      $field_list = PhabricatorCustomField::getObjectFields(
 119        $revision,
 120        PhabricatorCustomField::ROLE_VIEW);
 121  
 122      $field_list->setViewer($user);
 123      $field_list->readFieldsFromStorage($revision);
 124  
 125      $warning_handle_map = array();
 126      foreach ($field_list->getFields() as $key => $field) {
 127        $req = $field->getRequiredHandlePHIDsForRevisionHeaderWarnings();
 128        foreach ($req as $phid) {
 129          $warning_handle_map[$key][] = $phid;
 130          $object_phids[] = $phid;
 131        }
 132      }
 133  
 134      $handles = $this->loadViewerHandles($object_phids);
 135  
 136      $request_uri = $request->getRequestURI();
 137  
 138      $limit = 100;
 139      $large = $request->getStr('large');
 140      if (count($changesets) > $limit && !$large) {
 141        $count = count($changesets);
 142        $warning = new AphrontErrorView();
 143        $warning->setTitle('Very Large Diff');
 144        $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
 145        $warning->appendChild(hsprintf(
 146          '%s <strong>%s</strong>',
 147          pht(
 148            'This diff is very large and affects %s files. '.
 149            'You may load each file individually or ',
 150            new PhutilNumber($count)),
 151          phutil_tag(
 152            'a',
 153            array(
 154              'class' => 'button grey',
 155              'href' => $request_uri
 156                ->alter('large', 'true')
 157                ->setFragment('toc'),
 158            ),
 159            pht('Show All Files Inline'))));
 160        $warning = $warning->render();
 161  
 162        $my_inlines = id(new DifferentialInlineCommentQuery())
 163          ->withDraftComments($user->getPHID(), $this->revisionID)
 164          ->execute();
 165  
 166        $visible_changesets = array();
 167        foreach ($inlines + $my_inlines as $inline) {
 168          $changeset_id = $inline->getChangesetID();
 169          if (isset($changesets[$changeset_id])) {
 170            $visible_changesets[$changeset_id] = $changesets[$changeset_id];
 171          }
 172        }
 173  
 174        if (!empty($props['arc:lint'])) {
 175          $changeset_paths = mpull($changesets, null, 'getFilename');
 176          foreach ($props['arc:lint'] as $lint) {
 177            $changeset = idx($changeset_paths, $lint['path']);
 178            if ($changeset) {
 179              $visible_changesets[$changeset->getID()] = $changeset;
 180            }
 181          }
 182        }
 183      } else {
 184        $warning = null;
 185        $visible_changesets = $changesets;
 186      }
 187  
 188  
 189      // TODO: This should be in a DiffQuery or similar.
 190      $need_props = array();
 191      foreach ($field_list->getFields() as $field) {
 192        foreach ($field->getRequiredDiffPropertiesForRevisionView() as $prop) {
 193          $need_props[$prop] = $prop;
 194        }
 195      }
 196  
 197      if ($need_props) {
 198        $prop_diff = $revision->getActiveDiff();
 199        $load_props = id(new DifferentialDiffProperty())->loadAllWhere(
 200          'diffID = %d AND name IN (%Ls)',
 201          $prop_diff->getID(),
 202          $need_props);
 203        $load_props = mpull($load_props, 'getData', 'getName');
 204        foreach ($need_props as $need) {
 205          $prop_diff->attachProperty($need, idx($load_props, $need));
 206        }
 207      }
 208  
 209      $commit_hashes = mpull($diffs, 'getSourceControlBaseRevision');
 210      $local_commits = idx($props, 'local:commits', array());
 211      foreach ($local_commits as $local_commit) {
 212        $commit_hashes[] = idx($local_commit, 'tree');
 213        $commit_hashes[] = idx($local_commit, 'local');
 214      }
 215      $commit_hashes = array_unique(array_filter($commit_hashes));
 216      if ($commit_hashes) {
 217        $commits_for_links = id(new DiffusionCommitQuery())
 218          ->setViewer($user)
 219          ->withIdentifiers($commit_hashes)
 220          ->execute();
 221        $commits_for_links = mpull(
 222          $commits_for_links,
 223          null,
 224          'getCommitIdentifier');
 225      } else {
 226        $commits_for_links = array();
 227      }
 228  
 229      $revision_detail = id(new DifferentialRevisionDetailView())
 230        ->setUser($user)
 231        ->setRevision($revision)
 232        ->setDiff(end($diffs))
 233        ->setCustomFields($field_list)
 234        ->setURI($request->getRequestURI());
 235  
 236      $actions = $this->getRevisionActions($revision);
 237  
 238      $whitespace = $request->getStr(
 239        'whitespace',
 240        DifferentialChangesetParser::WHITESPACE_IGNORE_ALL);
 241  
 242      $arc_project = $target->getArcanistProject();
 243      if ($arc_project) {
 244        list($symbol_indexes, $project_phids) = $this->buildSymbolIndexes(
 245          $arc_project,
 246          $visible_changesets);
 247      } else {
 248        $symbol_indexes = array();
 249        $project_phids = null;
 250      }
 251  
 252      $revision_detail->setActions($actions);
 253      $revision_detail->setUser($user);
 254  
 255      $revision_detail_box = $revision_detail->render();
 256  
 257      $revision_warnings = $this->buildRevisionWarnings(
 258        $revision,
 259        $field_list,
 260        $warning_handle_map,
 261        $handles);
 262      if ($revision_warnings) {
 263        $revision_warnings = id(new AphrontErrorView())
 264          ->setSeverity(AphrontErrorView::SEVERITY_WARNING)
 265          ->setErrors($revision_warnings);
 266        $revision_detail_box->setErrorView($revision_warnings);
 267      }
 268  
 269      $comment_view = $this->buildTransactions(
 270        $revision,
 271        $diff_vs ? $diffs[$diff_vs] : $target,
 272        $target,
 273        $all_changesets);
 274  
 275      if (!$viewer_is_anonymous) {
 276        $comment_view->setQuoteRef('D'.$revision->getID());
 277        $comment_view->setQuoteTargetID('comment-content');
 278      }
 279  
 280      $wrap_id = celerity_generate_unique_node_id();
 281      $comment_view = phutil_tag(
 282        'div',
 283        array(
 284          'id' => $wrap_id,
 285        ),
 286        $comment_view);
 287  
 288      if ($arc_project) {
 289        Javelin::initBehavior(
 290          'repository-crossreference',
 291          array(
 292            'section' => $wrap_id,
 293            'projects' => $project_phids,
 294          ));
 295      }
 296  
 297      $changeset_view = new DifferentialChangesetListView();
 298      $changeset_view->setChangesets($changesets);
 299      $changeset_view->setVisibleChangesets($visible_changesets);
 300  
 301      if (!$viewer_is_anonymous) {
 302        $changeset_view->setInlineCommentControllerURI(
 303          '/differential/comment/inline/edit/'.$revision->getID().'/');
 304      }
 305  
 306      $changeset_view->setStandaloneURI('/differential/changeset/');
 307      $changeset_view->setRawFileURIs(
 308        '/differential/changeset/?view=old',
 309        '/differential/changeset/?view=new');
 310  
 311      $changeset_view->setUser($user);
 312      $changeset_view->setDiff($target);
 313      $changeset_view->setRenderingReferences($rendering_references);
 314      $changeset_view->setVsMap($vs_map);
 315      $changeset_view->setWhitespace($whitespace);
 316      if ($repository) {
 317        $changeset_view->setRepository($repository);
 318      }
 319      $changeset_view->setSymbolIndexes($symbol_indexes);
 320      $changeset_view->setTitle('Diff '.$target->getID());
 321  
 322      $diff_history = id(new DifferentialRevisionUpdateHistoryView())
 323        ->setUser($user)
 324        ->setDiffs($diffs)
 325        ->setSelectedVersusDiffID($diff_vs)
 326        ->setSelectedDiffID($target->getID())
 327        ->setSelectedWhitespace($whitespace)
 328        ->setCommitsForLinks($commits_for_links);
 329  
 330      $local_view = id(new DifferentialLocalCommitsView())
 331        ->setUser($user)
 332        ->setLocalCommits(idx($props, 'local:commits'))
 333        ->setCommitsForLinks($commits_for_links);
 334  
 335      if ($repository) {
 336        $other_revisions = $this->loadOtherRevisions(
 337          $changesets,
 338          $target,
 339          $repository);
 340      } else {
 341        $other_revisions = array();
 342      }
 343  
 344      $other_view = null;
 345      if ($other_revisions) {
 346        $other_view = $this->renderOtherRevisions($other_revisions);
 347      }
 348  
 349      $toc_view = new DifferentialDiffTableOfContentsView();
 350      $toc_view->setChangesets($changesets);
 351      $toc_view->setVisibleChangesets($visible_changesets);
 352      $toc_view->setRenderingReferences($rendering_references);
 353      $toc_view->setUnitTestData(idx($props, 'arc:unit', array()));
 354      if ($repository) {
 355        $toc_view->setRepository($repository);
 356      }
 357      $toc_view->setDiff($target);
 358      $toc_view->setUser($user);
 359      $toc_view->setRevisionID($revision->getID());
 360      $toc_view->setWhitespace($whitespace);
 361  
 362      $comment_form = null;
 363      if (!$viewer_is_anonymous) {
 364        $draft = id(new PhabricatorDraft())->loadOneWhere(
 365          'authorPHID = %s AND draftKey = %s',
 366          $user->getPHID(),
 367          'differential-comment-'.$revision->getID());
 368  
 369        $reviewers = array();
 370        $ccs = array();
 371        if ($draft) {
 372          $reviewers = idx($draft->getMetadata(), 'reviewers', array());
 373          $ccs = idx($draft->getMetadata(), 'ccs', array());
 374          if ($reviewers || $ccs) {
 375            $handles = $this->loadViewerHandles(array_merge($reviewers, $ccs));
 376            $reviewers = array_select_keys($handles, $reviewers);
 377            $ccs = array_select_keys($handles, $ccs);
 378          }
 379        }
 380  
 381        $comment_form = new DifferentialAddCommentView();
 382        $comment_form->setRevision($revision);
 383  
 384        $review_warnings = array();
 385        foreach ($field_list->getFields() as $field) {
 386          $review_warnings[] = $field->getWarningsForDetailView();
 387        }
 388        $review_warnings = array_mergev($review_warnings);
 389  
 390        if ($review_warnings) {
 391          $review_warnings_panel = id(new AphrontErrorView())
 392            ->setSeverity(AphrontErrorView::SEVERITY_WARNING)
 393            ->setErrors($review_warnings);
 394          $comment_form->setErrorView($review_warnings_panel);
 395        }
 396  
 397        $comment_form->setActions($this->getRevisionCommentActions($revision));
 398        $action_uri = $this->getApplicationURI(
 399          'comment/save/'.$revision->getID().'/');
 400  
 401        $comment_form->setActionURI($action_uri);
 402        $comment_form->setUser($user);
 403        $comment_form->setDraft($draft);
 404        $comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'));
 405        $comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
 406  
 407        // TODO: This just makes the "Z" key work. Generalize this and remove
 408        // it at some point.
 409        $comment_form = phutil_tag(
 410          'div',
 411          array(
 412            'class' => 'differential-add-comment-panel',
 413          ),
 414          $comment_form);
 415      }
 416  
 417      $pane_id = celerity_generate_unique_node_id();
 418      Javelin::initBehavior(
 419        'differential-keyboard-navigation',
 420        array(
 421          'haunt' => $pane_id,
 422        ));
 423      Javelin::initBehavior('differential-user-select');
 424  
 425      $page_pane = id(new DifferentialPrimaryPaneView())
 426        ->setID($pane_id)
 427        ->appendChild(array(
 428          $comment_view,
 429          $diff_history,
 430          $warning,
 431          $local_view,
 432          $toc_view,
 433          $other_view,
 434          $changeset_view,
 435        ));
 436      if ($comment_form) {
 437  
 438        $page_pane->appendChild($comment_form);
 439      } else {
 440        // TODO: For now, just use this to get "Login to Comment".
 441        $page_pane->appendChild(
 442          id(new PhabricatorApplicationTransactionCommentView())
 443            ->setUser($user)
 444            ->setRequestURI($request->getRequestURI()));
 445      }
 446  
 447  
 448      $object_id = 'D'.$revision->getID();
 449  
 450      $top_anchor = id(new PhabricatorAnchorView())
 451        ->setAnchorName('top')
 452        ->setNavigationMarker(true);
 453  
 454      $content = array(
 455        $top_anchor,
 456        $revision_detail_box,
 457        $page_pane,
 458      );
 459  
 460      $crumbs = $this->buildApplicationCrumbs();
 461      $crumbs->addTextCrumb($object_id, '/'.$object_id);
 462      $crumbs->setActionList($revision_detail->getActionList());
 463  
 464      $prefs = $user->loadPreferences();
 465  
 466      $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
 467      if ($prefs->getPreference($pref_filetree)) {
 468        $collapsed = $prefs->getPreference(
 469          PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED,
 470          false);
 471  
 472        $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
 473          ->setAnchorName('top')
 474          ->setTitle('D'.$revision->getID())
 475          ->setBaseURI(new PhutilURI('/D'.$revision->getID()))
 476          ->setCollapsed((bool)$collapsed)
 477          ->build($changesets);
 478        $nav->appendChild($content);
 479        $nav->setCrumbs($crumbs);
 480        $content = $nav;
 481      } else {
 482        array_unshift($content, $crumbs);
 483      }
 484  
 485      return $this->buildApplicationPage(
 486        $content,
 487        array(
 488          'title' => $object_id.' '.$revision->getTitle(),
 489          'pageObjects' => array($revision->getPHID()),
 490        ));
 491    }
 492  
 493    private function getRevisionActions(DifferentialRevision $revision) {
 494      $viewer = $this->getRequest()->getUser();
 495      $revision_id = $revision->getID();
 496      $revision_phid = $revision->getPHID();
 497  
 498      $can_edit = PhabricatorPolicyFilter::hasCapability(
 499        $viewer,
 500        $revision,
 501        PhabricatorPolicyCapability::CAN_EDIT);
 502  
 503      $actions = array();
 504  
 505      $actions[] = id(new PhabricatorActionView())
 506        ->setIcon('fa-pencil')
 507        ->setHref("/differential/revision/edit/{$revision_id}/")
 508        ->setName(pht('Edit Revision'))
 509        ->setDisabled(!$can_edit)
 510        ->setWorkflow(!$can_edit);
 511  
 512      $this->requireResource('phabricator-object-selector-css');
 513      $this->requireResource('javelin-behavior-phabricator-object-selector');
 514  
 515      $actions[] = id(new PhabricatorActionView())
 516        ->setIcon('fa-link')
 517        ->setName(pht('Edit Dependencies'))
 518        ->setHref("/search/attach/{$revision_phid}/DREV/dependencies/")
 519        ->setWorkflow(true)
 520        ->setDisabled(!$can_edit);
 521  
 522      $maniphest = 'PhabricatorManiphestApplication';
 523      if (PhabricatorApplication::isClassInstalled($maniphest)) {
 524        $actions[] = id(new PhabricatorActionView())
 525          ->setIcon('fa-anchor')
 526          ->setName(pht('Edit Maniphest Tasks'))
 527          ->setHref("/search/attach/{$revision_phid}/TASK/")
 528          ->setWorkflow(true)
 529          ->setDisabled(!$can_edit);
 530      }
 531  
 532      $request_uri = $this->getRequest()->getRequestURI();
 533      $actions[] = id(new PhabricatorActionView())
 534        ->setIcon('fa-download')
 535        ->setName(pht('Download Raw Diff'))
 536        ->setHref($request_uri->alter('download', 'true'));
 537  
 538      return $actions;
 539    }
 540  
 541    private function getRevisionCommentActions(DifferentialRevision $revision) {
 542      $actions = array(
 543        DifferentialAction::ACTION_COMMENT => true,
 544      );
 545  
 546      $viewer = $this->getRequest()->getUser();
 547      $viewer_phid = $viewer->getPHID();
 548      $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
 549      $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
 550      $status = $revision->getStatus();
 551  
 552      $viewer_has_accepted = false;
 553      $viewer_has_rejected = false;
 554      $status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
 555      $status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
 556      foreach ($revision->getReviewerStatus() as $reviewer) {
 557        if ($reviewer->getReviewerPHID() == $viewer_phid) {
 558          if ($reviewer->getStatus() == $status_accepted) {
 559            $viewer_has_accepted = true;
 560          }
 561          if ($reviewer->getStatus() == $status_rejected) {
 562            $viewer_has_rejected = true;
 563          }
 564          break;
 565        }
 566      }
 567  
 568      $allow_self_accept = PhabricatorEnv::getEnvConfig(
 569        'differential.allow-self-accept');
 570      $always_allow_abandon = PhabricatorEnv::getEnvConfig(
 571        'differential.always-allow-abandon');
 572      $always_allow_close = PhabricatorEnv::getEnvConfig(
 573        'differential.always-allow-close');
 574      $allow_reopen = PhabricatorEnv::getEnvConfig(
 575        'differential.allow-reopen');
 576  
 577      if ($viewer_is_owner) {
 578        switch ($status) {
 579          case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
 580            $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
 581            $actions[DifferentialAction::ACTION_ABANDON] = true;
 582            $actions[DifferentialAction::ACTION_RETHINK] = true;
 583            break;
 584          case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
 585          case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
 586            $actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
 587            $actions[DifferentialAction::ACTION_ABANDON] = true;
 588            $actions[DifferentialAction::ACTION_REQUEST] = true;
 589            break;
 590          case ArcanistDifferentialRevisionStatus::ACCEPTED:
 591            $actions[DifferentialAction::ACTION_ABANDON] = true;
 592            $actions[DifferentialAction::ACTION_REQUEST] = true;
 593            $actions[DifferentialAction::ACTION_RETHINK] = true;
 594            $actions[DifferentialAction::ACTION_CLOSE] = true;
 595            break;
 596          case ArcanistDifferentialRevisionStatus::CLOSED:
 597            break;
 598          case ArcanistDifferentialRevisionStatus::ABANDONED:
 599            $actions[DifferentialAction::ACTION_RECLAIM] = true;
 600            break;
 601        }
 602      } else {
 603        switch ($status) {
 604          case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
 605            $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
 606            $actions[DifferentialAction::ACTION_ACCEPT] = true;
 607            $actions[DifferentialAction::ACTION_REJECT] = true;
 608            $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
 609            break;
 610          case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
 611          case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
 612            $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
 613            $actions[DifferentialAction::ACTION_ACCEPT] = true;
 614            $actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected;
 615            $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
 616            break;
 617          case ArcanistDifferentialRevisionStatus::ACCEPTED:
 618            $actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
 619            $actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted;
 620            $actions[DifferentialAction::ACTION_REJECT] = true;
 621            $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
 622            break;
 623          case ArcanistDifferentialRevisionStatus::CLOSED:
 624          case ArcanistDifferentialRevisionStatus::ABANDONED:
 625            break;
 626        }
 627        if ($status != ArcanistDifferentialRevisionStatus::CLOSED) {
 628          $actions[DifferentialAction::ACTION_CLAIM] = true;
 629          $actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close;
 630        }
 631      }
 632  
 633      $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
 634      $actions[DifferentialAction::ACTION_ADDCCS] = true;
 635      $actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen &&
 636        ($status == ArcanistDifferentialRevisionStatus::CLOSED);
 637  
 638      $actions = array_keys(array_filter($actions));
 639      $actions_dict = array();
 640      foreach ($actions as $action) {
 641        $actions_dict[$action] = DifferentialAction::getActionVerb($action);
 642      }
 643  
 644      return $actions_dict;
 645    }
 646  
 647    private function loadInlineComments(
 648      DifferentialRevision $revision,
 649      array &$changesets) {
 650      assert_instances_of($changesets, 'DifferentialChangeset');
 651  
 652      $inline_comments = array();
 653  
 654      $inline_comments = id(new DifferentialInlineCommentQuery())
 655        ->withRevisionIDs(array($revision->getID()))
 656        ->withNotDraft(true)
 657        ->execute();
 658  
 659      $load_changesets = array();
 660      foreach ($inline_comments as $inline) {
 661        $changeset_id = $inline->getChangesetID();
 662        if (isset($changesets[$changeset_id])) {
 663          continue;
 664        }
 665        $load_changesets[$changeset_id] = true;
 666      }
 667  
 668      $more_changesets = array();
 669      if ($load_changesets) {
 670        $changeset_ids = array_keys($load_changesets);
 671        $more_changesets += id(new DifferentialChangeset())
 672          ->loadAllWhere(
 673            'id IN (%Ld)',
 674            $changeset_ids);
 675      }
 676  
 677      if ($more_changesets) {
 678        $changesets += $more_changesets;
 679        $changesets = msort($changesets, 'getSortKey');
 680      }
 681  
 682      return $inline_comments;
 683    }
 684  
 685    private function loadChangesetsAndVsMap(
 686      DifferentialDiff $target,
 687      DifferentialDiff $diff_vs = null,
 688      PhabricatorRepository $repository = null) {
 689  
 690      $load_diffs = array($target);
 691      if ($diff_vs) {
 692        $load_diffs[] = $diff_vs;
 693      }
 694  
 695      $raw_changesets = id(new DifferentialChangesetQuery())
 696        ->setViewer($this->getRequest()->getUser())
 697        ->withDiffs($load_diffs)
 698        ->execute();
 699      $changeset_groups = mgroup($raw_changesets, 'getDiffID');
 700  
 701      $changesets = idx($changeset_groups, $target->getID(), array());
 702      $changesets = mpull($changesets, null, 'getID');
 703  
 704      $refs          = array();
 705      $vs_map        = array();
 706      $vs_changesets = array();
 707      if ($diff_vs) {
 708        $vs_id                  = $diff_vs->getID();
 709        $vs_changesets_path_map = array();
 710        foreach (idx($changeset_groups, $vs_id, array()) as $changeset) {
 711          $path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs);
 712          $vs_changesets_path_map[$path] = $changeset;
 713          $vs_changesets[$changeset->getID()] = $changeset;
 714        }
 715        foreach ($changesets as $key => $changeset) {
 716          $path = $changeset->getAbsoluteRepositoryPath($repository, $target);
 717          if (isset($vs_changesets_path_map[$path])) {
 718            $vs_map[$changeset->getID()] =
 719              $vs_changesets_path_map[$path]->getID();
 720            $refs[$changeset->getID()] =
 721              $changeset->getID().'/'.$vs_changesets_path_map[$path]->getID();
 722            unset($vs_changesets_path_map[$path]);
 723          } else {
 724            $refs[$changeset->getID()] = $changeset->getID();
 725          }
 726        }
 727        foreach ($vs_changesets_path_map as $path => $changeset) {
 728          $changesets[$changeset->getID()] = $changeset;
 729          $vs_map[$changeset->getID()]     = -1;
 730          $refs[$changeset->getID()]       = $changeset->getID().'/-1';
 731        }
 732      } else {
 733        foreach ($changesets as $changeset) {
 734          $refs[$changeset->getID()] = $changeset->getID();
 735        }
 736      }
 737  
 738      $changesets = msort($changesets, 'getSortKey');
 739  
 740      return array($changesets, $vs_map, $vs_changesets, $refs);
 741    }
 742  
 743    private function buildSymbolIndexes(
 744      PhabricatorRepositoryArcanistProject $arc_project,
 745      array $visible_changesets) {
 746      assert_instances_of($visible_changesets, 'DifferentialChangeset');
 747  
 748      $engine = PhabricatorSyntaxHighlighter::newEngine();
 749  
 750      $langs = $arc_project->getSymbolIndexLanguages();
 751      if (!$langs) {
 752        return array(array(), array());
 753      }
 754  
 755      $symbol_indexes = array();
 756  
 757      $project_phids = array_merge(
 758        array($arc_project->getPHID()),
 759        nonempty($arc_project->getSymbolIndexProjects(), array()));
 760  
 761      $indexed_langs = array_fill_keys($langs, true);
 762      foreach ($visible_changesets as $key => $changeset) {
 763        $lang = $engine->getLanguageFromFilename($changeset->getFilename());
 764        if (isset($indexed_langs[$lang])) {
 765          $symbol_indexes[$key] = array(
 766            'lang'      => $lang,
 767            'projects'  => $project_phids,
 768          );
 769        }
 770      }
 771  
 772      return array($symbol_indexes, $project_phids);
 773    }
 774  
 775    private function loadOtherRevisions(
 776      array $changesets,
 777      DifferentialDiff $target,
 778      PhabricatorRepository $repository) {
 779      assert_instances_of($changesets, 'DifferentialChangeset');
 780  
 781      $paths = array();
 782      foreach ($changesets as $changeset) {
 783        $paths[] = $changeset->getAbsoluteRepositoryPath(
 784          $repository,
 785          $target);
 786      }
 787  
 788      if (!$paths) {
 789        return array();
 790      }
 791  
 792      $path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
 793  
 794      if (!$path_map) {
 795        return array();
 796      }
 797  
 798      $query = id(new DifferentialRevisionQuery())
 799        ->setViewer($this->getRequest()->getUser())
 800        ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
 801        ->setOrder(DifferentialRevisionQuery::ORDER_PATH_MODIFIED)
 802        ->setLimit(10)
 803        ->needFlags(true)
 804        ->needDrafts(true)
 805        ->needRelationships(true);
 806  
 807      foreach ($path_map as $path => $path_id) {
 808        $query->withPath($repository->getID(), $path_id);
 809      }
 810  
 811      $results = $query->execute();
 812  
 813      // Strip out *this* revision.
 814      foreach ($results as $key => $result) {
 815        if ($result->getID() == $this->revisionID) {
 816          unset($results[$key]);
 817        }
 818      }
 819  
 820      return $results;
 821    }
 822  
 823    private function renderOtherRevisions(array $revisions) {
 824      assert_instances_of($revisions, 'DifferentialRevision');
 825  
 826      $user = $this->getRequest()->getUser();
 827  
 828      $view = id(new DifferentialRevisionListView())
 829        ->setRevisions($revisions)
 830        ->setUser($user);
 831  
 832      $phids = $view->getRequiredHandlePHIDs();
 833      $handles = $this->loadViewerHandles($phids);
 834      $view->setHandles($handles);
 835  
 836      return id(new PHUIObjectBoxView())
 837        ->setHeaderText(pht('Open Revisions Affecting These Files'))
 838        ->appendChild($view);
 839    }
 840  
 841  
 842    /**
 843     * Note this code is somewhat similar to the buildPatch method in
 844     * @{class:DifferentialReviewRequestMail}.
 845     *
 846     * @return @{class:AphrontRedirectResponse}
 847     */
 848    private function buildRawDiffResponse(
 849      DifferentialRevision $revision,
 850      array $changesets,
 851      array $vs_changesets,
 852      array $vs_map,
 853      PhabricatorRepository $repository = null) {
 854  
 855      assert_instances_of($changesets,    'DifferentialChangeset');
 856      assert_instances_of($vs_changesets, 'DifferentialChangeset');
 857  
 858      $viewer = $this->getRequest()->getUser();
 859  
 860      id(new DifferentialHunkQuery())
 861        ->setViewer($viewer)
 862        ->withChangesets($changesets)
 863        ->needAttachToChangesets(true)
 864        ->execute();
 865  
 866      $diff = new DifferentialDiff();
 867      $diff->attachChangesets($changesets);
 868      $raw_changes = $diff->buildChangesList();
 869      $changes = array();
 870      foreach ($raw_changes as $changedict) {
 871        $changes[] = ArcanistDiffChange::newFromDictionary($changedict);
 872      }
 873  
 874      $loader = id(new PhabricatorFileBundleLoader())
 875        ->setViewer($viewer);
 876  
 877      $bundle = ArcanistBundle::newFromChanges($changes);
 878      $bundle->setLoadFileDataCallback(array($loader, 'loadFileData'));
 879  
 880      $vcs = $repository ? $repository->getVersionControlSystem() : null;
 881      switch ($vcs) {
 882        case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
 883        case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
 884          $raw_diff = $bundle->toGitPatch();
 885          break;
 886        case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
 887        default:
 888          $raw_diff = $bundle->toUnifiedDiff();
 889          break;
 890      }
 891  
 892      $request_uri = $this->getRequest()->getRequestURI();
 893  
 894      // this ends up being something like
 895      //   D123.diff
 896      // or the verbose
 897      //   D123.vs123.id123.whitespaceignore-all.diff
 898      // lame but nice to include these options
 899      $file_name = ltrim($request_uri->getPath(), '/').'.';
 900      foreach ($request_uri->getQueryParams() as $key => $value) {
 901        if ($key == 'download') {
 902          continue;
 903        }
 904        $file_name .= $key.$value.'.';
 905      }
 906      $file_name .= 'diff';
 907  
 908      $file = PhabricatorFile::buildFromFileDataOrHash(
 909        $raw_diff,
 910        array(
 911          'name' => $file_name,
 912          'ttl' => (60 * 60 * 24),
 913          'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
 914        ));
 915  
 916      $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 917        $file->attachToObject($revision->getPHID());
 918      unset($unguarded);
 919  
 920      return $file->getRedirectResponse();
 921    }
 922  
 923    private function buildTransactions(
 924      DifferentialRevision $revision,
 925      DifferentialDiff $left_diff,
 926      DifferentialDiff $right_diff,
 927      array $changesets) {
 928  
 929      $viewer = $this->getRequest()->getUser();
 930  
 931      $xactions = id(new DifferentialTransactionQuery())
 932        ->setViewer($viewer)
 933        ->withObjectPHIDs(array($revision->getPHID()))
 934        ->needComments(true)
 935        ->execute();
 936  
 937      $timeline = id(new DifferentialTransactionView())
 938        ->setUser($viewer)
 939        ->setObjectPHID($revision->getPHID())
 940        ->setChangesets($changesets)
 941        ->setRevision($revision)
 942        ->setLeftDiff($left_diff)
 943        ->setRightDiff($right_diff)
 944        ->setTransactions($xactions);
 945  
 946      return $timeline;
 947    }
 948  
 949    private function buildRevisionWarnings(
 950      DifferentialRevision $revision,
 951      PhabricatorCustomFieldList $field_list,
 952      array $warning_handle_map,
 953      array $handles) {
 954  
 955      $warnings = array();
 956      foreach ($field_list->getFields() as $key => $field) {
 957        $phids = idx($warning_handle_map, $key, array());
 958        $field_handles = array_select_keys($handles, $phids);
 959        $field_warnings = $field->getWarningsForRevisionHeader($field_handles);
 960        foreach ($field_warnings as $warning) {
 961          $warnings[] = $warning;
 962        }
 963      }
 964  
 965      return $warnings;
 966    }
 967  
 968  }


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