Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/controller/DifferentialRevisionListController.php b/src/applications/differential/controller/DifferentialRevisionListController.php
index 39cc2a605b..331d353a1b 100644
--- a/src/applications/differential/controller/DifferentialRevisionListController.php
+++ b/src/applications/differential/controller/DifferentialRevisionListController.php
@@ -1,91 +1,87 @@
<?php
final class DifferentialRevisionListController extends DifferentialController
implements PhabricatorApplicationSearchResultsControllerInterface {
private $queryKey;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->queryKey = idx($data, 'queryKey');
}
public function processRequest() {
$request = $this->getRequest();
$controller = id(new PhabricatorApplicationSearchController($request))
->setQueryKey($this->queryKey)
->setSearchEngine(new DifferentialRevisionSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function renderResultsList(
array $revisions,
PhabricatorSavedQuery $query) {
assert_instances_of($revisions, 'DifferentialRevision');
$user = $this->getRequest()->getUser();
$template = id(new DifferentialRevisionListView())
->setUser($user)
->setFields(DifferentialRevisionListView::getDefaultFields($user));
$views = array();
if ($query->getQueryKey() == 'active') {
$split = DifferentialRevisionQuery::splitResponsible(
$revisions,
$query->getParameter('responsiblePHIDs'));
list($blocking, $active, $waiting) = $split;
$views[] = id(clone $template)
->setHeader(pht('Blocking Others'))
->setNoDataString(
pht('No revisions are blocked on your action.'))
->setHighlightAge(true)
->setRevisions($blocking)
- ->setHandles(array())
- ->loadAssets();
+ ->setHandles(array());
$views[] = id(clone $template)
->setHeader(pht('Action Required'))
->setNoDataString(
pht('No revisions require your action.'))
->setHighlightAge(true)
->setRevisions($active)
- ->setHandles(array())
- ->loadAssets();
+ ->setHandles(array());
$views[] = id(clone $template)
->setHeader(pht('Waiting on Others'))
->setNoDataString(
pht('You have no revisions waiting on others.'))
->setRevisions($waiting)
- ->setHandles(array())
- ->loadAssets();
+ ->setHandles(array());
} else {
$views[] = id(clone $template)
->setRevisions($revisions)
- ->setHandles(array())
- ->loadAssets();
+ ->setHandles(array());
}
$phids = array_mergev(mpull($views, 'getRequiredHandlePHIDs'));
$handles = $this->loadViewerHandles($phids);
foreach ($views as $view) {
$view->setHandles($handles);
}
if (count($views) == 1) {
// Reduce this to a PHUIObjectItemListView so we can get the free
// support from ApplicationSearch.
return head($views)->render();
} else {
return $views;
}
}
}
diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
index 494669e261..b4a8047e7b 100644
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1,967 +1,968 @@
<?php
final class DifferentialRevisionViewController extends DifferentialController {
private $revisionID;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer_is_anonymous = !$user->isLoggedIn();
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($this->revisionID))
->setViewer($request->getUser())
->needRelationships(true)
->needReviewerStatus(true)
->needReviewerAuthority(true)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$diffs = id(new DifferentialDiffQuery())
->setViewer($request->getUser())
->withRevisionIDs(array($this->revisionID))
->execute();
$diffs = array_reverse($diffs, $preserve_keys = true);
if (!$diffs) {
throw new Exception(
"This revision has no diffs. Something has gone quite wrong.");
}
$diff_vs = $request->getInt('vs');
$target_id = $request->getInt('id');
$target = idx($diffs, $target_id, end($diffs));
$target_manual = $target;
if (!$target_id) {
foreach ($diffs as $diff) {
if ($diff->getCreationMethod() != 'commit') {
$target_manual = $diff;
}
}
}
if (empty($diffs[$diff_vs])) {
$diff_vs = null;
}
$arc_project = $target->loadArcanistProject();
$repository = ($arc_project ? $arc_project->loadRepository() : null);
list($changesets, $vs_map, $vs_changesets, $rendering_references) =
$this->loadChangesetsAndVsMap(
$target,
idx($diffs, $diff_vs),
$repository);
if ($request->getExists('download')) {
return $this->buildRawDiffResponse(
$revision,
$changesets,
$vs_changesets,
$vs_map,
$repository);
}
$props = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d',
$target_manual->getID());
$props = mpull($props, 'getData', 'getName');
$aux_fields = $this->loadAuxiliaryFields($revision);
$comments = $revision->loadComments();
$all_changesets = $changesets;
$inlines = $this->loadInlineComments(
$revision,
$all_changesets);
$object_phids = array_merge(
$revision->getReviewers(),
$revision->getCCPHIDs(),
$revision->loadCommitPHIDs(),
array(
$revision->getAuthorPHID(),
$user->getPHID(),
),
mpull($comments, 'getAuthorPHID'));
foreach ($comments as $comment) {
foreach ($comment->getRequiredHandlePHIDs() as $phid) {
$object_phids[] = $phid;
}
}
foreach ($revision->getAttached() as $type => $phids) {
foreach ($phids as $phid => $info) {
$object_phids[] = $phid;
}
}
$aux_phids = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setDiff($target);
$aux_field->setManualDiff($target_manual);
$aux_field->setDiffProperties($props);
$aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView();
}
$object_phids = array_merge($object_phids, array_mergev($aux_phids));
$object_phids = array_unique($object_phids);
$handles = $this->loadViewerHandles($object_phids);
foreach ($aux_fields as $key => $aux_field) {
// Make sure each field only has access to handles it specifically
// requested, not all handles. Otherwise you can get a field which works
// only in the presence of other fields.
$aux_field->setHandles(array_select_keys($handles, $aux_phids[$key]));
}
$reviewer_warning = null;
if ($revision->getStatus() ==
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) {
$has_live_reviewer = false;
foreach ($revision->getReviewers() as $reviewer) {
if (!$handles[$reviewer]->isDisabled()) {
$has_live_reviewer = true;
break;
}
}
if (!$has_live_reviewer) {
$reviewer_warning = new AphrontErrorView();
$reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$reviewer_warning->setTitle(pht('No Active Reviewers'));
if ($revision->getReviewers()) {
$reviewer_warning->appendChild(
phutil_tag(
'p',
array(),
pht('All specified reviewers are disabled and this revision '.
'needs review. You may want to add some new reviewers.')));
} else {
$reviewer_warning->appendChild(
phutil_tag(
'p',
array(),
pht('This revision has no specified reviewers and needs '.
'review. You may want to add some reviewers.')));
}
}
}
$request_uri = $request->getRequestURI();
$limit = 100;
$large = $request->getStr('large');
if (count($changesets) > $limit && !$large) {
$count = count($changesets);
$warning = new AphrontErrorView();
$warning->setTitle('Very Large Diff');
$warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$warning->appendChild(hsprintf(
'%s <strong>%s</strong>',
pht(
'This diff is very large and affects %s files. Load each file '.
'individually.',
new PhutilNumber($count)),
phutil_tag(
'a',
array(
'href' => $request_uri
->alter('large', 'true')
->setFragment('toc'),
),
pht('Show All Files Inline'))));
$warning = $warning->render();
$my_inlines = id(new DifferentialInlineCommentQuery())
->withDraftComments($user->getPHID(), $this->revisionID)
->execute();
$visible_changesets = array();
foreach ($inlines + $my_inlines as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($changesets[$changeset_id])) {
$visible_changesets[$changeset_id] = $changesets[$changeset_id];
}
}
if (!empty($props['arc:lint'])) {
$changeset_paths = mpull($changesets, null, 'getFilename');
foreach ($props['arc:lint'] as $lint) {
$changeset = idx($changeset_paths, $lint['path']);
if ($changeset) {
$visible_changesets[$changeset->getID()] = $changeset;
}
}
}
} else {
$warning = null;
$visible_changesets = $changesets;
}
$revision_detail = id(new DifferentialRevisionDetailView())
->setUser($user)
->setRevision($revision)
->setDiff(end($diffs))
->setAuxiliaryFields($aux_fields)
->setURI($request->getRequestURI());
$actions = $this->getRevisionActions($revision);
$custom_renderer_class = PhabricatorEnv::getEnvConfig(
'differential.revision-custom-detail-renderer');
if ($custom_renderer_class) {
// TODO: build a better version of the action links and deprecate the
// whole DifferentialRevisionDetailRenderer class.
$custom_renderer = newv($custom_renderer_class, array());
$custom_renderer->setUser($user);
$custom_renderer->setDiff($target);
if ($diff_vs) {
$custom_renderer->setVSDiff($diffs[$diff_vs]);
}
$actions = array_merge(
$actions,
$custom_renderer->generateActionLinks($revision, $target_manual));
}
$whitespace = $request->getStr(
'whitespace',
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL);
if ($arc_project) {
list($symbol_indexes, $project_phids) = $this->buildSymbolIndexes(
$arc_project,
$visible_changesets);
} else {
$symbol_indexes = array();
$project_phids = null;
}
$revision_detail->setActions($actions);
$revision_detail->setUser($user);
$comment_view = $this->buildTransactions(
$revision,
$diff_vs ? $diffs[$diff_vs] : $target,
$target,
$all_changesets);
$wrap_id = celerity_generate_unique_node_id();
$comment_view = phutil_tag(
'div',
array(
'id' => $wrap_id,
),
$comment_view);
if ($arc_project) {
Javelin::initBehavior(
'repository-crossreference',
array(
'section' => $wrap_id,
'projects' => $project_phids,
));
}
$changeset_view = new DifferentialChangesetListView();
$changeset_view->setChangesets($changesets);
$changeset_view->setVisibleChangesets($visible_changesets);
if (!$viewer_is_anonymous) {
$changeset_view->setInlineCommentControllerURI(
'/differential/comment/inline/edit/'.$revision->getID().'/');
}
$changeset_view->setStandaloneURI('/differential/changeset/');
$changeset_view->setRawFileURIs(
'/differential/changeset/?view=old',
'/differential/changeset/?view=new');
$changeset_view->setUser($user);
$changeset_view->setDiff($target);
$changeset_view->setRenderingReferences($rendering_references);
$changeset_view->setVsMap($vs_map);
$changeset_view->setWhitespace($whitespace);
if ($repository) {
$changeset_view->setRepository($repository);
}
$changeset_view->setSymbolIndexes($symbol_indexes);
$changeset_view->setTitle('Diff '.$target->getID());
$diff_history = new DifferentialRevisionUpdateHistoryView();
$diff_history->setDiffs($diffs);
$diff_history->setSelectedVersusDiffID($diff_vs);
$diff_history->setSelectedDiffID($target->getID());
$diff_history->setSelectedWhitespace($whitespace);
$diff_history->setUser($user);
$local_view = new DifferentialLocalCommitsView();
$local_view->setUser($user);
$local_view->setLocalCommits(idx($props, 'local:commits'));
if ($repository) {
$other_revisions = $this->loadOtherRevisions(
$changesets,
$target,
$repository);
} else {
$other_revisions = array();
}
$other_view = null;
if ($other_revisions) {
$other_view = $this->renderOtherRevisions($other_revisions);
}
$toc_view = new DifferentialDiffTableOfContentsView();
$toc_view->setChangesets($changesets);
$toc_view->setVisibleChangesets($visible_changesets);
$toc_view->setRenderingReferences($rendering_references);
$toc_view->setUnitTestData(idx($props, 'arc:unit', array()));
if ($repository) {
$toc_view->setRepository($repository);
}
$toc_view->setDiff($target);
$toc_view->setUser($user);
$toc_view->setRevisionID($revision->getID());
$toc_view->setWhitespace($whitespace);
$comment_form = null;
if (!$viewer_is_anonymous) {
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'differential-comment-'.$revision->getID());
$reviewers = array();
$ccs = array();
if ($draft) {
$reviewers = idx($draft->getMetadata(), 'reviewers', array());
$ccs = idx($draft->getMetadata(), 'ccs', array());
if ($reviewers || $ccs) {
$handles = $this->loadViewerHandles(array_merge($reviewers, $ccs));
$reviewers = array_select_keys($handles, $reviewers);
$ccs = array_select_keys($handles, $ccs);
}
}
$comment_form = new DifferentialAddCommentView();
$comment_form->setRevision($revision);
$comment_form->setAuxFields($aux_fields);
$comment_form->setActions($this->getRevisionCommentActions($revision));
$comment_form->setActionURI('/differential/comment/save/');
$comment_form->setUser($user);
$comment_form->setDraft($draft);
$comment_form->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'));
$comment_form->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
// TODO: This just makes the "Z" key work. Generalize this and remove
// it at some point.
$comment_form = phutil_tag(
'div',
array(
'class' => 'differential-add-comment-panel',
),
$comment_form);
}
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
Javelin::initBehavior('differential-user-select');
$page_pane = id(new DifferentialPrimaryPaneView())
->setID($pane_id)
->appendChild(array(
$comment_view,
$diff_history,
$warning,
$local_view,
$toc_view,
$other_view,
$changeset_view,
));
if ($comment_form) {
$page_pane->appendChild($comment_form);
} else {
// TODO: For now, just use this to get "Login to Comment".
$page_pane->appendChild(
id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setRequestURI($request->getRequestURI()));
}
$object_id = 'D'.$revision->getID();
$top_anchor = id(new PhabricatorAnchorView())
->setAnchorName('top')
->setNavigationMarker(true);
$content = array(
$reviewer_warning,
$top_anchor,
$revision_detail,
$page_pane,
);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($object_id, '/'.$object_id);
$prefs = $user->loadPreferences();
$pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
if ($prefs->getPreference($pref_filetree)) {
$collapsed = $prefs->getPreference(
PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED,
false);
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setAnchorName('top')
->setTitle('D'.$revision->getID())
->setBaseURI(new PhutilURI('/D'.$revision->getID()))
->setCollapsed((bool)$collapsed)
->build($changesets);
$nav->appendChild($content);
$nav->setCrumbs($crumbs);
$content = $nav;
} else {
array_unshift($content, $crumbs);
}
return $this->buildApplicationPage(
$content,
array(
'title' => $object_id.' '.$revision->getTitle(),
'pageObjects' => array($revision->getPHID()),
));
}
private function getRevisionActions(DifferentialRevision $revision) {
$user = $this->getRequest()->getUser();
$viewer_phid = $user->getPHID();
$viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid);
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs());
$logged_in = $this->getRequest()->getUser()->isLoggedIn();
$status = $revision->getStatus();
$revision_id = $revision->getID();
$revision_phid = $revision->getPHID();
$links = array();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$revision,
PhabricatorPolicyCapability::CAN_EDIT);
$links[] = array(
'icon' => 'edit',
'href' => "/differential/revision/edit/{$revision_id}/",
'name' => pht('Edit Revision'),
'disabled' => !$can_edit,
'sigil' => $can_edit ? null : 'workflow',
);
if (!$viewer_is_owner && !$viewer_is_reviewer) {
$action = $viewer_is_cc ? 'rem' : 'add';
$links[] = array(
'icon' => $viewer_is_cc ? 'disable' : 'check',
'href' => "/differential/subscribe/{$action}/{$revision_id}/",
'name' => $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe'),
'instant' => $logged_in,
'disabled' => !$logged_in,
'sigil' => $can_edit ? null : 'workflow',
);
} else {
$links[] = array(
'icon' => 'enable',
'name' => pht('Automatically Subscribed'),
'disabled' => true,
);
}
$this->requireResource('phabricator-object-selector-css');
$this->requireResource('javelin-behavior-phabricator-object-selector');
$links[] = array(
'icon' => 'link',
'name' => pht('Edit Dependencies'),
'href' => "/search/attach/{$revision_phid}/DREV/dependencies/",
'sigil' => 'workflow',
'disabled' => !$can_edit,
);
$maniphest = 'PhabricatorApplicationManiphest';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$links[] = array(
'icon' => 'attach',
'name' => pht('Edit Maniphest Tasks'),
'href' => "/search/attach/{$revision_phid}/TASK/",
'sigil' => 'workflow',
'disabled' => !$can_edit,
);
}
$request_uri = $this->getRequest()->getRequestURI();
$links[] = array(
'icon' => 'download',
'name' => pht('Download Raw Diff'),
'href' => $request_uri->alter('download', 'true')
);
return $links;
}
private function getRevisionCommentActions(DifferentialRevision $revision) {
$actions = array(
DifferentialAction::ACTION_COMMENT => true,
);
$viewer = $this->getRequest()->getUser();
$viewer_phid = $viewer->getPHID();
$viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$status = $revision->getStatus();
$viewer_has_accepted = false;
$viewer_has_rejected = false;
$status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
$status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
foreach ($revision->getReviewerStatus() as $reviewer) {
if ($reviewer->getReviewerPHID() == $viewer_phid) {
if ($reviewer->getStatus() == $status_accepted) {
$viewer_has_accepted = true;
}
if ($reviewer->getStatus() == $status_rejected) {
$viewer_has_rejected = true;
}
break;
}
}
$allow_self_accept = PhabricatorEnv::getEnvConfig(
'differential.allow-self-accept');
$always_allow_close = PhabricatorEnv::getEnvConfig(
'differential.always-allow-close');
$allow_reopen = PhabricatorEnv::getEnvConfig(
'differential.allow-reopen');
if ($viewer_is_owner) {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
$actions[DifferentialAction::ACTION_CLOSE] = true;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
$actions[DifferentialAction::ACTION_RECLAIM] = true;
break;
}
} else {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
case ArcanistDifferentialRevisionStatus::ABANDONED:
break;
}
if ($status != ArcanistDifferentialRevisionStatus::CLOSED) {
$actions[DifferentialAction::ACTION_CLAIM] = true;
$actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close;
}
}
$actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
$actions[DifferentialAction::ACTION_ADDCCS] = true;
$actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen &&
($status == ArcanistDifferentialRevisionStatus::CLOSED);
$actions = array_keys(array_filter($actions));
$actions_dict = array();
foreach ($actions as $action) {
$actions_dict[$action] = DifferentialAction::getActionVerb($action);
}
return $actions_dict;
}
private function loadInlineComments(
DifferentialRevision $revision,
array &$changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$inline_comments = array();
$inline_comments = id(new DifferentialInlineCommentQuery())
->withRevisionIDs(array($revision->getID()))
->withNotDraft(true)
->execute();
$load_changesets = array();
foreach ($inline_comments as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($changesets[$changeset_id])) {
continue;
}
$load_changesets[$changeset_id] = true;
}
$more_changesets = array();
if ($load_changesets) {
$changeset_ids = array_keys($load_changesets);
$more_changesets += id(new DifferentialChangeset())
->loadAllWhere(
'id IN (%Ld)',
$changeset_ids);
}
if ($more_changesets) {
$changesets += $more_changesets;
$changesets = msort($changesets, 'getSortKey');
}
return $inline_comments;
}
private function loadChangesetsAndVsMap(
DifferentialDiff $target,
DifferentialDiff $diff_vs = null,
PhabricatorRepository $repository = null) {
$load_ids = array();
if ($diff_vs) {
$load_ids[] = $diff_vs->getID();
}
$load_ids[] = $target->getID();
$raw_changesets = id(new DifferentialChangeset())
->loadAllWhere(
'diffID IN (%Ld)',
$load_ids);
$changeset_groups = mgroup($raw_changesets, 'getDiffID');
$changesets = idx($changeset_groups, $target->getID(), array());
$changesets = mpull($changesets, null, 'getID');
$refs = array();
$vs_map = array();
$vs_changesets = array();
if ($diff_vs) {
$vs_id = $diff_vs->getID();
$vs_changesets_path_map = array();
foreach (idx($changeset_groups, $vs_id, array()) as $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $diff_vs);
$vs_changesets_path_map[$path] = $changeset;
$vs_changesets[$changeset->getID()] = $changeset;
}
foreach ($changesets as $key => $changeset) {
$path = $changeset->getAbsoluteRepositoryPath($repository, $target);
if (isset($vs_changesets_path_map[$path])) {
$vs_map[$changeset->getID()] =
$vs_changesets_path_map[$path]->getID();
$refs[$changeset->getID()] =
$changeset->getID().'/'.$vs_changesets_path_map[$path]->getID();
unset($vs_changesets_path_map[$path]);
} else {
$refs[$changeset->getID()] = $changeset->getID();
}
}
foreach ($vs_changesets_path_map as $path => $changeset) {
$changesets[$changeset->getID()] = $changeset;
$vs_map[$changeset->getID()] = -1;
$refs[$changeset->getID()] = $changeset->getID().'/-1';
}
} else {
foreach ($changesets as $changeset) {
$refs[$changeset->getID()] = $changeset->getID();
}
}
$changesets = msort($changesets, 'getSortKey');
return array($changesets, $vs_map, $vs_changesets, $refs);
}
private function loadAuxiliaryFields(DifferentialRevision $revision) {
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
if (!$aux_field->shouldAppearOnRevisionView()) {
unset($aux_fields[$key]);
} else {
$aux_field->setUser($this->getRequest()->getUser());
}
}
$aux_fields = DifferentialAuxiliaryField::loadFromStorage(
$revision,
$aux_fields);
return $aux_fields;
}
private function buildSymbolIndexes(
PhabricatorRepositoryArcanistProject $arc_project,
array $visible_changesets) {
assert_instances_of($visible_changesets, 'DifferentialChangeset');
$engine = PhabricatorSyntaxHighlighter::newEngine();
$langs = $arc_project->getSymbolIndexLanguages();
if (!$langs) {
return array(array(), array());
}
$symbol_indexes = array();
$project_phids = array_merge(
array($arc_project->getPHID()),
nonempty($arc_project->getSymbolIndexProjects(), array()));
$indexed_langs = array_fill_keys($langs, true);
foreach ($visible_changesets as $key => $changeset) {
$lang = $engine->getLanguageFromFilename($changeset->getFilename());
if (isset($indexed_langs[$lang])) {
$symbol_indexes[$key] = array(
'lang' => $lang,
'projects' => $project_phids,
);
}
}
return array($symbol_indexes, $project_phids);
}
private function loadOtherRevisions(
array $changesets,
DifferentialDiff $target,
PhabricatorRepository $repository) {
assert_instances_of($changesets, 'DifferentialChangeset');
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $changeset->getAbsoluteRepositoryPath(
$repository,
$target);
}
if (!$paths) {
return array();
}
$path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
if (!$path_map) {
return array();
}
$query = id(new DifferentialRevisionQuery())
->setViewer($this->getRequest()->getUser())
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->setOrder(DifferentialRevisionQuery::ORDER_PATH_MODIFIED)
->setLimit(10)
+ ->needFlags(true)
+ ->needDrafts(true)
->needRelationships(true);
foreach ($path_map as $path => $path_id) {
$query->withPath($repository->getID(), $path_id);
}
$results = $query->execute();
// Strip out *this* revision.
foreach ($results as $key => $result) {
if ($result->getID() == $this->revisionID) {
unset($results[$key]);
}
}
return $results;
}
private function renderOtherRevisions(array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$user = $this->getRequest()->getUser();
$view = id(new DifferentialRevisionListView())
->setRevisions($revisions)
->setFields(DifferentialRevisionListView::getDefaultFields($user))
- ->setUser($user)
- ->loadAssets();
+ ->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Open Revisions Affecting These Files'))
->appendChild($view);
}
/**
* Note this code is somewhat similar to the buildPatch method in
* @{class:DifferentialReviewRequestMail}.
*
* @return @{class:AphrontRedirectResponse}
*/
private function buildRawDiffResponse(
DifferentialRevision $revision,
array $changesets,
array $vs_changesets,
array $vs_map,
PhabricatorRepository $repository = null) {
assert_instances_of($changesets, 'DifferentialChangeset');
assert_instances_of($vs_changesets, 'DifferentialChangeset');
$viewer = $this->getRequest()->getUser();
foreach ($changesets as $changeset) {
$changeset->attachHunks($changeset->loadHunks());
}
$diff = new DifferentialDiff();
$diff->attachChangesets($changesets);
$raw_changes = $diff->buildChangesList();
$changes = array();
foreach ($raw_changes as $changedict) {
$changes[] = ArcanistDiffChange::newFromDictionary($changedict);
}
$loader = id(new PhabricatorFileBundleLoader())
->setViewer($viewer);
$bundle = ArcanistBundle::newFromChanges($changes);
$bundle->setLoadFileDataCallback(array($loader, 'loadFileData'));
$vcs = $repository ? $repository->getVersionControlSystem() : null;
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$raw_diff = $bundle->toGitPatch();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
default:
$raw_diff = $bundle->toUnifiedDiff();
break;
}
$request_uri = $this->getRequest()->getRequestURI();
// this ends up being something like
// D123.diff
// or the verbose
// D123.vs123.id123.whitespaceignore-all.diff
// lame but nice to include these options
$file_name = ltrim($request_uri->getPath(), '/').'.';
foreach ($request_uri->getQueryParams() as $key => $value) {
if ($key == 'download') {
continue;
}
$file_name .= $key.$value.'.';
}
$file_name .= 'diff';
$file = PhabricatorFile::buildFromFileDataOrHash(
$raw_diff,
array(
'name' => $file_name,
'ttl' => (60 * 60 * 24),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$file->attachToObject(
$this->getRequest()->getUser(),
$revision->getPHID());
unset($unguarded);
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
private function buildTransactions(
DifferentialRevision $revision,
DifferentialDiff $left_diff,
DifferentialDiff $right_diff,
array $changesets) {
$viewer = $this->getRequest()->getUser();
$xactions = id(new DifferentialTransactionQuery())
->setViewer($viewer)
->withObjectPHIDs(array($revision->getPHID()))
->needComments(true)
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
$timeline = id(new DifferentialTransactionView())
->setUser($viewer)
->setObjectPHID($revision->getPHID())
->setChangesets($changesets)
->setRevision($revision)
->setLeftDiff($left_diff)
->setRightDiff($right_diff)
->setTransactions($xactions);
// TODO: Make this work and restore edit links. We need to copy
// `revisionPHID` to the new version of the comment. This should be simple,
// but can happen post-merge. See T2222.
$timeline->setShowEditActions(false);
return $timeline;
}
}
diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php
index c942fad813..0277cc4518 100644
--- a/src/applications/differential/query/DifferentialRevisionQuery.php
+++ b/src/applications/differential/query/DifferentialRevisionQuery.php
@@ -1,1116 +1,1161 @@
<?php
/**
* Flexible query API for Differential revisions. Example:
*
* // Load open revisions
* $revisions = id(new DifferentialRevisionQuery())
* ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
* ->execute();
*
* @task config Query Configuration
* @task exec Query Execution
* @task internal Internals
*/
final class DifferentialRevisionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $pathIDs = array();
private $status = 'status-any';
const STATUS_ANY = 'status-any';
const STATUS_OPEN = 'status-open';
const STATUS_ACCEPTED = 'status-accepted';
const STATUS_NEEDS_REVIEW = 'status-needs-review';
const STATUS_NEEDS_REVISION = 'status-needs-revision';
const STATUS_CLOSED = 'status-closed';
const STATUS_ABANDONED = 'status-abandoned';
private $authors = array();
private $draftAuthors = array();
private $ccs = array();
private $reviewers = array();
private $revIDs = array();
private $commitHashes = array();
private $phids = array();
private $responsibles = array();
private $branches = array();
private $arcanistProjectPHIDs = array();
private $repositoryPHIDs;
private $order = 'order-modified';
const ORDER_MODIFIED = 'order-modified';
const ORDER_CREATED = 'order-created';
/**
* This is essentially a denormalized copy of the revision modified time that
* should perform better for path queries with a LIMIT. Critically, when you
* browse "/", every revision in that repository for all time will match so
* the query benefits from being able to stop before fully materializing the
* result set.
*/
const ORDER_PATH_MODIFIED = 'order-path-modified';
private $needRelationships = false;
private $needActiveDiffs = false;
private $needDiffIDs = false;
private $needCommitPHIDs = false;
private $needHashes = false;
private $needReviewerStatus = false;
private $needReviewerAuthority;
+ private $needDrafts;
+ private $needFlags;
private $buildingGlobalOrder;
/* -( Query Configuration )------------------------------------------------ */
/**
* Filter results to revisions which affect a Diffusion path ID in a given
* repository. You can call this multiple times to select revisions for
* several paths.
*
* @param int Diffusion repository ID.
* @param int Diffusion path ID.
* @return this
* @task config
*/
public function withPath($repository_id, $path_id) {
$this->pathIDs[] = array(
'repositoryID' => $repository_id,
'pathID' => $path_id,
);
return $this;
}
/**
* Filter results to revisions authored by one of the given PHIDs. Calling
* this function will clear anything set by previous calls to
* @{method:withAuthors}.
*
* @param array List of PHIDs of authors
* @return this
* @task config
*/
public function withAuthors(array $author_phids) {
$this->authors = $author_phids;
return $this;
}
/**
* Filter results to revisions with comments authored bythe given PHIDs
*
* @param array List of PHIDs of authors
* @return this
* @task config
*/
public function withDraftRepliesByAuthors(array $author_phids) {
$this->draftAuthors = $author_phids;
return $this;
}
/**
* Filter results to revisions which CC one of the listed people. Calling this
* function will clear anything set by previous calls to @{method:withCCs}.
*
* @param array List of PHIDs of subscribers.
* @return this
* @task config
*/
public function withCCs(array $cc_phids) {
$this->ccs = $cc_phids;
return $this;
}
/**
* Filter results to revisions that have one of the provided PHIDs as
* reviewers. Calling this function will clear anything set by previous calls
* to @{method:withReviewers}.
*
* @param array List of PHIDs of reviewers
* @return this
* @task config
*/
public function withReviewers(array $reviewer_phids) {
$this->reviewers = $reviewer_phids;
return $this;
}
/**
* Filter results to revisions that have one of the provided commit hashes.
* Calling this function will clear anything set by previous calls to
* @{method:withCommitHashes}.
*
* @param array List of pairs <Class
* ArcanistDifferentialRevisionHash::HASH_$type constant,
* hash>
* @return this
* @task config
*/
public function withCommitHashes(array $commit_hashes) {
$this->commitHashes = $commit_hashes;
return $this;
}
/**
* Filter results to revisions with a given status. Provide a class constant,
* such as ##DifferentialRevisionQuery::STATUS_OPEN##.
*
* @param const Class STATUS constant, like STATUS_OPEN.
* @return this
* @task config
*/
public function withStatus($status_constant) {
$this->status = $status_constant;
return $this;
}
/**
* Filter results to revisions on given branches.
*
* @param list List of branch names.
* @return this
* @task config
*/
public function withBranches(array $branches) {
$this->branches = $branches;
return $this;
}
/**
* Filter results to only return revisions whose ids are in the given set.
*
* @param array List of revision ids
* @return this
* @task config
*/
public function withIDs(array $ids) {
$this->revIDs = $ids;
return $this;
}
/**
* Filter results to only return revisions whose PHIDs are in the given set.
*
* @param array List of revision PHIDs
* @return this
* @task config
*/
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
/**
* Given a set of users, filter results to return only revisions they are
* responsible for (i.e., they are either authors or reviewers).
*
* @param array List of user PHIDs.
* @return this
* @task config
*/
public function withResponsibleUsers(array $responsible_phids) {
$this->responsibles = $responsible_phids;
return $this;
}
/**
* Filter results to only return revisions with a given set of arcanist
* projects.
*
* @param array List of project PHIDs.
* @return this
* @task config
*/
public function withArcanistProjectPHIDs(array $arc_project_phids) {
$this->arcanistProjectPHIDs = $arc_project_phids;
return $this;
}
public function withRepositoryPHIDs(array $repository_phids) {
$this->repositoryPHIDs = $repository_phids;
return $this;
}
/**
* Set result ordering. Provide a class constant, such as
* ##DifferentialRevisionQuery::ORDER_CREATED##.
*
* @task config
*/
public function setOrder($order_constant) {
$this->order = $order_constant;
return $this;
}
/**
* Set whether or not the query will load and attach relationships.
*
* @param bool True to load and attach relationships.
* @return this
* @task config
*/
public function needRelationships($need_relationships) {
$this->needRelationships = $need_relationships;
return $this;
}
/**
* Set whether or not the query should load the active diff for each
* revision.
*
* @param bool True to load and attach diffs.
* @return this
* @task config
*/
public function needActiveDiffs($need_active_diffs) {
$this->needActiveDiffs = $need_active_diffs;
return $this;
}
/**
* Set whether or not the query should load the associated commit PHIDs for
* each revision.
*
* @param bool True to load and attach diffs.
* @return this
* @task config
*/
public function needCommitPHIDs($need_commit_phids) {
$this->needCommitPHIDs = $need_commit_phids;
return $this;
}
/**
* Set whether or not the query should load associated diff IDs for each
* revision.
*
* @param bool True to load and attach diff IDs.
* @return this
* @task config
*/
public function needDiffIDs($need_diff_ids) {
$this->needDiffIDs = $need_diff_ids;
return $this;
}
/**
* Set whether or not the query should load associated commit hashes for each
* revision.
*
* @param bool True to load and attach commit hashes.
* @return this
* @task config
*/
public function needHashes($need_hashes) {
$this->needHashes = $need_hashes;
return $this;
}
/**
* Set whether or not the query should load associated reviewer status.
*
* @param bool True to load and attach reviewers.
* @return this
* @task config
*/
public function needReviewerStatus($need_reviewer_status) {
$this->needReviewerStatus = $need_reviewer_status;
return $this;
}
/**
* Request information about the viewer's authority to act on behalf of each
* reviewer. In particular, they have authority to act on behalf of projects
* they are a member of.
*
* @param bool True to load and attach authority.
* @return this
* @task config
*/
public function needReviewerAuthority($need_reviewer_authority) {
$this->needReviewerAuthority = $need_reviewer_authority;
return $this;
}
+ public function needFlags($need_flags) {
+ $this->needFlags = $need_flags;
+ return $this;
+ }
+
+ public function needDrafts($need_drafts) {
+ $this->needDrafts = $need_drafts;
+ return $this;
+ }
+
/* -( Query Execution )---------------------------------------------------- */
/**
* Execute the query as configured, returning matching
* @{class:DifferentialRevision} objects.
*
* @return list List of matching DifferentialRevision objects.
* @task exec
*/
public function loadPage() {
$table = new DifferentialRevision();
$conn_r = $table->establishConnection('r');
$data = $this->loadData();
return $table->loadAllFromArray($data);
}
public function willFilterPage(array $revisions) {
$viewer = $this->getViewer();
$repository_phids = mpull($revisions, 'getRepositoryPHID');
$repository_phids = array_filter($repository_phids);
$repositories = array();
if ($repository_phids) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
}
// If a revision is associated with a repository:
//
// - the viewer must be able to see the repository; or
// - the viewer must have an automatic view capability.
//
// In the latter case, we'll load the revision but not load the repository.
$can_view = PhabricatorPolicyCapability::CAN_VIEW;
foreach ($revisions as $key => $revision) {
$repo_phid = $revision->getRepositoryPHID();
if (!$repo_phid) {
// The revision has no associated repository. Attach `null` and move on.
$revision->attachRepository(null);
continue;
}
$repository = idx($repositories, $repo_phid);
if ($repository) {
// The revision has an associated repository, and the viewer can see
// it. Attach it and move on.
$revision->attachRepository($repository);
continue;
}
if ($revision->hasAutomaticCapability($can_view, $viewer)) {
// The revision has an associated repository which the viewer can not
// see, but the viewer has an automatic capability on this revision.
// Load the revision without attaching a repository.
$revision->attachRepository(null);
continue;
}
if ($this->getViewer()->isOmnipotent()) {
// The viewer is omnipotent. Allow the revision to load even without
// a repository.
$revision->attachRepository(null);
continue;
}
// The revision has an associated repository, and the viewer can't see
// it, and the viewer has no special capabilities. Filter out this
// revision.
$this->didRejectResult($revision);
unset($revisions[$key]);
}
if (!$revisions) {
return array();
}
$table = new DifferentialRevision();
$conn_r = $table->establishConnection('r');
if ($this->needRelationships) {
$this->loadRelationships($conn_r, $revisions);
}
if ($this->needCommitPHIDs) {
$this->loadCommitPHIDs($conn_r, $revisions);
}
$need_active = $this->needActiveDiffs;
$need_ids = $need_active || $this->needDiffIDs;
if ($need_ids) {
$this->loadDiffIDs($conn_r, $revisions);
}
if ($need_active) {
$this->loadActiveDiffs($conn_r, $revisions);
}
if ($this->needHashes) {
$this->loadHashes($conn_r, $revisions);
}
if ($this->needReviewerStatus || $this->needReviewerAuthority) {
$this->loadReviewers($conn_r, $revisions);
}
return $revisions;
}
+ protected function didFilterPage(array $revisions) {
+ $viewer = $this->getViewer();
+
+ if ($this->needFlags) {
+ $flags = id(new PhabricatorFlagQuery())
+ ->setViewer($viewer)
+ ->withOwnerPHIDs(array($viewer->getPHID()))
+ ->withObjectPHIDs(mpull($revisions, 'getPHID'))
+ ->execute();
+ $flags = mpull($flags, null, 'getObjectPHID');
+ foreach ($revisions as $revision) {
+ $revision->attachFlag(
+ $viewer,
+ idx($flags, $revision->getPHID()));
+ }
+ }
+
+ if ($this->needDrafts) {
+ $drafts = id(new DifferentialDraft())->loadAllWhere(
+ 'authorPHID = %s AND objectPHID IN (%Ls)',
+ $viewer->getPHID(),
+ mpull($revisions, 'getPHID'));
+ $drafts = mgroup($drafts, 'getObjectPHID');
+ foreach ($revisions as $revision) {
+ $revision->attachDrafts(
+ $viewer,
+ idx($drafts, $revision->getPHID(), array()));
+ }
+ }
+
+ return $revisions;
+ }
+
private function loadData() {
$table = new DifferentialRevision();
$conn_r = $table->establishConnection('r');
$selects = array();
// NOTE: If the query includes "responsiblePHIDs", we execute it as a
// UNION of revisions they own and revisions they're reviewing. This has
// much better performance than doing it with JOIN/WHERE.
if ($this->responsibles) {
$basic_authors = $this->authors;
$basic_reviewers = $this->reviewers;
$authority_projects = id(new PhabricatorProjectQuery())
->setViewer($this->getViewer())
->withMemberPHIDs($this->responsibles)
->execute();
$authority_phids = mpull($authority_projects, 'getPHID');
try {
// Build the query where the responsible users are authors.
$this->authors = array_merge($basic_authors, $this->responsibles);
$this->reviewers = $basic_reviewers;
$selects[] = $this->buildSelectStatement($conn_r);
// Build the query where the responsible users are reviewers, or
// projects they are members of are reviewers.
$this->authors = $basic_authors;
$this->reviewers = array_merge(
$basic_reviewers,
$this->responsibles,
$authority_phids);
$selects[] = $this->buildSelectStatement($conn_r);
// Put everything back like it was.
$this->authors = $basic_authors;
$this->reviewers = $basic_reviewers;
} catch (Exception $ex) {
$this->authors = $basic_authors;
$this->reviewers = $basic_reviewers;
throw $ex;
}
} else {
$selects[] = $this->buildSelectStatement($conn_r);
}
if (count($selects) > 1) {
$this->buildingGlobalOrder = true;
$query = qsprintf(
$conn_r,
'%Q %Q %Q',
implode(' UNION DISTINCT ', $selects),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
} else {
$query = head($selects);
}
return queryfx_all($conn_r, '%Q', $query);
}
private function buildSelectStatement(AphrontDatabaseConnection $conn_r) {
$table = new DifferentialRevision();
$select = qsprintf(
$conn_r,
'SELECT r.* FROM %T r',
$table->getTableName());
$joins = $this->buildJoinsClause($conn_r);
$where = $this->buildWhereClause($conn_r);
$group_by = $this->buildGroupByClause($conn_r);
$this->buildingGlobalOrder = false;
$order_by = $this->buildOrderClause($conn_r);
$limit = $this->buildLimitClause($conn_r);
return qsprintf(
$conn_r,
'(%Q %Q %Q %Q %Q %Q)',
$select,
$joins,
$where,
$group_by,
$order_by,
$limit);
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
private function buildJoinsClause($conn_r) {
$joins = array();
if ($this->pathIDs) {
$path_table = new DifferentialAffectedPath();
$joins[] = qsprintf(
$conn_r,
'JOIN %T p ON p.revisionID = r.id',
$path_table->getTableName());
}
if ($this->commitHashes) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T hash_rel ON hash_rel.revisionID = r.id',
ArcanistDifferentialRevisionHash::TABLE_NAME);
}
if ($this->ccs) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T e_ccs ON e_ccs.src = r.phid '.
'AND e_ccs.type = %s '.
'AND e_ccs.dst in (%Ls)',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER,
$this->ccs);
}
if ($this->reviewers) {
$joins[] = qsprintf(
$conn_r,
'JOIN %T e_reviewers ON e_reviewers.src = r.phid '.
'AND e_reviewers.type = %s '.
'AND e_reviewers.dst in (%Ls)',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER,
$this->reviewers);
}
if ($this->draftAuthors) {
$differential_draft = new DifferentialDraft();
$joins[] = qsprintf(
$conn_r,
'JOIN %T has_draft ON has_draft.objectPHID = r.phid '.
'AND has_draft.authorPHID IN (%Ls)',
$differential_draft->getTableName(),
$this->draftAuthors);
}
$joins = implode(' ', $joins);
return $joins;
}
/**
* @task internal
*/
private function buildWhereClause($conn_r) {
$where = array();
if ($this->pathIDs) {
$path_clauses = array();
$repo_info = igroup($this->pathIDs, 'repositoryID');
foreach ($repo_info as $repository_id => $paths) {
$path_clauses[] = qsprintf(
$conn_r,
'(p.repositoryID = %d AND p.pathID IN (%Ld))',
$repository_id,
ipull($paths, 'pathID'));
}
$path_clauses = '('.implode(' OR ', $path_clauses).')';
$where[] = $path_clauses;
}
if ($this->authors) {
$where[] = qsprintf(
$conn_r,
'r.authorPHID IN (%Ls)',
$this->authors);
}
if ($this->revIDs) {
$where[] = qsprintf(
$conn_r,
'r.id IN (%Ld)',
$this->revIDs);
}
if ($this->repositoryPHIDs) {
$where[] = qsprintf(
$conn_r,
'r.repositoryPHID IN (%Ls)',
$this->repositoryPHIDs);
}
if ($this->commitHashes) {
$hash_clauses = array();
foreach ($this->commitHashes as $info) {
list($type, $hash) = $info;
$hash_clauses[] = qsprintf(
$conn_r,
'(hash_rel.type = %s AND hash_rel.hash = %s)',
$type,
$hash);
}
$hash_clauses = '('.implode(' OR ', $hash_clauses).')';
$where[] = $hash_clauses;
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'r.phid IN (%Ls)',
$this->phids);
}
if ($this->branches) {
$where[] = qsprintf(
$conn_r,
'r.branchName in (%Ls)',
$this->branches);
}
if ($this->arcanistProjectPHIDs) {
$where[] = qsprintf(
$conn_r,
'r.arcanistProjectPHID in (%Ls)',
$this->arcanistProjectPHIDs);
}
switch ($this->status) {
case self::STATUS_ANY:
break;
case self::STATUS_OPEN:
$where[] = qsprintf(
$conn_r,
'r.status IN (%Ld)',
DifferentialRevisionStatus::getOpenStatuses());
break;
case self::STATUS_NEEDS_REVIEW:
$where[] = qsprintf(
$conn_r,
'r.status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW,
));
break;
case self::STATUS_NEEDS_REVISION:
$where[] = qsprintf(
$conn_r,
'r.status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::NEEDS_REVISION,
));
break;
case self::STATUS_ACCEPTED:
$where[] = qsprintf(
$conn_r,
'r.status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::ACCEPTED,
));
break;
case self::STATUS_CLOSED:
$where[] = qsprintf(
$conn_r,
'r.status IN (%Ld)',
DifferentialRevisionStatus::getClosedStatuses());
break;
case self::STATUS_ABANDONED:
$where[] = qsprintf(
$conn_r,
'r.status IN (%Ld)',
array(
ArcanistDifferentialRevisionStatus::ABANDONED,
));
break;
default:
throw new Exception(
"Unknown revision status filter constant '{$this->status}'!");
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
/**
* @task internal
*/
private function buildGroupByClause($conn_r) {
$join_triggers = array_merge(
$this->pathIDs,
$this->ccs,
$this->reviewers);
$needs_distinct = (count($join_triggers) > 1);
if ($needs_distinct) {
return 'GROUP BY r.id';
} else {
return '';
}
}
private function loadCursorObject($id) {
$results = id(new DifferentialRevisionQuery())
->setViewer($this->getPagingViewer())
->withIDs(array((int)$id))
->execute();
return head($results);
}
protected function buildPagingClause(AphrontDatabaseConnection $conn_r) {
$default = parent::buildPagingClause($conn_r);
$before_id = $this->getBeforeID();
$after_id = $this->getAfterID();
if (!$before_id && !$after_id) {
return $default;
}
if ($before_id) {
$cursor = $this->loadCursorObject($before_id);
} else {
$cursor = $this->loadCursorObject($after_id);
}
if (!$cursor) {
return null;
}
$columns = array();
switch ($this->order) {
case self::ORDER_CREATED:
return $default;
case self::ORDER_MODIFIED:
$columns[] = array(
'name' => 'r.dateModified',
'value' => $cursor->getDateModified(),
'type' => 'int',
);
break;
case self::ORDER_PATH_MODIFIED:
$columns[] = array(
'name' => 'p.epoch',
'value' => $cursor->getDateCreated(),
'type' => 'int',
);
break;
}
$columns[] = array(
'name' => 'r.id',
'value' => $cursor->getID(),
'type' => 'int',
);
return $this->buildPagingClauseFromMultipleColumns(
$conn_r,
$columns,
array(
'reversed' => (bool)($before_id xor $this->getReversePaging()),
));
}
protected function getPagingColumn() {
$is_global = $this->buildingGlobalOrder;
switch ($this->order) {
case self::ORDER_MODIFIED:
if ($is_global) {
return 'dateModified';
}
return 'r.dateModified';
case self::ORDER_CREATED:
if ($is_global) {
return 'id';
}
return 'r.id';
case self::ORDER_PATH_MODIFIED:
if (!$this->pathIDs) {
throw new Exception(
"To use ORDER_PATH_MODIFIED, you must specify withPath().");
}
return 'p.epoch';
default:
throw new Exception("Unknown query order constant '{$this->order}'.");
}
}
private function loadRelationships($conn_r, array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$type_reviewer = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER;
$type_subscriber = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER;
$edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($revisions, 'getPHID'))
->withEdgeTypes(array($type_reviewer, $type_subscriber))
->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST)
->execute();
$type_map = array(
DifferentialRevision::RELATION_REVIEWER => $type_reviewer,
DifferentialRevision::RELATION_SUBSCRIBED => $type_subscriber,
);
foreach ($revisions as $revision) {
$data = array();
foreach ($type_map as $rel_type => $edge_type) {
$revision_edges = $edges[$revision->getPHID()][$edge_type];
foreach ($revision_edges as $dst_phid => $edge_data) {
$data[] = array(
'relation' => $rel_type,
'objectPHID' => $dst_phid,
'reasonPHID' => null,
);
}
}
$revision->attachRelationships($data);
}
}
private function loadCommitPHIDs($conn_r, array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$commit_phids = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE revisionID IN (%Ld)',
DifferentialRevision::TABLE_COMMIT,
mpull($revisions, 'getID'));
$commit_phids = igroup($commit_phids, 'revisionID');
foreach ($revisions as $revision) {
$phids = idx($commit_phids, $revision->getID(), array());
$phids = ipull($phids, 'commitPHID');
$revision->attachCommitPHIDs($phids);
}
}
private function loadDiffIDs($conn_r, array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$diff_table = new DifferentialDiff();
$diff_ids = queryfx_all(
$conn_r,
'SELECT revisionID, id FROM %T WHERE revisionID IN (%Ld)
ORDER BY id DESC',
$diff_table->getTableName(),
mpull($revisions, 'getID'));
$diff_ids = igroup($diff_ids, 'revisionID');
foreach ($revisions as $revision) {
$ids = idx($diff_ids, $revision->getID(), array());
$ids = ipull($ids, 'id');
$revision->attachDiffIDs($ids);
}
}
private function loadActiveDiffs($conn_r, array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$diff_table = new DifferentialDiff();
$load_ids = array();
foreach ($revisions as $revision) {
$diffs = $revision->getDiffIDs();
if ($diffs) {
$load_ids[] = max($diffs);
}
}
$active_diffs = array();
if ($load_ids) {
$active_diffs = $diff_table->loadAllWhere(
'id IN (%Ld)',
$load_ids);
}
$active_diffs = mpull($active_diffs, null, 'getRevisionID');
foreach ($revisions as $revision) {
$revision->attachActiveDiff(idx($active_diffs, $revision->getID()));
}
}
private function loadHashes(
AphrontDatabaseConnection $conn_r,
array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T WHERE revisionID IN (%Ld)',
'differential_revisionhash',
mpull($revisions, 'getID'));
$data = igroup($data, 'revisionID');
foreach ($revisions as $revision) {
$hashes = idx($data, $revision->getID(), array());
$list = array();
foreach ($hashes as $hash) {
$list[] = array($hash['type'], $hash['hash']);
}
$revision->attachHashes($list);
}
}
private function loadReviewers(
AphrontDatabaseConnection $conn_r,
array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$edge_type = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER;
$edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($revisions, 'getPHID'))
->withEdgeTypes(array($edge_type))
->needEdgeData(true)
->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST)
->execute();
$viewer = $this->getViewer();
$viewer_phid = $viewer->getPHID();
// Figure out which of these reviewers the viewer has authority to act as.
if ($this->needReviewerAuthority && $viewer_phid) {
$allow_key = 'differential.allow-self-accept';
$allow_self = PhabricatorEnv::getEnvConfig($allow_key);
$authority = $this->loadReviewerAuthority(
$revisions,
$edges,
$allow_self);
}
foreach ($revisions as $revision) {
$revision_edges = $edges[$revision->getPHID()][$edge_type];
$reviewers = array();
foreach ($revision_edges as $reviewer_phid => $edge) {
$reviewer = new DifferentialReviewer($reviewer_phid, $edge['data']);
if ($this->needReviewerAuthority) {
if (!$viewer_phid) {
// Logged-out users never have authority.
$has_authority = false;
} else if ((!$allow_self) &&
($revision->getAuthorPHID() == $viewer_phid)) {
// The author can never have authority unless we allow self-accept.
$has_authority = false;
} else {
// Otherwise, look up whether th viewer has authority.
$has_authority = isset($authority[$reviewer_phid]);
}
$reviewer->attachAuthority($viewer, $has_authority);
}
$reviewers[$reviewer_phid] = $reviewer;
}
$revision->attachReviewerStatus($reviewers);
}
}
public static function splitResponsible(array $revisions, array $user_phids) {
$blocking = array();
$active = array();
$waiting = array();
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
// Bucket revisions into $blocking (revisions where you are blocking
// others), $active (revisions you need to do something about) and $waiting
// (revisions you're waiting on someone else to do something about).
foreach ($revisions as $revision) {
$needs_review = ($revision->getStatus() == $status_review);
$filter_is_author = in_array($revision->getAuthorPHID(), $user_phids);
if (!$revision->getReviewers()) {
$needs_review = false;
}
// If exactly one of "needs review" and "the user is the author" is
// true, the user needs to act on it. Otherwise, they're waiting on
// it.
if ($needs_review ^ $filter_is_author) {
if ($needs_review) {
array_unshift($blocking, $revision);
} else {
$active[] = $revision;
}
} else {
$waiting[] = $revision;
}
}
return array($blocking, $active, $waiting);
}
private function loadReviewerAuthority(
array $revisions,
array $edges,
$allow_self) {
$revision_map = mpull($revisions, null, 'getPHID');
$viewer_phid = $this->getViewer()->getPHID();
// Find all the project reviewers which the user may have authority over.
$project_phids = array();
$project_type = PhabricatorProjectPHIDTypeProject::TYPECONST;
$edge_type = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER;
foreach ($edges as $src => $types) {
if (!$allow_self) {
if ($revision_map[$src]->getAuthorPHID() == $viewer_phid) {
// If self-review isn't permitted, the user will never have
// authority over projects on revisions they authored because you
// can't accept your own revisions, so we don't need to load any
// data about these reviewers.
continue;
}
}
$edge_data = idx($types, $edge_type, array());
foreach ($edge_data as $dst => $data) {
if (phid_get_type($dst) == $project_type) {
$project_phids[] = $dst;
}
}
}
// Now, figure out which of these projects the viewer is actually a
// member of.
$project_authority = array();
if ($project_phids) {
$project_authority = id(new PhabricatorProjectQuery())
->setViewer($this->getViewer())
->withPHIDs($project_phids)
->withMemberPHIDs(array($viewer_phid))
->execute();
$project_authority = mpull($project_authority, 'getPHID');
}
// Finally, the viewer has authority over themselves.
return array(
$viewer_phid => true,
) + array_fuse($project_authority);
}
public function getQueryApplicationClass() {
return 'PhabricatorApplicationDifferential';
}
}
diff --git a/src/applications/differential/query/DifferentialRevisionSearchEngine.php b/src/applications/differential/query/DifferentialRevisionSearchEngine.php
index b9e2969cce..eb80f6caeb 100644
--- a/src/applications/differential/query/DifferentialRevisionSearchEngine.php
+++ b/src/applications/differential/query/DifferentialRevisionSearchEngine.php
@@ -1,244 +1,246 @@
<?php
final class DifferentialRevisionSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getPageSize(PhabricatorSavedQuery $saved) {
if ($saved->getQueryKey() == 'active') {
return 0xFFFF;
}
return parent::getPageSize($saved);
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
$saved->setParameter(
'responsiblePHIDs',
$this->readUsersFromRequest($request, 'responsibles'));
$saved->setParameter(
'authorPHIDs',
$this->readUsersFromRequest($request, 'authors'));
$saved->setParameter(
'reviewerPHIDs',
$this->readUsersFromRequest(
$request,
'reviewers',
array(
PhabricatorProjectPHIDTypeProject::TYPECONST,
)));
$saved->setParameter(
'subscriberPHIDs',
$this->readUsersFromRequest($request, 'subscribers'));
$saved->setParameter(
'repositoryPHIDs',
$request->getArr('repositories'));
$saved->setParameter(
'draft',
$request->getBool('draft'));
$saved->setParameter(
'order',
$request->getStr('order'));
$saved->setParameter(
'status',
$request->getStr('status'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new DifferentialRevisionQuery())
+ ->needFlags(true)
+ ->needDrafts(true)
->needRelationships(true);
$responsible_phids = $saved->getParameter('responsiblePHIDs', array());
if ($responsible_phids) {
$query->withResponsibleUsers($responsible_phids);
}
$author_phids = $saved->getParameter('authorPHIDs', array());
if ($author_phids) {
$query->withAuthors($author_phids);
}
$reviewer_phids = $saved->getParameter('reviewerPHIDs', array());
if ($reviewer_phids) {
$query->withReviewers($reviewer_phids);
}
$subscriber_phids = $saved->getParameter('subscriberPHIDs', array());
if ($subscriber_phids) {
$query->withCCs($subscriber_phids);
}
$repository_phids = $saved->getParameter('repositoryPHIDs', array());
if ($repository_phids) {
$query->withRepositoryPHIDs($repository_phids);
}
$draft = $saved->getParameter('draft', false);
if ($draft && $this->requireViewer()->isLoggedIn()) {
$query->withDraftRepliesByAuthors(
array($this->requireViewer()->getPHID()));
}
$status = $saved->getParameter('status');
if (idx($this->getStatusOptions(), $status)) {
$query->withStatus($status);
}
$order = $saved->getParameter('order');
if (idx($this->getOrderOptions(), $order)) {
$query->setOrder($order);
} else {
$query->setOrder(DifferentialRevisionQuery::ORDER_CREATED);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$responsible_phids = $saved->getParameter('responsiblePHIDs', array());
$author_phids = $saved->getParameter('authorPHIDs', array());
$reviewer_phids = $saved->getParameter('reviewerPHIDs', array());
$subscriber_phids = $saved->getParameter('subscriberPHIDs', array());
$repository_phids = $saved->getParameter('repositoryPHIDs', array());
$only_draft = $saved->getParameter('draft', false);
$all_phids = array_mergev(
array(
$responsible_phids,
$author_phids,
$reviewer_phids,
$subscriber_phids,
$repository_phids,
));
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($all_phids)
->execute();
$form
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Responsible Users'))
->setName('responsibles')
->setDatasource('/typeahead/common/accounts/')
->setValue(array_select_keys($handles, $responsible_phids)))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Authors'))
->setName('authors')
->setDatasource('/typeahead/common/accounts/')
->setValue(array_select_keys($handles, $author_phids)))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Reviewers'))
->setName('reviewers')
->setDatasource('/typeahead/common/accountsorprojects/')
->setValue(array_select_keys($handles, $reviewer_phids)))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Subscribers'))
->setName('subscribers')
->setDatasource('/typeahead/common/allmailable/')
->setValue(array_select_keys($handles, $subscriber_phids)))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Repositories'))
->setName('repositories')
->setDatasource('/typeahead/common/repositories/')
->setValue(array_select_keys($handles, $repository_phids)))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Status'))
->setName('status')
->setOptions($this->getStatusOptions())
->setValue($saved->getParameter('status')));
if ($this->requireViewer()->isLoggedIn()) {
$form
->appendChild(
id(new AphrontFormCheckboxControl())
->addCheckbox(
'draft',
1,
pht('Show only revisions with a draft comment.'),
$only_draft));
}
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Order'))
->setName('order')
->setOptions($this->getOrderOptions())
->setValue($saved->getParameter('order')));
}
protected function getURI($path) {
return '/differential/'.$path;
}
public function getBuiltinQueryNames() {
$names = array();
if ($this->requireViewer()->isLoggedIn()) {
$names['active'] = pht('Active Revisions');
$names['authored'] = pht('Authored');
}
$names['all'] = pht('All Revisions');
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
$viewer = $this->requireViewer();
switch ($query_key) {
case 'active':
return $query
->setParameter('responsiblePHIDs', array($viewer->getPHID()))
->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN);
case 'authored':
return $query
->setParameter('authorPHIDs', array($viewer->getPHID()));
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
private function getStatusOptions() {
return array(
DifferentialRevisionQuery::STATUS_ANY => pht('All'),
DifferentialRevisionQuery::STATUS_OPEN => pht('Open'),
DifferentialRevisionQuery::STATUS_CLOSED => pht('Closed'),
DifferentialRevisionQuery::STATUS_ABANDONED => pht('Abandoned'),
);
}
private function getOrderOptions() {
return array(
DifferentialRevisionQuery::ORDER_CREATED => pht('Created'),
DifferentialRevisionQuery::ORDER_MODIFIED => pht('Updated'),
);
}
}
diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php
index c52a38e94c..79c73c58b0 100644
--- a/src/applications/differential/storage/DifferentialRevision.php
+++ b/src/applications/differential/storage/DifferentialRevision.php
@@ -1,466 +1,488 @@
<?php
final class DifferentialRevision extends DifferentialDAO
implements
PhabricatorTokenReceiverInterface,
PhabricatorPolicyInterface,
PhabricatorFlaggableInterface,
PhrequentTrackableInterface,
HarbormasterBuildableInterface,
PhabricatorSubscribableInterface,
PhabricatorCustomFieldInterface {
protected $title = '';
protected $originalTitle;
protected $status;
protected $summary = '';
protected $testPlan = '';
protected $authorPHID;
protected $lastReviewerPHID;
protected $dateCommitted;
protected $lineCount = 0;
protected $attached = array();
protected $mailKey;
protected $branchName;
protected $arcanistProjectPHID;
protected $repositoryPHID;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $editPolicy = PhabricatorPolicies::POLICY_USER;
private $relationships = self::ATTACHABLE;
private $commits = self::ATTACHABLE;
private $activeDiff = self::ATTACHABLE;
private $diffIDs = self::ATTACHABLE;
private $hashes = self::ATTACHABLE;
private $repository = self::ATTACHABLE;
private $reviewerStatus = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
+ private $drafts = array();
+ private $flags = array();
const TABLE_COMMIT = 'differential_commit';
const RELATION_REVIEWER = 'revw';
const RELATION_SUBSCRIBED = 'subd';
public static function initializeNewRevision(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorApplicationDifferential'))
->executeOne();
$view_policy = $app->getPolicy(
DifferentialCapabilityDefaultView::CAPABILITY);
return id(new DifferentialRevision())
->setViewPolicy($view_policy)
->setAuthorPHID($actor->getPHID())
->attachRelationships(array())
->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'attached' => self::SERIALIZATION_JSON,
'unsubscribed' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function setTitle($title) {
$this->title = $title;
if (!$this->getID()) {
$this->originalTitle = $title;
}
return $this;
}
public function loadIDsByCommitPHIDs($phids) {
if (!$phids) {
return array();
}
$revision_ids = queryfx_all(
$this->establishConnection('r'),
'SELECT * FROM %T WHERE commitPHID IN (%Ls)',
self::TABLE_COMMIT,
$phids);
return ipull($revision_ids, 'revisionID', 'commitPHID');
}
public function loadCommitPHIDs() {
if (!$this->getID()) {
return ($this->commits = array());
}
$commits = queryfx_all(
$this->establishConnection('r'),
'SELECT commitPHID FROM %T WHERE revisionID = %d',
self::TABLE_COMMIT,
$this->getID());
$commits = ipull($commits, 'commitPHID');
return ($this->commits = $commits);
}
public function getCommitPHIDs() {
return $this->assertAttached($this->commits);
}
public function getActiveDiff() {
// TODO: Because it's currently technically possible to create a revision
// without an associated diff, we allow an attached-but-null active diff.
// It would be good to get rid of this once we make diff-attaching
// transactional.
return $this->assertAttached($this->activeDiff);
}
public function attachActiveDiff($diff) {
$this->activeDiff = $diff;
return $this;
}
public function getDiffIDs() {
return $this->assertAttached($this->diffIDs);
}
public function attachDiffIDs(array $ids) {
rsort($ids);
$this->diffIDs = array_values($ids);
return $this;
}
public function attachCommitPHIDs(array $phids) {
$this->commits = array_values($phids);
return $this;
}
public function getAttachedPHIDs($type) {
return array_keys(idx($this->attached, $type, array()));
}
public function setAttachedPHIDs($type, array $phids) {
$this->attached[$type] = array_fill_keys($phids, array());
return $this;
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
DifferentialPHIDTypeRevision::TYPECONST);
}
public function loadComments() {
if (!$this->getID()) {
return array();
}
return id(new DifferentialCommentQuery())
->withRevisionPHIDs(array($this->getPHID()))
->execute();
}
public function loadActiveDiff() {
return id(new DifferentialDiff())->loadOneWhere(
'revisionID = %d ORDER BY id DESC LIMIT 1',
$this->getID());
}
public function save() {
if (!$this->getMailKey()) {
$this->mailKey = Filesystem::readRandomCharacters(40);
}
return parent::save();
}
public function delete() {
$this->openTransaction();
$diffs = id(new DifferentialDiffQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withRevisionIDs(array($this->getID()))
->execute();
foreach ($diffs as $diff) {
$diff->delete();
}
$conn_w = $this->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE revisionID = %d',
self::TABLE_COMMIT,
$this->getID());
$comments = id(new DifferentialCommentQuery())
->withRevisionPHIDs(array($this->getPHID()))
->execute();
foreach ($comments as $comment) {
$comment->delete();
}
$inlines = id(new DifferentialInlineCommentQuery())
->withRevisionIDs(array($this->getID()))
->execute();
foreach ($inlines as $inline) {
$inline->delete();
}
$fields = id(new DifferentialAuxiliaryField())->loadAllWhere(
'revisionPHID = %s',
$this->getPHID());
foreach ($fields as $field) {
$field->delete();
}
// we have to do paths a little differentally as they do not have
// an id or phid column for delete() to act on
$dummy_path = new DifferentialAffectedPath();
queryfx(
$conn_w,
'DELETE FROM %T WHERE revisionID = %d',
$dummy_path->getTableName(),
$this->getID());
$result = parent::delete();
$this->saveTransaction();
return $result;
}
public function loadRelationships() {
if (!$this->getID()) {
$this->relationships = array();
return;
}
$data = array();
$subscriber_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->getPHID(),
PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER);
$subscriber_phids = array_reverse($subscriber_phids);
foreach ($subscriber_phids as $phid) {
$data[] = array(
'relation' => self::RELATION_SUBSCRIBED,
'objectPHID' => $phid,
'reasonPHID' => null,
);
}
$reviewer_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$this->getPHID(),
PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER);
$reviewer_phids = array_reverse($reviewer_phids);
foreach ($reviewer_phids as $phid) {
$data[] = array(
'relation' => self::RELATION_REVIEWER,
'objectPHID' => $phid,
'reasonPHID' => null,
);
}
return $this->attachRelationships($data);
}
public function attachRelationships(array $relationships) {
$this->relationships = igroup($relationships, 'relation');
return $this;
}
public function getReviewers() {
return $this->getRelatedPHIDs(self::RELATION_REVIEWER);
}
public function getCCPHIDs() {
return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED);
}
private function getRelatedPHIDs($relation) {
$this->assertAttached($this->relationships);
return ipull($this->getRawRelations($relation), 'objectPHID');
}
public function getRawRelations($relation) {
return idx($this->relationships, $relation, array());
}
public function getPrimaryReviewer() {
$reviewers = $this->getReviewers();
$last = $this->lastReviewerPHID;
if (!$last || !in_array($last, $reviewers)) {
return head($this->getReviewers());
}
return $last;
}
public function loadReviewedBy() {
$reviewer = null;
if ($this->status == ArcanistDifferentialRevisionStatus::ACCEPTED ||
$this->status == ArcanistDifferentialRevisionStatus::CLOSED) {
$comments = $this->loadComments();
foreach ($comments as $comment) {
$action = $comment->getAction();
if ($action == DifferentialAction::ACTION_ACCEPT) {
$reviewer = $comment->getAuthorPHID();
} else if ($action == DifferentialAction::ACTION_REJECT ||
$action == DifferentialAction::ACTION_ABANDON ||
$action == DifferentialAction::ACTION_RETHINK) {
$reviewer = null;
}
}
}
return $reviewer;
}
public function getHashes() {
return $this->assertAttached($this->hashes);
}
public function attachHashes(array $hashes) {
$this->hashes = $hashes;
return $this;
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
// A revision's author (which effectively means "owner" after we added
// commandeering) can always view and edit it.
$author_phid = $this->getAuthorPHID();
if ($author_phid) {
if ($user->getPHID() == $author_phid) {
return true;
}
}
return false;
}
public function describeAutomaticCapability($capability) {
$description = array(
pht('The owner of a revision can always view and edit it.'),
);
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$description[] = pht(
"A revision's reviewers can always view it.");
$description[] = pht(
'If a revision belongs to a repository, other users must be able '.
'to view the repository in order to view the revision.');
break;
}
return $description;
}
public function getUsersToNotifyOfTokenGiven() {
return array(
$this->getAuthorPHID(),
);
}
public function getReviewerStatus() {
return $this->assertAttached($this->reviewerStatus);
}
public function attachReviewerStatus(array $reviewers) {
assert_instances_of($reviewers, 'DifferentialReviewer');
$this->reviewerStatus = $reviewers;
return $this;
}
public function getRepository() {
return $this->assertAttached($this->repository);
}
public function attachRepository(PhabricatorRepository $repository = null) {
$this->repository = $repository;
return $this;
}
public function isClosed() {
return DifferentialRevisionStatus::isClosedStatus($this->getStatus());
}
+ public function getFlag(PhabricatorUser $viewer) {
+ return $this->assertAttachedKey($this->flags, $viewer->getPHID());
+ }
+
+ public function attachFlag(
+ PhabricatorUser $viewer,
+ PhabricatorFlag $flag = null) {
+ $this->flags[$viewer->getPHID()] = $flag;
+ return $this;
+ }
+
+ public function getDrafts(PhabricatorUser $viewer) {
+ return $this->assertAttachedKey($this->drafts, $viewer->getPHID());
+ }
+
+ public function attachDrafts(PhabricatorUser $viewer, array $drafts) {
+ $this->drafts[$viewer->getPHID()] = $drafts;
+ return $this;
+ }
+
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildablePHID() {
return $this->loadActiveDiff()->getPHID();
}
public function getHarbormasterContainerPHID() {
return $this->getPHID();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
// TODO: Reviewers are also automatically subscribed, but that data may
// not always be loaded, so be conservative for now. All the editing code
// has checks around this already.
return ($phid == $this->getAuthorPHID());
}
public function shouldShowSubscribersProperty() {
// TODO: For now, Differential has its own stuff.
return false;
}
public function shouldAllowSubscription($phid) {
// TODO: For now, Differential has its own stuff.
return false;
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return array_fill_keys(
array(
),
array('disabled' => false));
}
public function getCustomFieldBaseClass() {
return 'DifferentialCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php
index 0addfa5b62..52565bb5ff 100644
--- a/src/applications/differential/view/DifferentialRevisionListView.php
+++ b/src/applications/differential/view/DifferentialRevisionListView.php
@@ -1,253 +1,226 @@
<?php
/**
* Render a table of Differential revisions.
*/
final class DifferentialRevisionListView extends AphrontView {
private $revisions;
- private $flags = array();
- private $drafts = array();
private $handles;
private $fields;
private $highlightAge;
private $header;
private $noDataString;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
}
public function setHeader($header) {
$this->header = $header;
return $this;
}
public function setFields(array $fields) {
assert_instances_of($fields, 'DifferentialFieldSpecification');
$this->fields = $fields;
return $this;
}
public function setRevisions(array $revisions) {
assert_instances_of($revisions, 'DifferentialRevision');
$this->revisions = $revisions;
return $this;
}
public function setHighlightAge($bool) {
$this->highlightAge = $bool;
return $this;
}
public function getRequiredHandlePHIDs() {
$phids = array();
foreach ($this->fields as $field) {
foreach ($this->revisions as $revision) {
$phids[] = $field->getRequiredHandlePHIDsForRevisionList($revision);
}
}
return array_mergev($phids);
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
- public function loadAssets() {
- $user = $this->user;
- if (!$user) {
- throw new Exception("Call setUser() before loadAssets()!");
- }
- if ($this->revisions === null) {
- throw new Exception("Call setRevisions() before loadAssets()!");
- }
-
- $this->flags = id(new PhabricatorFlagQuery())
- ->setViewer($user)
- ->withOwnerPHIDs(array($user->getPHID()))
- ->withObjectPHIDs(mpull($this->revisions, 'getPHID'))
- ->execute();
-
- $this->drafts = id(new DifferentialRevisionQuery())
- ->setViewer($user)
- ->withIDs(mpull($this->revisions, 'getID'))
- ->withDraftRepliesByAuthors(array($user->getPHID()))
- ->execute();
-
- return $this;
- }
-
public function render() {
$user = $this->user;
if (!$user) {
throw new Exception("Call setUser() before render()!");
}
$fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh');
if ($fresh) {
$fresh = PhabricatorCalendarHoliday::getNthBusinessDay(
time(),
-$fresh);
}
$stale = PhabricatorEnv::getEnvConfig('differential.days-stale');
if ($stale) {
$stale = PhabricatorCalendarHoliday::getNthBusinessDay(
time(),
-$stale);
}
$this->initBehavior('phabricator-tooltips', array());
$this->requireResource('aphront-tooltip-css');
- $flagged = mpull($this->flags, null, 'getObjectPHID');
-
foreach ($this->fields as $field) {
$field->setHandles($this->handles);
}
$list = new PHUIObjectItemListView();
$list->setCards(true);
foreach ($this->revisions as $revision) {
$item = id(new PHUIObjectItemView())
->setUser($user);
$rev_fields = array();
$icons = array();
$phid = $revision->getPHID();
- if (isset($flagged[$phid])) {
- $flag = $flagged[$phid];
+ $flag = $revision->getFlag($user);
+ if ($flag) {
$flag_class = PhabricatorFlagColor::getCSSClass($flag->getColor());
$icons['flag'] = phutil_tag(
'div',
array(
'class' => 'phabricator-flag-icon '.$flag_class,
),
'');
}
- if (array_key_exists($revision->getID(), $this->drafts)) {
+
+ if ($revision->getDrafts($user)) {
$icons['draft'] = true;
}
$modified = $revision->getDateModified();
$status = $revision->getStatus();
$show_age = ($fresh || $stale) &&
$this->highlightAge &&
!$revision->isClosed();
$object_age = PHUIObjectItemView::AGE_FRESH;
foreach ($this->fields as $field) {
if ($show_age) {
if ($field instanceof DifferentialDateModifiedFieldSpecification) {
if ($stale && $modified < $stale) {
$object_age = PHUIObjectItemView::AGE_OLD;
} else if ($fresh && $modified < $fresh) {
$object_age = PHUIObjectItemView::AGE_STALE;
}
}
}
$rev_header = $field->renderHeaderForRevisionList();
$rev_fields[$rev_header] = $field
->renderValueForRevisionList($revision);
}
$status_name =
ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status);
if (isset($icons['flag'])) {
$item->addHeadIcon($icons['flag']);
}
$item->setObjectName('D'.$revision->getID());
$item->setHeader(phutil_tag('a',
array('href' => '/D'.$revision->getID()),
$revision->getTitle()));
if (isset($icons['draft'])) {
$draft = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_ICONS)
->setSpriteIcon('file-grey')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Unsubmitted Comments'),
));
$item->addAttribute($draft);
}
$item->addAttribute($status_name);
// Author
$author_handle = $this->handles[$revision->getAuthorPHID()];
$item->addByline(pht('Author: %s', $author_handle->renderLink()));
// Reviewers
$item->addAttribute(pht('Reviewers: %s', $rev_fields['Reviewers']));
$item->setEpoch($revision->getDateModified(), $object_age);
// First remove the fields we already have
$count = 7;
$rev_fields = array_slice($rev_fields, $count);
// Then add each one of them
// TODO: Add render-to-foot-icon support
foreach ($rev_fields as $header => $field) {
$item->addAttribute(pht('%s: %s', $header, $field));
}
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$item->setBarColor('red');
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$item->setBarColor('green');
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
$item->setDisabled(true);
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
$item->setBarColor('black');
break;
}
$list->addItem($item);
}
$list->setHeader($this->header);
$list->setNoDataString($this->noDataString);
return $list;
}
public static function getDefaultFields(PhabricatorUser $user) {
$selector = DifferentialFieldSelector::newSelector();
$fields = $selector->getFieldSpecifications();
foreach ($fields as $key => $field) {
$field->setUser($user);
if (!$field->shouldAppearOnRevisionList()) {
unset($fields[$key]);
}
}
if (!$fields) {
throw new Exception(
"Phabricator configuration has no fields that appear on the list ".
"interface!");
}
return $selector->sortFieldsForRevisionList($fields);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php
index 73e0e73ef4..0c1a7325e2 100644
--- a/src/applications/diffusion/controller/DiffusionBrowseController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseController.php
@@ -1,226 +1,227 @@
<?php
abstract class DiffusionBrowseController extends DiffusionController {
public function shouldAllowPublic() {
return true;
}
protected function renderSearchForm($collapsed) {
$drequest = $this->getDiffusionRequest();
$forms = array();
$form = id(new AphrontFormView())
->setUser($this->getRequest()->getUser())
->setMethod('GET');
switch ($drequest->getRepository()->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$forms[] = id(clone $form)
->appendChild(pht('Search is not available in Subversion.'));
break;
default:
$forms[] = id(clone $form)
->appendChild(
id(new AphrontFormTextWithSubmitControl())
->setLabel(pht('File Name'))
->setSubmitLabel(pht('Search File Names'))
->setName('find')
->setValue($this->getRequest()->getStr('find')));
$forms[] = id(clone $form)
->appendChild(
id(new AphrontFormTextWithSubmitControl())
->setLabel(pht('Pattern'))
->setSubmitLabel(pht('Grep File Content'))
->setName('grep')
->setValue($this->getRequest()->getStr('grep')));
break;
}
$filter = new AphrontListFilterView();
$filter->appendChild($forms);
if ($collapsed) {
$filter->setCollapsed(
pht('Show Search'),
pht('Hide Search'),
pht('Search for file names or content in this directory.'),
'#');
}
return $filter;
}
protected function markupText($text) {
$engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
$engine->setConfig('viewer', $this->getRequest()->getUser());
$text = $engine->markupText($text);
$text = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$text);
return $text;
}
protected function buildHeaderView(DiffusionRequest $drequest) {
$viewer = $this->getRequest()->getUser();
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($this->renderPathLinks($drequest, $mode = 'browse'))
->setPolicyObject($drequest->getRepository());
return $header;
}
protected function buildActionView(DiffusionRequest $drequest) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$history_uri = $drequest->generateURI(
array(
'action' => 'history',
));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setHref($history_uri)
->setIcon('history'));
$behind_head = $drequest->getRawCommit();
$head_uri = $drequest->generateURI(
array(
'commit' => '',
'action' => 'browse',
));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Jump to HEAD'))
->setHref($head_uri)
->setIcon('home')
->setDisabled(!$behind_head));
// TODO: Ideally, this should live in Owners and be event-triggered, but
// there's no reasonable object for it to react to right now.
$owners = 'PhabricatorApplicationOwners';
if (PhabricatorApplication::isClassInstalled($owners)) {
$owners_uri = id(new PhutilURI('/owners/view/search/'))
->setQueryParams(
array(
'repository' => $drequest->getCallsign(),
'path' => '/'.$drequest->getPath(),
));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Find Owners'))
->setHref((string)$owners_uri)
->setIcon('preview'));
}
return $view;
}
protected function buildPropertyView(
DiffusionRequest $drequest,
PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($actions);
$stable_commit = $drequest->getStableCommitName();
$callsign = $drequest->getRepository()->getCallsign();
$view->addProperty(
pht('Commit'),
phutil_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $stable_commit,
)),
),
$drequest->getRepository()->formatCommitName($stable_commit)));
if ($drequest->getCommitType() == 'tag') {
$symbolic = $drequest->getSymbolicCommit();
$view->addProperty(pht('Tag'), $symbolic);
$tags = $this->callConduitWithDiffusionRequest(
'diffusion.tagsquery',
array(
'names' => array($symbolic),
'needMessages' => true,
));
$tags = DiffusionRepositoryTag::newFromConduit($tags);
$tags = mpull($tags, null, 'getName');
$tag = idx($tags, $symbolic);
if ($tag && strlen($tag->getMessage())) {
$view->addSectionHeader(pht('Tag Content'));
$view->addTextContent($this->markupText($tag->getMessage()));
}
}
return $view;
}
protected function buildOpenRevisions() {
$user = $this->getRequest()->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$path_map = id(new DiffusionPathIDQuery(array($path)))->loadPathIDs();
$path_id = idx($path_map, $path);
if (!$path_id) {
return null;
}
$revisions = id(new DifferentialRevisionQuery())
->setViewer($user)
->withPath($repository->getID(), $path_id)
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->setOrder(DifferentialRevisionQuery::ORDER_PATH_MODIFIED)
->setLimit(10)
->needRelationships(true)
+ ->needFlags(true)
+ ->needDrafts(true)
->execute();
if (!$revisions) {
return null;
}
$view = id(new DifferentialRevisionListView())
->setRevisions($revisions)
->setFields(DifferentialRevisionListView::getDefaultFields($user))
- ->setUser($user)
- ->loadAssets();
+ ->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Pending Differential Revisions'))
->appendChild($view);
}
}
diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php
index c3a60dddde..b4cb8a0e3b 100644
--- a/src/applications/home/controller/PhabricatorHomeMainController.php
+++ b/src/applications/home/controller/PhabricatorHomeMainController.php
@@ -1,465 +1,466 @@
<?php
final class PhabricatorHomeMainController
extends PhabricatorHomeController {
private $filter;
private $minipanels = array();
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->filter = idx($data, 'filter');
}
public function processRequest() {
$user = $this->getRequest()->getUser();
if ($this->filter == 'jump') {
return $this->buildJumpResponse();
}
$nav = $this->buildNav();
$project_query = new PhabricatorProjectQuery();
$project_query->setViewer($user);
$project_query->withMemberPHIDs(array($user->getPHID()));
$projects = $project_query->execute();
return $this->buildMainResponse($nav, $projects);
}
private function buildMainResponse($nav, array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$maniphest = 'PhabricatorApplicationManiphest';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$unbreak_panel = $this->buildUnbreakNowPanel();
$triage_panel = $this->buildNeedsTriagePanel($projects);
$tasks_panel = $this->buildTasksPanel();
} else {
$unbreak_panel = null;
$triage_panel = null;
$tasks_panel = null;
}
$audit = 'PhabricatorApplicationAudit';
if (PhabricatorApplication::isClassInstalled($audit)) {
$audit_panel = $this->buildAuditPanel();
$commit_panel = $this->buildCommitPanel();
} else {
$audit_panel = null;
$commit_panel = null;
}
if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) {
$welcome_panel = $this->buildWelcomePanel();
} else {
$welcome_panel = null;
}
$jump_panel = $this->buildJumpPanel();
$revision_panel = $this->buildRevisionPanel();
$content = array(
$jump_panel,
$welcome_panel,
$unbreak_panel,
$triage_panel,
$revision_panel,
$tasks_panel,
$audit_panel,
$commit_panel,
$this->minipanels,
);
$nav->appendChild($content);
$nav->appendChild(new PhabricatorGlobalUploadTargetView());
return $this->buildApplicationPage(
$nav,
array(
'title' => 'Phabricator',
'device' => true,
));
}
private function buildJumpResponse() {
$request = $this->getRequest();
$jump = $request->getStr('jump');
$response = PhabricatorJumpNavHandler::getJumpResponse(
$request->getUser(),
$jump);
if ($response) {
return $response;
} else if ($request->isFormPost()) {
$uri = new PhutilURI('/search/');
$uri->setQueryParam('query', $jump);
$uri->setQueryParam('search:primary', 'true');
return id(new AphrontRedirectResponse())->setURI((string)$uri);
} else {
return id(new AphrontRedirectResponse())->setURI('/');
}
}
private function buildUnbreakNowPanel() {
$unbreak_now = PhabricatorEnv::getEnvConfig(
'maniphest.priorities.unbreak-now');
if (!$unbreak_now) {
return null;
}
$user = $this->getRequest()->getUser();
$task_query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->withPriorities(array($unbreak_now))
->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No "Unbreak Now!" Tasks',
'Nothing appears to be critically broken right now.');
}
$href = '/maniphest/?statuses[]=0&priorities[]='.$unbreak_now.'#R';
$title = pht('Unbreak Now!');
$panel = new AphrontPanelView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildNeedsTriagePanel(array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$needs_triage = PhabricatorEnv::getEnvConfig(
'maniphest.priorities.needs-triage');
if (!$needs_triage) {
return null;
}
$user = $this->getRequest()->getUser();
if (!$user->isLoggedIn()) {
return null;
}
if ($projects) {
$task_query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->withPriorities(array($needs_triage))
->withAnyProjects(mpull($projects, 'getPHID'))
->setLimit(10);
$tasks = $task_query->execute();
} else {
$tasks = array();
}
if (!$tasks) {
return $this->renderMiniPanel(
'No "Needs Triage" Tasks',
hsprintf(
'No tasks in <a href="/project/">projects you are a member of</a> '.
'need triage.'));
}
$title = pht('Needs Triage');
$href = '/maniphest/?statuses[]=0&priorities[]='.$needs_triage.
'&userProjects[]='.$user->getPHID().'#R';
$panel = new AphrontPanelView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildRevisionPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$revision_query = id(new DifferentialRevisionQuery())
->setViewer($user)
->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
->withResponsibleUsers(array($user_phid))
- ->needRelationships(true);
+ ->needRelationships(true)
+ ->needFlags(true)
+ ->needDrafts(true);
$revisions = $revision_query->execute();
list($blocking, $active, ) = DifferentialRevisionQuery::splitResponsible(
$revisions,
array($user_phid));
if (!$blocking && !$active) {
return $this->renderMiniPanel(
'No Waiting Revisions',
'No revisions are waiting on you.');
}
$title = pht('Revisions Waiting on You');
$href = '/differential';
$panel = new AphrontPanelView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$revision_view = id(new DifferentialRevisionListView())
->setHighlightAge(true)
->setRevisions(array_merge($blocking, $active))
->setFields(DifferentialRevisionListView::getDefaultFields($user))
- ->setUser($user)
- ->loadAssets();
+ ->setUser($user);
$phids = array_merge(
array($user_phid),
$revision_view->getRequiredHandlePHIDs());
$handles = $this->loadViewerHandles($phids);
$revision_view->setHandles($handles);
$list_view = $revision_view->render();
$list_view->setFlush(true);
$panel->appendChild($list_view);
$panel->setNoBackground();
return $panel;
}
private function buildWelcomePanel() {
$panel = new AphrontPanelView();
$panel->appendChild(
phutil_safe_html(
PhabricatorEnv::getEnvConfig('welcome.html')));
$panel->setNoBackground();
return $panel;
}
private function buildTasksPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = id(new ManiphestTaskQuery())
->setViewer($user)
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY)
->withOwners(array($user_phid))
->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No Assigned Tasks',
'You have no assigned tasks.');
}
$title = pht('Assigned Tasks');
$href = '/maniphest';
$panel = new AphrontPanelView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->appendChild($this->buildTaskListView($tasks));
$panel->setNoBackground();
return $panel;
}
private function buildTaskListView(array $tasks) {
assert_instances_of($tasks, 'ManiphestTask');
$user = $this->getRequest()->getUser();
$phids = array_merge(
array_filter(mpull($tasks, 'getOwnerPHID')),
array_mergev(mpull($tasks, 'getProjectPHIDs')));
$handles = $this->loadViewerHandles($phids);
$view = new ManiphestTaskListView();
$view->setTasks($tasks);
$view->setUser($user);
$view->setHandles($handles);
return $view;
}
private function buildJumpPanel($query=null) {
$request = $this->getRequest();
$user = $request->getUser();
$uniq_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'phabricator-autofocus',
array(
'id' => $uniq_id,
));
require_celerity_resource('phabricator-jump-nav');
$doc_href = PhabricatorEnv::getDocLink('article/Jump_Nav_User_Guide.html');
$doc_link = phutil_tag(
'a',
array(
'href' => $doc_href,
),
'Jump Nav User Guide');
$jump_input = phutil_tag(
'input',
array(
'type' => 'text',
'class' => 'phabricator-jump-nav',
'name' => 'jump',
'id' => $uniq_id,
'value' => $query,
));
$jump_caption = phutil_tag(
'p',
array(
'class' => 'phabricator-jump-nav-caption',
),
hsprintf(
'Enter the name of an object like <tt>D123</tt> to quickly jump to '.
'it. See %s or type <tt>help</tt>.',
$doc_link));
$form = phabricator_form(
$user,
array(
'action' => '/jump/',
'method' => 'POST',
'class' => 'phabricator-jump-nav-form',
),
array(
$jump_input,
$jump_caption,
));
$panel = new AphrontPanelView();
$panel->setNoBackground();
// $panel->appendChild();
$list_filter = new AphrontListFilterView();
$list_filter->appendChild($form);
$container = phutil_tag('div',
array('class' => 'phabricator-jump-nav-container'),
$list_filter);
return $container;
}
private function renderSectionHeader($title, $href) {
$header = phutil_tag(
'a',
array(
'href' => $href,
),
$title);
return $header;
}
private function renderMiniPanel($title, $body) {
$panel = new AphrontMiniPanelView();
$panel->appendChild(
phutil_tag(
'p',
array(
),
array(
phutil_tag('strong', array(), $title.': '),
$body
)));
$this->minipanels[] = $panel;
}
public function buildAuditPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$query = new PhabricatorAuditQuery();
$query->withAuditorPHIDs($phids);
$query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
$query->withAwaitingUser($user);
$query->needCommitData(true);
$query->setLimit(10);
$audits = $query->execute();
$commits = $query->getCommits();
if (!$audits) {
return $this->renderMinipanel(
'No Audits',
'No commits are waiting for you to audit them.');
}
$view = new PhabricatorAuditListView();
$view->setAudits($audits);
$view->setCommits($commits);
$view->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$title = pht('Audits');
$href = '/audit/';
$panel = new AphrontPanelView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->appendChild($view);
$panel->setNoBackground();
return $panel;
}
public function buildCommitPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = array($user->getPHID());
$query = new PhabricatorAuditCommitQuery();
$query->withAuthorPHIDs($phids);
$query->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN);
$query->needCommitData(true);
$query->setLimit(10);
$commits = $query->execute();
if (!$commits) {
return $this->renderMinipanel(
'No Problem Commits',
'No one has raised concerns with your commits.');
}
$view = new PhabricatorAuditCommitListView();
$view->setCommits($commits);
$view->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$title = pht('Problem Commits');
$href = '/audit/';
$panel = new AphrontPanelView();
$panel->setHeader($this->renderSectionHeader($title, $href));
$panel->appendChild($view);
$panel->setNoBackground();
return $panel;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 7, 10:17 AM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
223082
Default Alt Text
(117 KB)

Event Timeline