Something went wrong. Try again.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
Something went wrong. Try again.
38 kB · 1233 lines
PHP
at commit dc7d0b4a
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234<?php
final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
private $auditAuthorityPHIDs; private $highlightedAudits;
private $commitParents; private $commitRefs; private $commitMerges; private $commitErrors; private $commitExists;
public function shouldAllowPublic() { return true; }
public function handleRequest(AphrontRequest $request) { $response = $this->loadDiffusionContext(); if ($response) { return $response; }
$drequest = $this->getDiffusionRequest();
$user = $request->getUser();
if ($request->getStr('diff')) { return $this->buildRawDiffResponse($drequest); }
$repository = $drequest->getRepository();
$content = array(); $commit = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) ->withRepository($repository) ->withIdentifiers(array($drequest->getCommit())) ->needCommitData(true) ->needAuditRequests(true) ->executeOne();
$crumbs = $this->buildCrumbs(array( 'commit' => true, ));
if (!$commit) { if (!$this->getCommitExists()) { return new Aphront404Response(); }
$error = id(new PHUIInfoView()) ->setTitle(pht('Commit Still Parsing')) ->appendChild( pht( 'Failed to load the commit because the commit has not been '. 'parsed yet.'));
return $this->buildApplicationPage( array( $crumbs, $error, ), array( 'title' => pht('Commit Still Parsing'), )); }
$audit_requests = $commit->getAudits(); $this->auditAuthorityPHIDs = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$commit_data = $commit->getCommitData(); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath');
$error_panel = new PHUIInfoView(); $error_panel->setTitle(pht('Commit Not Tracked')); $error_panel->setSeverity(PHUIInfoView::SEVERITY_WARNING); $error_panel->appendChild( pht( "This Diffusion repository is configured to track only one ". "subdirectory of the entire Subversion repository, and this commit ". "didn't affect the tracked subdirectory ('%s'), so no ". "information is available.", $subpath)); $content[] = $error_panel; } else { $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); $engine->setConfig('viewer', $user);
$headsup_view = id(new PHUIHeaderView()) ->setHeader(nonempty($commit->getSummary(), pht('Commit Detail'))) ->setSubheader(pht('Commit: %s', $commit->getCommitIdentifier()));
$headsup_actions = $this->renderHeadsupActionList($commit, $repository);
$commit_properties = $this->loadCommitProperties( $commit, $commit_data, $audit_requests); $property_list = id(new PHUIPropertyListView()) ->setHasKeyboardShortcuts(true) ->setUser($user) ->setObject($commit); foreach ($commit_properties as $key => $value) { $property_list->addProperty($key, $value); }
$message = $commit_data->getCommitMessage();
$revision = $commit->getCommitIdentifier(); $message = $this->linkBugtraq($message);
$message = $engine->markupText($message);
$property_list->invokeWillRenderEvent(); $property_list->setActionList($headsup_actions);
$detail_list = new PHUIPropertyListView(); $detail_list->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $detail_list->addTextContent( phutil_tag( 'div', array( 'class' => 'diffusion-commit-message phabricator-remarkup', ), $message));
$headsup_view->setTall(true);
$object_box = id(new PHUIObjectBoxView()) ->setHeader($headsup_view) ->setFormErrors($this->getCommitErrors()) ->addPropertyList($property_list) ->addPropertyList($detail_list);
$content[] = $object_box; }
$content[] = $this->buildComments($commit);
$hard_limit = 1000;
if ($commit->isImported()) { $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest( $drequest); $change_query->setLimit($hard_limit + 1); $changes = $change_query->loadChanges(); } else { $changes = array(); }
$was_limited = (count($changes) > $hard_limit); if ($was_limited) { $changes = array_slice($changes, 0, $hard_limit); }
$content[] = $this->buildMergesTable($commit);
$highlighted_audits = $commit->getAuthorityAudits( $user, $this->auditAuthorityPHIDs);
$count = count($changes);
$bad_commit = null; if ($count == 0) { $bad_commit = queryfx_one( id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, $commit->getMonogram()); }
$show_changesets = false; if ($bad_commit) { $content[] = $this->renderStatusMessage( pht('Bad Commit'), $bad_commit['description']); } else if ($is_foreign) { // Don't render anything else. } else if (!$commit->isImported()) { $content[] = $this->renderStatusMessage( pht('Still Importing...'), pht( 'This commit is still importing. Changes will be visible once '. 'the import finishes.')); } else if (!count($changes)) { $content[] = $this->renderStatusMessage( pht('Empty Commit'), pht( 'This commit is empty and does not affect any paths.')); } else if ($was_limited) { $content[] = $this->renderStatusMessage( pht('Enormous Commit'), pht( 'This commit is enormous, and affects more than %d files. '. 'Changes are not shown.', $hard_limit)); } else if (!$this->getCommitExists()) { $content[] = $this->renderStatusMessage( pht('Commit No Longer Exists'), pht('This commit no longer exists in the repository.')); } else { $show_changesets = true;
// The user has clicked "Show All Changes", and we should show all the // changes inline even if there are more than the soft limit. $show_all_details = $request->getBool('show_all');
$change_panel = new PHUIObjectBoxView(); $header = new PHUIHeaderView(); $header->setHeader(pht('Changes (%s)', new PhutilNumber($count))); $change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT && !$show_all_details) {
$button = id(new PHUIButtonView()) ->setText(pht('Show All Changes')) ->setHref('?show_all=true') ->setTag('a') ->setIcon('fa-files-o');
$warning_view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle(pht('Very Large Commit')) ->appendChild( pht('This commit is very large. Load each file individually.'));
$change_panel->setInfoView($warning_view); $header->addActionLink($button); }
$changesets = DiffusionPathChange::convertToDifferentialChangesets( $user, $changes);
// TODO: This table and panel shouldn't really be separate, but we need // to clean up the "Load All Files" interaction first. $change_table = $this->buildTableOfContents( $changesets);
$change_panel->setTable($change_table); $change_panel->setHeader($header);
$content[] = $change_panel;
$vcs = $repository->getVersionControlSystem(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $vcs_supports_directory_changes = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $vcs_supports_directory_changes = false; break; default: throw new Exception(pht('Unknown VCS.')); }
$references = array(); foreach ($changesets as $key => $changeset) { $file_type = $changeset->getFileType(); if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { if (!$vcs_supports_directory_changes) { unset($changesets[$key]); continue; } }
$references[$key] = $drequest->generateURI( array( 'action' => 'rendering-ref', 'path' => $changeset->getFilename(), )); }
// TODO: Some parts of the views still rely on properties of the // DifferentialChangeset. Make the objects ephemeral to make sure we don't // accidentally save them, and then set their ID to the appropriate ID for // this application (the path IDs). $path_ids = array_flip(mpull($changes, 'getPath')); foreach ($changesets as $changeset) { $changeset->makeEphemeral(); $changeset->setID($path_ids[$changeset->getFilename()]); }
if ($count <= self::CHANGES_LIMIT || $show_all_details) { $visible_changesets = $changesets; } else { $visible_changesets = array(); $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments( $user, $commit->getPHID()); $path_ids = mpull($inlines, null, 'getPathID'); foreach ($changesets as $key => $changeset) { if (array_key_exists($changeset->getID(), $path_ids)) { $visible_changesets[$key] = $changeset; } } }
$change_list_title = $commit->getDisplayName();
$change_list = new DifferentialChangesetListView(); $change_list->setTitle($change_list_title); $change_list->setChangesets($changesets); $change_list->setVisibleChangesets($visible_changesets); $change_list->setRenderingReferences($references); $change_list->setRenderURI( $repository->getPathURI('diff/')); $change_list->setRepository($repository); $change_list->setUser($user);
// TODO: Try to setBranch() to something reasonable here?
$change_list->setStandaloneURI( $repository->getPathURI('diff/'));
$change_list->setRawFileURIs( // TODO: Implement this, somewhat tricky if there's an octopus merge // or whatever? null, $repository->getPathURI('diff/?view=r'));
$change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
$content[] = $change_list->render(); }
$content[] = $this->renderAddCommentPanel($commit, $audit_requests);
$prefs = $user->loadPreferences(); $pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE; $pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED; $show_filetree = $prefs->getPreference($pref_filetree); $collapsed = $prefs->getPreference($pref_collapse);
if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle($commit->getDisplayName()) ->setBaseURI(new PhutilURI($commit->getURI())) ->build($changesets) ->setCrumbs($crumbs) ->setCollapsed((bool)$collapsed) ->appendChild($content); $content = $nav; } else { $content = array($crumbs, $content); }
return $this->buildApplicationPage( $content, array( 'title' => $commit->getDisplayName(), 'pageObjects' => array($commit->getPHID()), )); }
private function loadCommitProperties( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data, array $audit_requests) {
$viewer = $this->getRequest()->getUser(); $commit_phid = $commit->getPHID(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository();
$edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($commit_phid)) ->withEdgeTypes(array( DiffusionCommitHasTaskEdgeType::EDGECONST, DiffusionCommitHasRevisionEdgeType::EDGECONST, DiffusionCommitRevertsCommitEdgeType::EDGECONST, DiffusionCommitRevertedByCommitEdgeType::EDGECONST, ));
$edges = $edge_query->execute();
$task_phids = array_keys( $edges[$commit_phid][DiffusionCommitHasTaskEdgeType::EDGECONST]); $revision_phid = key( $edges[$commit_phid][DiffusionCommitHasRevisionEdgeType::EDGECONST]);
$reverts_phids = array_keys( $edges[$commit_phid][DiffusionCommitRevertsCommitEdgeType::EDGECONST]); $reverted_by_phids = array_keys( $edges[$commit_phid][DiffusionCommitRevertedByCommitEdgeType::EDGECONST]);
$phids = $edge_query->getDestinationPHIDs(array($commit_phid));
if ($data->getCommitDetail('authorPHID')) { $phids[] = $data->getCommitDetail('authorPHID'); } if ($data->getCommitDetail('reviewerPHID')) { $phids[] = $data->getCommitDetail('reviewerPHID'); } if ($data->getCommitDetail('committerPHID')) { $phids[] = $data->getCommitDetail('committerPHID'); }
// NOTE: We should never normally have more than a single push log, but // it can occur naturally if a commit is pushed, then the branch it was // on is deleted, then the commit is pushed again (or through other similar // chains of events). This should be rare, but does not indicate a bug // or data issue.
// NOTE: We never query push logs in SVN because the commiter is always // the pusher and the commit time is always the push time; the push log // is redundant and we save a query by skipping it.
$push_logs = array(); if ($repository->isHosted() && !$repository->isSVN()) { $push_logs = id(new PhabricatorRepositoryPushLogQuery()) ->setViewer($viewer) ->withRepositoryPHIDs(array($repository->getPHID())) ->withNewRefs(array($commit->getCommitIdentifier())) ->withRefTypes(array(PhabricatorRepositoryPushLog::REFTYPE_COMMIT)) ->execute(); foreach ($push_logs as $log) { $phids[] = $log->getPusherPHID(); } }
$handles = array(); if ($phids) { $handles = $this->loadViewerHandles($phids); }
$props = array();
if ($commit->getAuditStatus()) { $status = PhabricatorAuditCommitStatusConstants::getStatusName( $commit->getAuditStatus()); $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setName($status);
switch ($commit->getAuditStatus()) { case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT: $tag->setBackgroundColor(PHUITagView::COLOR_ORANGE); break; case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED: $tag->setBackgroundColor(PHUITagView::COLOR_RED); break; case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED: $tag->setBackgroundColor(PHUITagView::COLOR_BLUE); break; case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED: $tag->setBackgroundColor(PHUITagView::COLOR_GREEN); break; }
$props['Status'] = $tag; }
if ($audit_requests) { $user_requests = array(); $other_requests = array(); foreach ($audit_requests as $audit_request) { if ($audit_request->isUser()) { $user_requests[] = $audit_request; } else { $other_requests[] = $audit_request; } }
if ($user_requests) { $props['Auditors'] = $this->renderAuditStatusView( $user_requests); }
if ($other_requests) { $props['Project/Package Auditors'] = $this->renderAuditStatusView( $other_requests); } }
$author_phid = $data->getCommitDetail('authorPHID'); $author_name = $data->getAuthorName();
if (!$repository->isSVN()) { $authored_info = id(new PHUIStatusItemView());
$author_epoch = $data->getCommitDetail('authorEpoch'); if ($author_epoch !== null) { $authored_info->setNote( phabricator_datetime($author_epoch, $viewer)); }
if ($author_phid) { $authored_info->setTarget($handles[$author_phid]->renderLink()); } else if (strlen($author_name)) { $authored_info->setTarget($author_name); }
$props['Authored'] = id(new PHUIStatusListView()) ->addItem($authored_info); }
$committed_info = id(new PHUIStatusItemView()) ->setNote(phabricator_datetime($commit->getEpoch(), $viewer));
$committer_phid = $data->getCommitDetail('committerPHID'); $committer_name = $data->getCommitDetail('committer'); if ($committer_phid) { $committed_info->setTarget($handles[$committer_phid]->renderLink()); } else if (strlen($committer_name)) { $committed_info->setTarget($committer_name); } else if ($author_phid) { $committed_info->setTarget($handles[$author_phid]->renderLink()); } else if (strlen($author_name)) { $committed_info->setTarget($author_name); }
$props['Committed'] = id(new PHUIStatusListView()) ->addItem($committed_info);
if ($push_logs) { $pushed_list = new PHUIStatusListView();
foreach ($push_logs as $push_log) { $pushed_item = id(new PHUIStatusItemView()) ->setTarget($handles[$push_log->getPusherPHID()]->renderLink()) ->setNote(phabricator_datetime($push_log->getEpoch(), $viewer)); $pushed_list->addItem($pushed_item); }
$props['Pushed'] = $pushed_list; }
$reviewer_phid = $data->getCommitDetail('reviewerPHID'); if ($reviewer_phid) { $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); }
if ($revision_phid) { $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); }
$parents = $this->getCommitParents(); if ($parents) { $props['Parents'] = $viewer->renderHandleList(mpull($parents, 'getPHID')); }
if ($this->getCommitExists()) { $props['Branches'] = phutil_tag( 'span', array( 'id' => 'commit-branches', ), pht('Unknown')); $props['Tags'] = phutil_tag( 'span', array( 'id' => 'commit-tags', ), pht('Unknown'));
$identifier = $commit->getCommitIdentifier(); $root = $repository->getPathURI("commit/{$identifier}"); Javelin::initBehavior( 'diffusion-commit-branches', array( $root.'/branches/' => 'commit-branches', $root.'/tags/' => 'commit-tags', )); }
$refs = $this->getCommitRefs(); if ($refs) { $ref_links = array(); foreach ($refs as $ref_data) { $ref_links[] = phutil_tag( 'a', array( 'href' => $ref_data['href'], ), $ref_data['ref']); } $props['References'] = phutil_implode_html(', ', $ref_links); }
if ($reverts_phids) { $props[pht('Reverts')] = $viewer->renderHandleList($reverts_phids); }
if ($reverted_by_phids) { $props[pht('Reverted By')] = $viewer->renderHandleList( $reverted_by_phids); }
if ($task_phids) { $task_list = array(); foreach ($task_phids as $phid) { $task_list[] = $handles[$phid]->renderLink(); } $task_list = phutil_implode_html(phutil_tag('br'), $task_list); $props['Tasks'] = $task_list; }
return $props; }
private function buildComments(PhabricatorRepositoryCommit $commit) { $timeline = $this->buildTransactionTimeline( $commit, new PhabricatorAuditTransactionQuery()); $commit->willRenderTimeline($timeline, $this->getRequest()); return $timeline; }
private function renderAddCommentPanel( PhabricatorRepositoryCommit $commit, array $audit_requests) { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$request = $this->getRequest(); $user = $request->getUser();
if (!$user->isLoggedIn()) { return id(new PhabricatorApplicationTransactionCommentView()) ->setUser($user) ->setRequestURI($request->getRequestURI()); }
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$pane_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'differential-keyboard-navigation', array( 'haunt' => $pane_id, ));
$draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), 'diffusion-audit-'.$commit->getID()); if ($draft) { $draft = $draft->getDraft(); } else { $draft = null; }
$actions = $this->getAuditActions($commit, $audit_requests);
$mailable_source = new PhabricatorMetaMTAMailableDatasource(); $auditor_source = new DiffusionAuditorDatasource();
$form = id(new AphrontFormView()) ->setUser($user) ->setAction('/audit/addcomment/') ->addHiddenInput('commit', $commit->getPHID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Action')) ->setName('action') ->setID('audit-action') ->setOptions($actions)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Add Auditors')) ->setName('auditors') ->setControlID('add-auditors') ->setControlStyle('display: none') ->setID('add-auditors-tokenizer') ->setDisableBehavior(true) ->setDatasource($auditor_source)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Add CCs')) ->setName('ccs') ->setControlID('add-ccs') ->setControlStyle('display: none') ->setID('add-ccs-tokenizer') ->setDisableBehavior(true) ->setDatasource($mailable_source)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setLabel(pht('Comments')) ->setName('content') ->setValue($draft) ->setID('audit-content') ->setUser($user)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit')));
$header = new PHUIHeaderView(); $header->setHeader( $is_serious ? pht('Audit Commit') : pht('Creative Accounting'));
Javelin::initBehavior( 'differential-add-reviewers-and-ccs', array( 'dynamic' => array( 'add-auditors-tokenizer' => array( 'actions' => array('add_auditors' => 1), 'src' => $auditor_source->getDatasourceURI(), 'row' => 'add-auditors', 'placeholder' => $auditor_source->getPlaceholderText(), ), 'add-ccs-tokenizer' => array( 'actions' => array('add_ccs' => 1), 'src' => $mailable_source->getDatasourceURI(), 'row' => 'add-ccs', 'placeholder' => $mailable_source->getPlaceholderText(), ), ), 'select' => 'audit-action', ));
Javelin::initBehavior('differential-feedback-preview', array( 'uri' => '/audit/preview/'.$commit->getID().'/', 'preview' => 'audit-preview', 'content' => 'audit-content', 'action' => 'audit-action', 'previewTokenizers' => array( 'auditors' => 'add-auditors-tokenizer', 'ccs' => 'add-ccs-tokenizer', ), 'inline' => 'inline-comment-preview', 'inlineuri' => '/diffusion/inline/preview/'.$commit->getPHID().'/', ));
$loading = phutil_tag_div( 'aphront-panel-preview-loading-text', pht('Loading preview...'));
$preview_panel = phutil_tag_div( 'aphront-panel-preview aphront-panel-flush', array( phutil_tag('div', array('id' => 'audit-preview'), $loading), phutil_tag('div', array('id' => 'inline-comment-preview')), ));
// TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css');
$anchor = id(new PhabricatorAnchorView()) ->setAnchorName('comment') ->setNavigationMarker(true) ->render();
$comment_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($form);
return phutil_tag( 'div', array( 'id' => $pane_id, ), phutil_tag_div( 'differential-add-comment-panel', array($anchor, $comment_box, $preview_panel))); }
/** * Return a map of available audit actions for rendering into a <select />. * This shows the user valid actions, and does not show nonsense/invalid * actions (like closing an already-closed commit, or resigning from a commit * you have no association with). */ private function getAuditActions( PhabricatorRepositoryCommit $commit, array $audit_requests) { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); $user = $this->getRequest()->getUser();
$user_is_author = ($commit->getAuthorPHID() == $user->getPHID());
$user_request = null; foreach ($audit_requests as $audit_request) { if ($audit_request->getAuditorPHID() == $user->getPHID()) { $user_request = $audit_request; break; } }
$actions = array(); $actions[PhabricatorAuditActionConstants::COMMENT] = true; $actions[PhabricatorAuditActionConstants::ADD_CCS] = true; $actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
// We allow you to accept your own commits. A use case here is that you // notice an issue with your own commit and "Raise Concern" as an indicator // to other auditors that you're on top of the issue, then later resolve it // and "Accept". You can not accept on behalf of projects or packages, // however. $actions[PhabricatorAuditActionConstants::ACCEPT] = true; $actions[PhabricatorAuditActionConstants::CONCERN] = true;
// To resign, a user must have authority on some request and not be the // commit's author. if (!$user_is_author) { $may_resign = false;
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true); foreach ($audit_requests as $request) { if (empty($authority_map[$request->getAuditorPHID()])) { continue; } $may_resign = true; break; }
// If the user has already resigned, don't show "Resign...". $status_resigned = PhabricatorAuditStatusConstants::RESIGNED; if ($user_request) { if ($user_request->getAuditStatus() == $status_resigned) { $may_resign = false; } }
if ($may_resign) { $actions[PhabricatorAuditActionConstants::RESIGN] = true; } }
$status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; $concern_raised = ($commit->getAuditStatus() == $status_concern); $can_close_option = PhabricatorEnv::getEnvConfig( 'audit.can-author-close-audit'); if ($can_close_option && $user_is_author && $concern_raised) { $actions[PhabricatorAuditActionConstants::CLOSE] = true; }
foreach ($actions as $constant => $ignored) { $actions[$constant] = PhabricatorAuditActionConstants::getActionName($constant); }
return $actions; }
private function buildMergesTable(PhabricatorRepositoryCommit $commit) { $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository();
$merges = $this->getCommitMerges(); if (!$merges) { return null; }
$limit = $this->getMergeDisplayLimit();
$caption = null; if (count($merges) > $limit) { $merges = array_slice($merges, 0, $limit); $caption = new PHUIInfoView(); $caption->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $caption->appendChild( pht( 'This commit merges a very large number of changes. '. 'Only the first %s are shown.', new PhutilNumber($limit))); }
$history_table = id(new DiffusionHistoryTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setHistory($merges);
$history_table->loadRevisions();
$panel = new PHUIObjectBoxView(); $panel->setHeaderText(pht('Merged Changes')); $panel->setTable($history_table); if ($caption) { $panel->setInfoView($caption); }
return $panel; }
private function renderHeadsupActionList( PhabricatorRepositoryCommit $commit, PhabricatorRepository $repository) {
$request = $this->getRequest(); $user = $request->getUser();
$actions = id(new PhabricatorActionListView()) ->setUser($user) ->setObject($commit);
$can_edit = PhabricatorPolicyFilter::hasCapability( $user, $commit, PhabricatorPolicyCapability::CAN_EDIT);
$identifier = $commit->getCommitIdentifier(); $uri = $repository->getPathURI("commit/{$identifier}/edit/");
$action = id(new PhabricatorActionView()) ->setName(pht('Edit Commit')) ->setHref($uri) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); $actions->addAction($action);
require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector');
$maniphest = 'PhabricatorManiphestApplication'; if (PhabricatorApplication::isClassInstalled($maniphest)) { $action = id(new PhabricatorActionView()) ->setName(pht('Edit Maniphest Tasks')) ->setIcon('fa-anchor') ->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/') ->setWorkflow(true) ->setDisabled(!$can_edit); $actions->addAction($action); }
$action = id(new PhabricatorActionView()) ->setName(pht('Download Raw Diff')) ->setHref($request->getRequestURI()->alter('diff', true)) ->setIcon('fa-download'); $actions->addAction($action);
return $actions; }
private function buildRawDiffResponse(DiffusionRequest $drequest) { $raw_diff = $this->callConduitWithDiffusionRequest( 'diffusion.rawdiffquery', array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), ));
$file = PhabricatorFile::buildFromFileDataOrHash( $raw_diff, array( 'name' => $drequest->getCommit().'.diff', 'ttl' => (60 * 60 * 24), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, ));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file->attachToObject($drequest->getRepository()->getPHID()); unset($unguarded);
return $file->getRedirectResponse(); }
private function renderAuditStatusView(array $audit_requests) { assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest'); $viewer = $this->getViewer();
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
$view = new PHUIStatusListView(); foreach ($audit_requests as $request) { $code = $request->getAuditStatus(); $item = new PHUIStatusItemView(); $item->setIcon( PhabricatorAuditStatusConstants::getStatusIcon($code), PhabricatorAuditStatusConstants::getStatusColor($code), PhabricatorAuditStatusConstants::getStatusName($code));
$note = array(); foreach ($request->getAuditReasons() as $reason) { $note[] = phutil_tag('div', array(), $reason); } $item->setNote($note);
$auditor_phid = $request->getAuditorPHID(); $target = $viewer->renderHandle($auditor_phid); $item->setTarget($target);
if (isset($authority_map[$auditor_phid])) { $item->setHighlighted(true); }
$view->addItem($item); }
return $view; }
private function linkBugtraq($corpus) { $url = PhabricatorEnv::getEnvConfig('bugtraq.url'); if (!strlen($url)) { return $corpus; }
$regexes = PhabricatorEnv::getEnvConfig('bugtraq.logregex'); if (!$regexes) { return $corpus; }
$parser = id(new PhutilBugtraqParser()) ->setBugtraqPattern("[[ {$url} | %BUGID% ]]") ->setBugtraqCaptureExpression(array_shift($regexes));
$select = array_shift($regexes); if ($select) { $parser->setBugtraqSelectExpression($select); }
return $parser->processCorpus($corpus); }
private function buildTableOfContents(array $changesets) { $drequest = $this->getDiffusionRequest(); $viewer = $this->getViewer();
$toc_view = id(new PHUIDiffTableOfContentsListView()) ->setUser($viewer);
// TODO: This is hacky, we just want access to the linkX() methods on // DiffusionView. $diffusion_view = id(new DiffusionEmptyResultView()) ->setDiffusionRequest($drequest);
$have_owners = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorOwnersApplication', $viewer);
if (!$changesets) { $have_owners = false; }
if ($have_owners) { if ($viewer->getPHID()) { $packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) ->withAuthorityPHIDs(array($viewer->getPHID())) ->execute(); $toc_view->setAuthorityPackages($packages); }
$repository = $drequest->getRepository(); $repository_phid = $repository->getPHID();
$control_query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) ->withControl($repository_phid, mpull($changesets, 'getFilename')); $control_query->execute(); }
foreach ($changesets as $changeset_id => $changeset) { $path = $changeset->getFilename(); $anchor = substr(md5($path), 0, 8);
$history_link = $diffusion_view->linkHistory($path); $browse_link = $diffusion_view->linkBrowse( $path, array( 'type' => $changeset->getFileType(), ));
$item = id(new PHUIDiffTableOfContentsItemView()) ->setChangeset($changeset) ->setAnchor($anchor) ->setContext( array( $history_link, ' ', $browse_link, ));
if ($have_owners) { $packages = $control_query->getControllingPackagesForPath( $repository_phid, $changeset->getFilename()); $item->setPackages($packages); }
$toc_view->addItem($item); }
return $toc_view; }
private function loadCommitState() { $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $commit = $drequest->getCommit();
// TODO: We could use futures here and resolve these calls in parallel.
$exceptions = array();
try { $parent_refs = $this->callConduitWithDiffusionRequest( 'diffusion.commitparentsquery', array( 'commit' => $commit, ));
if ($parent_refs) { $parents = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withRepository($repository) ->withIdentifiers($parent_refs) ->execute(); } else { $parents = array(); }
$this->commitParents = $parents; } catch (Exception $ex) { $this->commitParents = false; $exceptions[] = $ex; }
$merge_limit = $this->getMergeDisplayLimit();
try { if ($repository->isSVN()) { $this->commitMerges = array(); } else { $merges = $this->callConduitWithDiffusionRequest( 'diffusion.mergedcommitsquery', array( 'commit' => $commit, 'limit' => $merge_limit + 1, )); $this->commitMerges = DiffusionPathChange::newFromConduit($merges); } } catch (Exception $ex) { $this->commitMerges = false; $exceptions[] = $ex; }
try { if ($repository->isGit()) { $refs = $this->callConduitWithDiffusionRequest( 'diffusion.refsquery', array( 'commit' => $commit, )); } else { $refs = array(); }
$this->commitRefs = $refs; } catch (Exception $ex) { $this->commitRefs = false; $exceptions[] = $ex; }
if ($exceptions) { $exists = $this->callConduitWithDiffusionRequest( 'diffusion.existsquery', array( 'commit' => $commit, ));
if ($exists) { $this->commitExists = true; foreach ($exceptions as $exception) { $this->commitErrors[] = $exception->getMessage(); } } else { $this->commitExists = false; $this->commitErrors[] = pht( 'This commit no longer exists in the repository. It may have '. 'been part of a branch which was deleted.'); } } else { $this->commitExists = true; $this->commitErrors = array(); } }
private function getMergeDisplayLimit() { return 50; }
private function getCommitExists() { if ($this->commitExists === null) { $this->loadCommitState(); }
return $this->commitExists; }
private function getCommitParents() { if ($this->commitParents === null) { $this->loadCommitState(); }
return $this->commitParents; }
private function getCommitRefs() { if ($this->commitRefs === null) { $this->loadCommitState(); }
return $this->commitRefs; }
private function getCommitMerges() { if ($this->commitMerges === null) { $this->loadCommitState(); }
return $this->commitMerges; }
private function getCommitErrors() { if ($this->commitErrors === null) { $this->loadCommitState(); }
return $this->commitErrors; }
}