[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |