[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

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

   1  <?php
   2  
   3  final class DiffusionCommitController extends DiffusionController {
   4  
   5    const CHANGES_LIMIT = 100;
   6  
   7    private $auditAuthorityPHIDs;
   8    private $highlightedAudits;
   9  
  10    public function shouldAllowPublic() {
  11      return true;
  12    }
  13  
  14    public function willProcessRequest(array $data) {
  15      // This controller doesn't use blob/path stuff, just pass the dictionary
  16      // in directly instead of using the AphrontRequest parsing mechanism.
  17      $data['user'] = $this->getRequest()->getUser();
  18      $drequest = DiffusionRequest::newFromDictionary($data);
  19      $this->diffusionRequest = $drequest;
  20    }
  21  
  22    public function processRequest() {
  23      $drequest = $this->getDiffusionRequest();
  24  
  25      $request = $this->getRequest();
  26      $user = $request->getUser();
  27  
  28      if ($request->getStr('diff')) {
  29        return $this->buildRawDiffResponse($drequest);
  30      }
  31  
  32      $repository = $drequest->getRepository();
  33      $callsign = $repository->getCallsign();
  34  
  35      $content = array();
  36      $commit = id(new DiffusionCommitQuery())
  37        ->setViewer($request->getUser())
  38        ->withRepository($repository)
  39        ->withIdentifiers(array($drequest->getCommit()))
  40        ->needCommitData(true)
  41        ->needAuditRequests(true)
  42        ->executeOne();
  43  
  44      $crumbs = $this->buildCrumbs(array(
  45        'commit' => true,
  46      ));
  47  
  48      if (!$commit) {
  49        $exists = $this->callConduitWithDiffusionRequest(
  50          'diffusion.existsquery',
  51          array('commit' => $drequest->getCommit()));
  52        if (!$exists) {
  53          return new Aphront404Response();
  54        }
  55  
  56        $error = id(new AphrontErrorView())
  57          ->setTitle(pht('Commit Still Parsing'))
  58          ->appendChild(
  59            pht(
  60              'Failed to load the commit because the commit has not been '.
  61              'parsed yet.'));
  62  
  63        return $this->buildApplicationPage(
  64          array(
  65            $crumbs,
  66            $error,
  67          ),
  68          array(
  69            'title' => pht('Commit Still Parsing'),
  70            'device' => false,
  71          ));
  72      }
  73  
  74  
  75      $top_anchor = id(new PhabricatorAnchorView())
  76        ->setAnchorName('top')
  77        ->setNavigationMarker(true);
  78  
  79      $audit_requests = $commit->getAudits();
  80      $this->auditAuthorityPHIDs =
  81        PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
  82  
  83      $commit_data = $commit->getCommitData();
  84      $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
  85      $changesets = null;
  86      if ($is_foreign) {
  87        $subpath = $commit_data->getCommitDetail('svn-subpath');
  88  
  89        $error_panel = new AphrontErrorView();
  90        $error_panel->setTitle(pht('Commit Not Tracked'));
  91        $error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
  92        $error_panel->appendChild(
  93          pht("This Diffusion repository is configured to track only one ".
  94          "subdirectory of the entire Subversion repository, and this commit ".
  95          "didn't affect the tracked subdirectory ('%s'), so no ".
  96          "information is available.", $subpath));
  97        $content[] = $error_panel;
  98        $content[] = $top_anchor;
  99      } else {
 100        $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
 101        $engine->setConfig('viewer', $user);
 102  
 103        require_celerity_resource('phabricator-remarkup-css');
 104  
 105        $parents = $this->callConduitWithDiffusionRequest(
 106          'diffusion.commitparentsquery',
 107          array('commit' => $drequest->getCommit()));
 108  
 109        if ($parents) {
 110          $parents = id(new DiffusionCommitQuery())
 111            ->setViewer($user)
 112            ->withRepository($repository)
 113            ->withIdentifiers($parents)
 114            ->execute();
 115        }
 116  
 117        $headsup_view = id(new PHUIHeaderView())
 118          ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
 119  
 120        $headsup_actions = $this->renderHeadsupActionList($commit, $repository);
 121  
 122        $commit_properties = $this->loadCommitProperties(
 123          $commit,
 124          $commit_data,
 125          $parents,
 126          $audit_requests);
 127        $property_list = id(new PHUIPropertyListView())
 128          ->setHasKeyboardShortcuts(true)
 129          ->setUser($user)
 130          ->setObject($commit);
 131        foreach ($commit_properties as $key => $value) {
 132          $property_list->addProperty($key, $value);
 133        }
 134  
 135        $message = $commit_data->getCommitMessage();
 136  
 137        $revision = $commit->getCommitIdentifier();
 138        $message = $this->linkBugtraq($message);
 139  
 140        $message = $engine->markupText($message);
 141  
 142        $property_list->invokeWillRenderEvent();
 143        $property_list->setActionList($headsup_actions);
 144  
 145        $detail_list = new PHUIPropertyListView();
 146        $detail_list->addSectionHeader(
 147          pht('Description'),
 148          PHUIPropertyListView::ICON_SUMMARY);
 149        $detail_list->addTextContent(
 150          phutil_tag(
 151            'div',
 152            array(
 153              'class' => 'diffusion-commit-message phabricator-remarkup',
 154            ),
 155            $message));
 156        $content[] = $top_anchor;
 157  
 158        $object_box = id(new PHUIObjectBoxView())
 159          ->setHeader($headsup_view)
 160          ->addPropertyList($property_list)
 161          ->addPropertyList($detail_list);
 162  
 163        $content[] = $object_box;
 164      }
 165  
 166      $content[] = $this->buildComments($commit);
 167  
 168      $hard_limit = 1000;
 169  
 170      if ($commit->isImported()) {
 171        $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
 172          $drequest);
 173        $change_query->setLimit($hard_limit + 1);
 174        $changes = $change_query->loadChanges();
 175      } else {
 176        $changes = array();
 177      }
 178  
 179      $was_limited = (count($changes) > $hard_limit);
 180      if ($was_limited) {
 181        $changes = array_slice($changes, 0, $hard_limit);
 182      }
 183  
 184      $content[] = $this->buildMergesTable($commit);
 185  
 186      $highlighted_audits = $commit->getAuthorityAudits(
 187        $user,
 188        $this->auditAuthorityPHIDs);
 189  
 190      $owners_paths = array();
 191      if ($highlighted_audits) {
 192        $packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
 193          'phid IN (%Ls)',
 194          mpull($highlighted_audits, 'getAuditorPHID'));
 195        if ($packages) {
 196          $owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere(
 197            'repositoryPHID = %s AND packageID IN (%Ld)',
 198            $repository->getPHID(),
 199            mpull($packages, 'getID'));
 200        }
 201      }
 202  
 203      $change_table = new DiffusionCommitChangeTableView();
 204      $change_table->setDiffusionRequest($drequest);
 205      $change_table->setPathChanges($changes);
 206      $change_table->setOwnersPaths($owners_paths);
 207  
 208      $count = count($changes);
 209  
 210      $bad_commit = null;
 211      if ($count == 0) {
 212        $bad_commit = queryfx_one(
 213          id(new PhabricatorRepository())->establishConnection('r'),
 214          'SELECT * FROM %T WHERE fullCommitName = %s',
 215          PhabricatorRepository::TABLE_BADCOMMIT,
 216          'r'.$callsign.$commit->getCommitIdentifier());
 217      }
 218  
 219      if ($bad_commit) {
 220        $content[] = $this->renderStatusMessage(
 221          pht('Bad Commit'),
 222          $bad_commit['description']);
 223      } else if ($is_foreign) {
 224        // Don't render anything else.
 225      } else if (!$commit->isImported()) {
 226        $content[] = $this->renderStatusMessage(
 227          pht('Still Importing...'),
 228          pht(
 229            'This commit is still importing. Changes will be visible once '.
 230            'the import finishes.'));
 231      } else if (!count($changes)) {
 232        $content[] = $this->renderStatusMessage(
 233          pht('Empty Commit'),
 234          pht(
 235            'This commit is empty and does not affect any paths.'));
 236      } else if ($was_limited) {
 237        $content[] = $this->renderStatusMessage(
 238          pht('Enormous Commit'),
 239          pht(
 240            'This commit is enormous, and affects more than %d files. '.
 241            'Changes are not shown.',
 242            $hard_limit));
 243      } else {
 244        // The user has clicked "Show All Changes", and we should show all the
 245        // changes inline even if there are more than the soft limit.
 246        $show_all_details = $request->getBool('show_all');
 247  
 248        $change_panel = new PHUIObjectBoxView();
 249        $header = new PHUIHeaderView();
 250        $header->setHeader('Changes ('.number_format($count).')');
 251        $change_panel->setID('toc');
 252        if ($count > self::CHANGES_LIMIT && !$show_all_details) {
 253  
 254          $icon = id(new PHUIIconView())
 255            ->setIconFont('fa-files-o');
 256  
 257          $button = id(new PHUIButtonView())
 258            ->setText(pht('Show All Changes'))
 259            ->setHref('?show_all=true')
 260            ->setTag('a')
 261            ->setIcon($icon);
 262  
 263          $warning_view = id(new AphrontErrorView())
 264            ->setSeverity(AphrontErrorView::SEVERITY_WARNING)
 265            ->setTitle('Very Large Commit')
 266            ->appendChild(
 267              pht('This commit is very large. Load each file individually.'));
 268  
 269          $change_panel->setErrorView($warning_view);
 270          $header->addActionLink($button);
 271        }
 272  
 273        $change_panel->appendChild($change_table);
 274        $change_panel->setHeader($header);
 275  
 276        $content[] = $change_panel;
 277  
 278        $changesets = DiffusionPathChange::convertToDifferentialChangesets(
 279          $user,
 280          $changes);
 281  
 282        $vcs = $repository->getVersionControlSystem();
 283        switch ($vcs) {
 284          case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
 285            $vcs_supports_directory_changes = true;
 286            break;
 287          case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
 288          case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
 289            $vcs_supports_directory_changes = false;
 290            break;
 291          default:
 292            throw new Exception('Unknown VCS.');
 293        }
 294  
 295        $references = array();
 296        foreach ($changesets as $key => $changeset) {
 297          $file_type = $changeset->getFileType();
 298          if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
 299            if (!$vcs_supports_directory_changes) {
 300              unset($changesets[$key]);
 301              continue;
 302            }
 303          }
 304  
 305          $references[$key] = $drequest->generateURI(
 306            array(
 307              'action' => 'rendering-ref',
 308              'path'   => $changeset->getFilename(),
 309            ));
 310        }
 311  
 312        // TODO: Some parts of the views still rely on properties of the
 313        // DifferentialChangeset. Make the objects ephemeral to make sure we don't
 314        // accidentally save them, and then set their ID to the appropriate ID for
 315        // this application (the path IDs).
 316        $path_ids = array_flip(mpull($changes, 'getPath'));
 317        foreach ($changesets as $changeset) {
 318          $changeset->makeEphemeral();
 319          $changeset->setID($path_ids[$changeset->getFilename()]);
 320        }
 321  
 322        if ($count <= self::CHANGES_LIMIT || $show_all_details) {
 323          $visible_changesets = $changesets;
 324        } else {
 325          $visible_changesets = array();
 326          $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments(
 327            $user,
 328            $commit->getPHID());
 329          $path_ids = mpull($inlines, null, 'getPathID');
 330          foreach ($changesets as $key => $changeset) {
 331            if (array_key_exists($changeset->getID(), $path_ids)) {
 332              $visible_changesets[$key] = $changeset;
 333            }
 334          }
 335        }
 336  
 337        $change_list_title = DiffusionView::nameCommit(
 338          $repository,
 339          $commit->getCommitIdentifier());
 340        $change_list = new DifferentialChangesetListView();
 341        $change_list->setTitle($change_list_title);
 342        $change_list->setChangesets($changesets);
 343        $change_list->setVisibleChangesets($visible_changesets);
 344        $change_list->setRenderingReferences($references);
 345        $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
 346        $change_list->setRepository($repository);
 347        $change_list->setUser($user);
 348  
 349        // TODO: Try to setBranch() to something reasonable here?
 350  
 351        $change_list->setStandaloneURI(
 352          '/diffusion/'.$callsign.'/diff/');
 353        $change_list->setRawFileURIs(
 354          // TODO: Implement this, somewhat tricky if there's an octopus merge
 355          // or whatever?
 356          null,
 357          '/diffusion/'.$callsign.'/diff/?view=r');
 358  
 359        $change_list->setInlineCommentControllerURI(
 360          '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
 361  
 362        $change_references = array();
 363        foreach ($changesets as $key => $changeset) {
 364          $change_references[$changeset->getID()] = $references[$key];
 365        }
 366        $change_table->setRenderingReferences($change_references);
 367  
 368        $content[] = $change_list->render();
 369      }
 370  
 371      $content[] = $this->renderAddCommentPanel($commit, $audit_requests);
 372  
 373      $commit_id = 'r'.$callsign.$commit->getCommitIdentifier();
 374      $short_name = DiffusionView::nameCommit(
 375        $repository,
 376        $commit->getCommitIdentifier());
 377  
 378      $prefs = $user->loadPreferences();
 379      $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
 380      $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED;
 381      $show_filetree = $prefs->getPreference($pref_filetree);
 382      $collapsed = $prefs->getPreference($pref_collapse);
 383  
 384      if ($changesets && $show_filetree) {
 385        $nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
 386          ->setAnchorName('top')
 387          ->setTitle($short_name)
 388          ->setBaseURI(new PhutilURI('/'.$commit_id))
 389          ->build($changesets)
 390          ->setCrumbs($crumbs)
 391          ->setCollapsed((bool)$collapsed)
 392          ->appendChild($content);
 393        $content = $nav;
 394      } else {
 395        $content = array($crumbs, $content);
 396      }
 397  
 398      return $this->buildApplicationPage(
 399        $content,
 400        array(
 401          'title' => $commit_id,
 402          'pageObjects' => array($commit->getPHID()),
 403          'device' => false,
 404        ));
 405    }
 406  
 407    private function loadCommitProperties(
 408      PhabricatorRepositoryCommit $commit,
 409      PhabricatorRepositoryCommitData $data,
 410      array $parents,
 411      array $audit_requests) {
 412  
 413      assert_instances_of($parents, 'PhabricatorRepositoryCommit');
 414      $viewer = $this->getRequest()->getUser();
 415      $commit_phid = $commit->getPHID();
 416      $drequest = $this->getDiffusionRequest();
 417      $repository = $drequest->getRepository();
 418  
 419      $edge_query = id(new PhabricatorEdgeQuery())
 420        ->withSourcePHIDs(array($commit_phid))
 421        ->withEdgeTypes(array(
 422          DiffusionCommitHasTaskEdgeType::EDGECONST,
 423          PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV,
 424        ));
 425  
 426      $edges = $edge_query->execute();
 427  
 428      $task_phids = array_keys(
 429        $edges[$commit_phid][DiffusionCommitHasTaskEdgeType::EDGECONST]);
 430      $revision_phid = key(
 431        $edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV]);
 432  
 433      $phids = $edge_query->getDestinationPHIDs(array($commit_phid));
 434  
 435      if ($data->getCommitDetail('authorPHID')) {
 436        $phids[] = $data->getCommitDetail('authorPHID');
 437      }
 438      if ($data->getCommitDetail('reviewerPHID')) {
 439        $phids[] = $data->getCommitDetail('reviewerPHID');
 440      }
 441      if ($data->getCommitDetail('committerPHID')) {
 442        $phids[] = $data->getCommitDetail('committerPHID');
 443      }
 444      if ($parents) {
 445        foreach ($parents as $parent) {
 446          $phids[] = $parent->getPHID();
 447        }
 448      }
 449  
 450      // NOTE: We should never normally have more than a single push log, but
 451      // it can occur naturally if a commit is pushed, then the branch it was
 452      // on is deleted, then the commit is pushed again (or through other similar
 453      // chains of events). This should be rare, but does not indicate a bug
 454      // or data issue.
 455  
 456      // NOTE: We never query push logs in SVN because the commiter is always
 457      // the pusher and the commit time is always the push time; the push log
 458      // is redundant and we save a query by skipping it.
 459  
 460      $push_logs = array();
 461      if ($repository->isHosted() && !$repository->isSVN()) {
 462        $push_logs = id(new PhabricatorRepositoryPushLogQuery())
 463          ->setViewer($viewer)
 464          ->withRepositoryPHIDs(array($repository->getPHID()))
 465          ->withNewRefs(array($commit->getCommitIdentifier()))
 466          ->withRefTypes(array(PhabricatorRepositoryPushLog::REFTYPE_COMMIT))
 467          ->execute();
 468        foreach ($push_logs as $log) {
 469          $phids[] = $log->getPusherPHID();
 470        }
 471      }
 472  
 473      $handles = array();
 474      if ($phids) {
 475        $handles = $this->loadViewerHandles($phids);
 476      }
 477  
 478      $props = array();
 479  
 480      if ($commit->getAuditStatus()) {
 481        $status = PhabricatorAuditCommitStatusConstants::getStatusName(
 482          $commit->getAuditStatus());
 483        $tag = id(new PHUITagView())
 484          ->setType(PHUITagView::TYPE_STATE)
 485          ->setName($status);
 486  
 487        switch ($commit->getAuditStatus()) {
 488          case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT:
 489            $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE);
 490            break;
 491          case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
 492            $tag->setBackgroundColor(PHUITagView::COLOR_RED);
 493            break;
 494          case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED:
 495            $tag->setBackgroundColor(PHUITagView::COLOR_BLUE);
 496            break;
 497          case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
 498            $tag->setBackgroundColor(PHUITagView::COLOR_GREEN);
 499            break;
 500        }
 501  
 502        $props['Status'] = $tag;
 503      }
 504  
 505      if ($audit_requests) {
 506        $user_requests = array();
 507        $other_requests = array();
 508        foreach ($audit_requests as $audit_request) {
 509          if ($audit_request->isUser()) {
 510            $user_requests[] = $audit_request;
 511          } else {
 512            $other_requests[] = $audit_request;
 513          }
 514        }
 515  
 516        if ($user_requests) {
 517          $props['Auditors'] = $this->renderAuditStatusView(
 518            $user_requests);
 519        }
 520  
 521        if ($other_requests) {
 522          $props['Project/Package Auditors'] = $this->renderAuditStatusView(
 523            $other_requests);
 524        }
 525      }
 526  
 527      $author_phid = $data->getCommitDetail('authorPHID');
 528      $author_name = $data->getAuthorName();
 529  
 530      if (!$repository->isSVN()) {
 531        $authored_info = id(new PHUIStatusItemView());
 532        // TODO: In Git, a distinct authorship date is available. When present,
 533        // we should show it here.
 534  
 535        if ($author_phid) {
 536          $authored_info->setTarget($handles[$author_phid]->renderLink());
 537        } else if (strlen($author_name)) {
 538          $authored_info->setTarget($author_name);
 539        }
 540  
 541        $props['Authored'] = id(new PHUIStatusListView())
 542          ->addItem($authored_info);
 543      }
 544  
 545      $committed_info = id(new PHUIStatusItemView())
 546        ->setNote(phabricator_datetime($commit->getEpoch(), $viewer));
 547  
 548      $committer_phid = $data->getCommitDetail('committerPHID');
 549      $committer_name = $data->getCommitDetail('committer');
 550      if ($committer_phid) {
 551        $committed_info->setTarget($handles[$committer_phid]->renderLink());
 552      } else if (strlen($committer_name)) {
 553        $committed_info->setTarget($committer_name);
 554      } else if ($author_phid) {
 555        $committed_info->setTarget($handles[$author_phid]->renderLink());
 556      } else if (strlen($author_name)) {
 557        $committed_info->setTarget($author_name);
 558      }
 559  
 560      $props['Committed'] = id(new PHUIStatusListView())
 561        ->addItem($committed_info);
 562  
 563      if ($push_logs) {
 564        $pushed_list = new PHUIStatusListView();
 565  
 566        foreach ($push_logs as $push_log) {
 567          $pushed_item = id(new PHUIStatusItemView())
 568            ->setTarget($handles[$push_log->getPusherPHID()]->renderLink())
 569            ->setNote(phabricator_datetime($push_log->getEpoch(), $viewer));
 570          $pushed_list->addItem($pushed_item);
 571        }
 572  
 573        $props['Pushed'] = $pushed_list;
 574      }
 575  
 576      $reviewer_phid = $data->getCommitDetail('reviewerPHID');
 577      if ($reviewer_phid) {
 578        $props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
 579      }
 580  
 581      if ($revision_phid) {
 582        $props['Differential Revision'] = $handles[$revision_phid]->renderLink();
 583      }
 584  
 585      if ($parents) {
 586        $parent_links = array();
 587        foreach ($parents as $parent) {
 588          $parent_links[] = $handles[$parent->getPHID()]->renderLink();
 589        }
 590        $props['Parents'] = phutil_implode_html(" \xC2\xB7 ", $parent_links);
 591      }
 592  
 593      $props['Branches'] = phutil_tag(
 594        'span',
 595        array(
 596          'id' => 'commit-branches',
 597        ),
 598        pht('Unknown'));
 599      $props['Tags'] = phutil_tag(
 600        'span',
 601        array(
 602          'id' => 'commit-tags',
 603        ),
 604        pht('Unknown'));
 605  
 606      $callsign = $repository->getCallsign();
 607      $root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
 608      Javelin::initBehavior(
 609        'diffusion-commit-branches',
 610        array(
 611          $root.'/branches/' => 'commit-branches',
 612          $root.'/tags/' => 'commit-tags',
 613        ));
 614  
 615      $refs = $this->buildRefs($drequest);
 616      if ($refs) {
 617        $props['References'] = $refs;
 618      }
 619  
 620      if ($task_phids) {
 621        $task_list = array();
 622        foreach ($task_phids as $phid) {
 623          $task_list[] = $handles[$phid]->renderLink();
 624        }
 625        $task_list = phutil_implode_html(phutil_tag('br'), $task_list);
 626        $props['Tasks'] = $task_list;
 627      }
 628  
 629      return $props;
 630    }
 631  
 632    private function buildComments(PhabricatorRepositoryCommit $commit) {
 633      $viewer = $this->getRequest()->getUser();
 634  
 635      $xactions = id(new PhabricatorAuditTransactionQuery())
 636        ->setViewer($viewer)
 637        ->withObjectPHIDs(array($commit->getPHID()))
 638        ->needComments(true)
 639        ->execute();
 640  
 641      $path_ids = array();
 642      foreach ($xactions as $xaction) {
 643        if ($xaction->hasComment()) {
 644          $path_id = $xaction->getComment()->getPathID();
 645          if ($path_id) {
 646            $path_ids[] = $path_id;
 647          }
 648        }
 649      }
 650  
 651      $path_map = array();
 652      if ($path_ids) {
 653        $path_map = id(new DiffusionPathQuery())
 654          ->withPathIDs($path_ids)
 655          ->execute();
 656        $path_map = ipull($path_map, 'path', 'id');
 657      }
 658  
 659      return id(new PhabricatorAuditTransactionView())
 660        ->setUser($viewer)
 661        ->setObjectPHID($commit->getPHID())
 662        ->setPathMap($path_map)
 663        ->setTransactions($xactions);
 664    }
 665  
 666    private function renderAddCommentPanel(
 667      PhabricatorRepositoryCommit $commit,
 668      array $audit_requests) {
 669      assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
 670  
 671      $request = $this->getRequest();
 672      $user = $request->getUser();
 673  
 674      if (!$user->isLoggedIn()) {
 675        return id(new PhabricatorApplicationTransactionCommentView())
 676          ->setUser($user)
 677          ->setRequestURI($request->getRequestURI());
 678      }
 679  
 680      $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
 681  
 682      $pane_id = celerity_generate_unique_node_id();
 683      Javelin::initBehavior(
 684        'differential-keyboard-navigation',
 685        array(
 686          'haunt' => $pane_id,
 687        ));
 688  
 689      $draft = id(new PhabricatorDraft())->loadOneWhere(
 690        'authorPHID = %s AND draftKey = %s',
 691        $user->getPHID(),
 692        'diffusion-audit-'.$commit->getID());
 693      if ($draft) {
 694        $draft = $draft->getDraft();
 695      } else {
 696        $draft = null;
 697      }
 698  
 699      $actions = $this->getAuditActions($commit, $audit_requests);
 700  
 701      $form = id(new AphrontFormView())
 702        ->setUser($user)
 703        ->setAction('/audit/addcomment/')
 704        ->addHiddenInput('commit', $commit->getPHID())
 705        ->appendChild(
 706          id(new AphrontFormSelectControl())
 707            ->setLabel(pht('Action'))
 708            ->setName('action')
 709            ->setID('audit-action')
 710            ->setOptions($actions))
 711        ->appendChild(
 712          id(new AphrontFormTokenizerControl())
 713            ->setLabel(pht('Add Auditors'))
 714            ->setName('auditors')
 715            ->setControlID('add-auditors')
 716            ->setControlStyle('display: none')
 717            ->setID('add-auditors-tokenizer')
 718            ->setDisableBehavior(true))
 719        ->appendChild(
 720          id(new AphrontFormTokenizerControl())
 721            ->setLabel(pht('Add CCs'))
 722            ->setName('ccs')
 723            ->setControlID('add-ccs')
 724            ->setControlStyle('display: none')
 725            ->setID('add-ccs-tokenizer')
 726            ->setDisableBehavior(true))
 727        ->appendChild(
 728          id(new PhabricatorRemarkupControl())
 729            ->setLabel(pht('Comments'))
 730            ->setName('content')
 731            ->setValue($draft)
 732            ->setID('audit-content')
 733            ->setUser($user))
 734        ->appendChild(
 735          id(new AphrontFormSubmitControl())
 736            ->setValue(pht('Submit')));
 737  
 738      $header = new PHUIHeaderView();
 739      $header->setHeader(
 740        $is_serious ? pht('Audit Commit') : pht('Creative Accounting'));
 741  
 742      require_celerity_resource('phabricator-transaction-view-css');
 743  
 744      $mailable_source = new PhabricatorMetaMTAMailableDatasource();
 745      $auditor_source = new DiffusionAuditorDatasource();
 746  
 747      Javelin::initBehavior(
 748        'differential-add-reviewers-and-ccs',
 749        array(
 750          'dynamic' => array(
 751            'add-auditors-tokenizer' => array(
 752              'actions' => array('add_auditors' => 1),
 753              'src' => $auditor_source->getDatasourceURI(),
 754              'row' => 'add-auditors',
 755              'placeholder' => $auditor_source->getPlaceholderText(),
 756            ),
 757            'add-ccs-tokenizer' => array(
 758              'actions' => array('add_ccs' => 1),
 759              'src' => $mailable_source->getDatasourceURI(),
 760              'row' => 'add-ccs',
 761              'placeholder' => $mailable_source->getPlaceholderText(),
 762            ),
 763          ),
 764          'select' => 'audit-action',
 765        ));
 766  
 767      Javelin::initBehavior('differential-feedback-preview', array(
 768        'uri'       => '/audit/preview/'.$commit->getID().'/',
 769        'preview'   => 'audit-preview',
 770        'content'   => 'audit-content',
 771        'action'    => 'audit-action',
 772        'previewTokenizers' => array(
 773          'auditors' => 'add-auditors-tokenizer',
 774          'ccs'      => 'add-ccs-tokenizer',
 775        ),
 776        'inline'     => 'inline-comment-preview',
 777        'inlineuri'  => '/diffusion/inline/preview/'.$commit->getPHID().'/',
 778      ));
 779  
 780      $loading = phutil_tag_div(
 781        'aphront-panel-preview-loading-text',
 782        pht('Loading preview...'));
 783  
 784      $preview_panel = phutil_tag_div(
 785        'aphront-panel-preview aphront-panel-flush',
 786        array(
 787          phutil_tag('div', array('id' => 'audit-preview'), $loading),
 788          phutil_tag('div', array('id' => 'inline-comment-preview')),
 789        ));
 790  
 791      // TODO: This is pretty awkward, unify the CSS between Diffusion and
 792      // Differential better.
 793      require_celerity_resource('differential-core-view-css');
 794  
 795      $anchor = id(new PhabricatorAnchorView())
 796        ->setAnchorName('comment')
 797        ->setNavigationMarker(true)
 798        ->render();
 799  
 800      $comment_box = id(new PHUIObjectBoxView())
 801        ->setHeader($header)
 802        ->appendChild($form);
 803  
 804      return phutil_tag(
 805        'div',
 806        array(
 807          'id' => $pane_id,
 808        ),
 809        phutil_tag_div(
 810          'differential-add-comment-panel',
 811          array($anchor, $comment_box, $preview_panel)));
 812    }
 813  
 814    /**
 815     * Return a map of available audit actions for rendering into a <select />.
 816     * This shows the user valid actions, and does not show nonsense/invalid
 817     * actions (like closing an already-closed commit, or resigning from a commit
 818     * you have no association with).
 819     */
 820    private function getAuditActions(
 821      PhabricatorRepositoryCommit $commit,
 822      array $audit_requests) {
 823      assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
 824      $user = $this->getRequest()->getUser();
 825  
 826      $user_is_author = ($commit->getAuthorPHID() == $user->getPHID());
 827  
 828      $user_request = null;
 829      foreach ($audit_requests as $audit_request) {
 830        if ($audit_request->getAuditorPHID() == $user->getPHID()) {
 831          $user_request = $audit_request;
 832          break;
 833        }
 834      }
 835  
 836      $actions = array();
 837      $actions[PhabricatorAuditActionConstants::COMMENT] = true;
 838      $actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
 839      $actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
 840  
 841      // We allow you to accept your own commits. A use case here is that you
 842      // notice an issue with your own commit and "Raise Concern" as an indicator
 843      // to other auditors that you're on top of the issue, then later resolve it
 844      // and "Accept". You can not accept on behalf of projects or packages,
 845      // however.
 846      $actions[PhabricatorAuditActionConstants::ACCEPT]  = true;
 847      $actions[PhabricatorAuditActionConstants::CONCERN] = true;
 848  
 849  
 850      // To resign, a user must have authority on some request and not be the
 851      // commit's author.
 852      if (!$user_is_author) {
 853        $may_resign = false;
 854  
 855        $authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
 856        foreach ($audit_requests as $request) {
 857          if (empty($authority_map[$request->getAuditorPHID()])) {
 858            continue;
 859          }
 860          $may_resign = true;
 861          break;
 862        }
 863  
 864        // If the user has already resigned, don't show "Resign...".
 865        $status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
 866        if ($user_request) {
 867          if ($user_request->getAuditStatus() == $status_resigned) {
 868            $may_resign = false;
 869          }
 870        }
 871  
 872        if ($may_resign) {
 873          $actions[PhabricatorAuditActionConstants::RESIGN] = true;
 874        }
 875      }
 876  
 877      $status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
 878      $concern_raised = ($commit->getAuditStatus() == $status_concern);
 879      $can_close_option = PhabricatorEnv::getEnvConfig(
 880        'audit.can-author-close-audit');
 881      if ($can_close_option && $user_is_author && $concern_raised) {
 882        $actions[PhabricatorAuditActionConstants::CLOSE] = true;
 883      }
 884  
 885      foreach ($actions as $constant => $ignored) {
 886        $actions[$constant] =
 887          PhabricatorAuditActionConstants::getActionName($constant);
 888      }
 889  
 890      return $actions;
 891    }
 892  
 893    private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
 894      $drequest = $this->getDiffusionRequest();
 895      $limit = 50;
 896  
 897      $merges = array();
 898      try {
 899        $merges = $this->callConduitWithDiffusionRequest(
 900          'diffusion.mergedcommitsquery',
 901          array(
 902            'commit' => $drequest->getCommit(),
 903            'limit' => $limit + 1,
 904          ));
 905      } catch (ConduitException $ex) {
 906        if ($ex->getMessage() != 'ERR-UNSUPPORTED-VCS') {
 907          throw $ex;
 908        }
 909      }
 910  
 911      if (!$merges) {
 912        return null;
 913      }
 914  
 915      $caption = null;
 916      if (count($merges) > $limit) {
 917        $merges = array_slice($merges, 0, $limit);
 918        $caption =
 919          "This commit merges more than {$limit} changes. Only the first ".
 920          "{$limit} are shown.";
 921      }
 922  
 923      $history_table = new DiffusionHistoryTableView();
 924      $history_table->setUser($this->getRequest()->getUser());
 925      $history_table->setDiffusionRequest($drequest);
 926      $history_table->setHistory($merges);
 927      $history_table->loadRevisions();
 928  
 929      $phids = $history_table->getRequiredHandlePHIDs();
 930      $handles = $this->loadViewerHandles($phids);
 931      $history_table->setHandles($handles);
 932  
 933      $panel = new AphrontPanelView();
 934      $panel->setHeader(pht('Merged Changes'));
 935      $panel->setCaption($caption);
 936      $panel->appendChild($history_table);
 937      $panel->setNoBackground();
 938  
 939      return $panel;
 940    }
 941  
 942    private function renderHeadsupActionList(
 943      PhabricatorRepositoryCommit $commit,
 944      PhabricatorRepository $repository) {
 945  
 946      $request = $this->getRequest();
 947      $user = $request->getUser();
 948  
 949      $actions = id(new PhabricatorActionListView())
 950        ->setUser($user)
 951        ->setObject($commit)
 952        ->setObjectURI($request->getRequestURI());
 953  
 954      $can_edit = PhabricatorPolicyFilter::hasCapability(
 955        $user,
 956        $commit,
 957        PhabricatorPolicyCapability::CAN_EDIT);
 958  
 959      $uri = '/diffusion/'.$repository->getCallsign().'/commit/'.
 960             $commit->getCommitIdentifier().'/edit/';
 961  
 962      $action = id(new PhabricatorActionView())
 963        ->setName(pht('Edit Commit'))
 964        ->setHref($uri)
 965        ->setIcon('fa-pencil')
 966        ->setDisabled(!$can_edit)
 967        ->setWorkflow(!$can_edit);
 968      $actions->addAction($action);
 969  
 970      require_celerity_resource('phabricator-object-selector-css');
 971      require_celerity_resource('javelin-behavior-phabricator-object-selector');
 972  
 973      $maniphest = 'PhabricatorManiphestApplication';
 974      if (PhabricatorApplication::isClassInstalled($maniphest)) {
 975        $action = id(new PhabricatorActionView())
 976          ->setName(pht('Edit Maniphest Tasks'))
 977          ->setIcon('fa-anchor')
 978          ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
 979          ->setWorkflow(true)
 980          ->setDisabled(!$can_edit);
 981        $actions->addAction($action);
 982      }
 983  
 984      $action = id(new PhabricatorActionView())
 985        ->setName(pht('Download Raw Diff'))
 986        ->setHref($request->getRequestURI()->alter('diff', true))
 987        ->setIcon('fa-download');
 988      $actions->addAction($action);
 989  
 990      return $actions;
 991    }
 992  
 993    private function buildRefs(DiffusionRequest $request) {
 994      // this is git-only, so save a conduit round trip and just get out of
 995      // here if the repository isn't git
 996      $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
 997      $repository = $request->getRepository();
 998      if ($repository->getVersionControlSystem() != $type_git) {
 999        return null;
1000      }
1001  
1002      $results = $this->callConduitWithDiffusionRequest(
1003        'diffusion.refsquery',
1004        array('commit' => $request->getCommit()));
1005      $ref_links = array();
1006      foreach ($results as $ref_data) {
1007        $ref_links[] = phutil_tag('a',
1008          array('href' => $ref_data['href']),
1009          $ref_data['ref']);
1010      }
1011      return phutil_implode_html(', ', $ref_links);
1012    }
1013  
1014    private function buildRawDiffResponse(DiffusionRequest $drequest) {
1015      $raw_diff = $this->callConduitWithDiffusionRequest(
1016        'diffusion.rawdiffquery',
1017        array(
1018          'commit' => $drequest->getCommit(),
1019          'path' => $drequest->getPath(),
1020        ));
1021  
1022      $file = PhabricatorFile::buildFromFileDataOrHash(
1023        $raw_diff,
1024        array(
1025          'name' => $drequest->getCommit().'.diff',
1026          'ttl' => (60 * 60 * 24),
1027          'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
1028        ));
1029  
1030      $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
1031        $file->attachToObject($drequest->getRepository()->getPHID());
1032      unset($unguarded);
1033  
1034      return $file->getRedirectResponse();
1035    }
1036  
1037    private function renderAuditStatusView(array $audit_requests) {
1038      assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
1039  
1040      $phids = mpull($audit_requests, 'getAuditorPHID');
1041      $this->loadHandles($phids);
1042  
1043      $authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
1044  
1045      $view = new PHUIStatusListView();
1046      foreach ($audit_requests as $request) {
1047        $code = $request->getAuditStatus();
1048        $item = new PHUIStatusItemView();
1049        $item->setIcon(
1050          PhabricatorAuditStatusConstants::getStatusIcon($code),
1051          PhabricatorAuditStatusConstants::getStatusColor($code),
1052          PhabricatorAuditStatusConstants::getStatusName($code));
1053  
1054        $note = array();
1055        foreach ($request->getAuditReasons() as $reason) {
1056          $note[] = phutil_tag('div', array(), $reason);
1057        }
1058        $item->setNote($note);
1059  
1060        $auditor_phid = $request->getAuditorPHID();
1061        $target = $this->getHandle($auditor_phid)->renderLink();
1062        $item->setTarget($target);
1063  
1064        if (isset($authority_map[$auditor_phid])) {
1065          $item->setHighlighted(true);
1066        }
1067  
1068        $view->addItem($item);
1069      }
1070  
1071      return $view;
1072    }
1073  
1074    private function linkBugtraq($corpus) {
1075      $url = PhabricatorEnv::getEnvConfig('bugtraq.url');
1076      if (!strlen($url)) {
1077        return $corpus;
1078      }
1079  
1080      $regexes = PhabricatorEnv::getEnvConfig('bugtraq.logregex');
1081      if (!$regexes) {
1082        return $corpus;
1083      }
1084  
1085      $parser = id(new PhutilBugtraqParser())
1086        ->setBugtraqPattern("[[ {$url} | %BUGID% ]]")
1087        ->setBugtraqCaptureExpression(array_shift($regexes));
1088  
1089      $select = array_shift($regexes);
1090      if ($select) {
1091        $parser->setBugtraqSelectExpression($select);
1092      }
1093  
1094      return $parser->processCorpus($corpus);
1095    }
1096  
1097  }


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